Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8b5732046 | ||
|
|
02de7c6c57 | ||
|
|
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,14 +29,92 @@ 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");
|
||||
|
||||
// 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");
|
||||
|
||||
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");
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,14 +171,51 @@ 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");
|
||||
|
||||
// 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");
|
||||
|
||||
EnsureColumnSQLite(conn, "MeterTestRslt", "FlipMode", "INTEGER NULL");
|
||||
EnsureColumnSQLite(conn, "WaterMeter", "CalibFactorNominal", "REAL NOT NULL DEFAULT 4096");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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")]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@@ -1770,4 +1770,52 @@
|
||||
<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>
|
||||
<data name="GenesisTooltip_nameTextBox" xml:space="preserve"><value>Name
|
||||
|
||||
Účel: Identifikuje komponentu, na kterou odkazují testy procedury.
|
||||
Použití: Vyhledání komponenty a výběr Method v proceduře.
|
||||
Rozsah hodnot: Jedinečný název komponenty.
|
||||
Výchozí hodnota: SmartCommunicationGenesis
|
||||
Uplatnění: Po přejmenování je nutný restart; zkontrolujte odkazy v procedurách.</value></data>
|
||||
<data name="GenesisTooltip_commTimeoutTextBox" xml:space="preserve"><value>CommTimeout
|
||||
|
||||
Účel: Časový limit pro požadavek na zrušení jednotlivého Genesis GCI volání.
|
||||
Použití: Volání GciBridge z GenesisCommunicationForm (připojení, login, registry a související slot operace).
|
||||
Rozsah hodnot: Celé číslo: 500–5000 ms.
|
||||
Výchozí hodnota: 1800 ms.
|
||||
Uplatnění: Načte se na začátku aktivity vodoměru. Každé volání/pokus má vlastní limit; více kroků může trvat déle. Zrušení musí podporovat spodní vrstva ovladače. Ostatní uživatelé bridge zachovají původní limity.</value></data>
|
||||
<data name="GenesisTooltip_maxCommRetriesTextBox" xml:space="preserve"><value>MaxCommRetries
|
||||
|
||||
Účel: Uložené nastavení opakování komunikace.
|
||||
Použití: Aktuální tok Genesis slot/GCI jej nečte; nespouští opakování neúspěšné operace.
|
||||
Rozsah hodnot: Celé číslo: 1–10.
|
||||
Výchozí hodnota: 4
|
||||
Uplatnění: Zachováno pro kompatibilitu; bez vlivu na opakování slot/GCI.</value></data>
|
||||
<data name="GenesisTooltip_delayBetweenRetriesTextBox" xml:space="preserve"><value>DelayBetweenRetries
|
||||
|
||||
Účel: Uložená prodleva mezi opakováními komunikace.
|
||||
Použití: Aktuální tok Genesis slot/GCI ji nečte; neřídí zpoždění zavření formuláře.
|
||||
Rozsah hodnot: Celé číslo: 0–5000 ms.
|
||||
Výchozí hodnota: 0 ms
|
||||
Uplatnění: Zachováno pro kompatibilitu; bez vlivu na prodlevy slot/GCI.</value></data>
|
||||
<data name="GenesisTooltip_nrThreadsTextBox" xml:space="preserve"><value>NrThreads
|
||||
|
||||
Účel: Počet pracovních vláken pro nakonfigurované hlavy Genesis.
|
||||
Použití: GenesisCommunicationForm: vytvoření vláken a rozdělení slotů. Nemění počet vodoměrů.
|
||||
Rozsah hodnot: Celé číslo: 1–10.
|
||||
Výchozí hodnota: 10
|
||||
Uplatnění: Vyžaduje restart. Sestavení TURA_SPECIAL používají samostatné přidělování slotů.</value></data>
|
||||
<data name="GenesisTooltip_iperlCheckErrorsToStopTextBox" xml:space="preserve"><value>IperlCheckErrorsToStop
|
||||
|
||||
Účel: Počet neúspěšných vodoměrů, při kterém se zobrazí výsledek kontroly.
|
||||
Použití: GenesisCommunicationSeq: aktivita iPERL_check při sestavení s IPERL. Uživatel může pokračovat nebo přerušit; nejde o obecný limit chyb slotů.
|
||||
Rozsah hodnot: Celé číslo: 1–40. Práh: počet neúspěšných >= nastavení.
|
||||
Výchozí hodnota: 10
|
||||
Uplatnění: Použije se při následující příslušné kontrole; ne při běžných slot aktivitách.</value></data>
|
||||
</root>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@@ -2232,4 +2232,52 @@
|
||||
<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>
|
||||
<data name="GenesisTooltip_nameTextBox" xml:space="preserve"><value>Name
|
||||
|
||||
Zweck: Identifiziert die Komponente, auf die die Tests der Prozedur verweisen.
|
||||
Verwendung: Komponentensuche und Auswahl von Method in der Prozedur.
|
||||
Wertebereich: Eindeutiger Komponentenname.
|
||||
Standardwert: SmartCommunicationGenesis
|
||||
Anwendung: Nach Umbenennung neu starten und Prozedurverweise prüfen.</value></data>
|
||||
<data name="GenesisTooltip_commTimeoutTextBox" xml:space="preserve"><value>CommTimeout
|
||||
|
||||
Zweck: Frist für die Abbruchanforderung eines einzelnen Genesis-GCI-Aufrufs.
|
||||
Verwendung: GciBridge-Aufrufe aus GenesisCommunicationForm (Verbindung, Login, Register und zugehörige Slot-Operationen).
|
||||
Wertebereich: Ganzzahl: 500–5000 ms.
|
||||
Standardwert: 1800 ms.
|
||||
Anwendung: Zu Beginn jeder Zähleraktivität gelesen. Jeder Aufruf/Versuch erhält eine eigene Frist; mehrere Schritte können länger dauern. Der Treiber muss den Abbruch unterstützen. Andere Bridge-Aufrufer behalten ihre bisherigen Zeitlimits.</value></data>
|
||||
<data name="GenesisTooltip_maxCommRetriesTextBox" xml:space="preserve"><value>MaxCommRetries
|
||||
|
||||
Zweck: Gespeicherte Einstellung für Kommunikationswiederholungen.
|
||||
Verwendung: Der aktuelle Genesis-Slot/GCI-Ablauf liest diesen Wert nicht; fehlgeschlagene Operationen werden dadurch nicht wiederholt.
|
||||
Wertebereich: Ganzzahl: 1–10.
|
||||
Standardwert: 4
|
||||
Anwendung: Für Kompatibilität gespeichert; keine Wirkung auf Slot/GCI-Wiederholungen.</value></data>
|
||||
<data name="GenesisTooltip_delayBetweenRetriesTextBox" xml:space="preserve"><value>DelayBetweenRetries
|
||||
|
||||
Zweck: Gespeicherte Pause zwischen Kommunikationswiederholungen.
|
||||
Verwendung: Der aktuelle Genesis-Slot/GCI-Ablauf liest diesen Wert nicht; die Verzögerung beim Schließen des Formulars bleibt unverändert.
|
||||
Wertebereich: Ganzzahl: 0–5000 ms.
|
||||
Standardwert: 0 ms
|
||||
Anwendung: Für Kompatibilität gespeichert; keine Wirkung auf Slot/GCI-Pausen.</value></data>
|
||||
<data name="GenesisTooltip_nrThreadsTextBox" xml:space="preserve"><value>NrThreads
|
||||
|
||||
Zweck: Anzahl der Arbeitsthreads für konfigurierte Genesis-Köpfe.
|
||||
Verwendung: GenesisCommunicationForm: Thread-Erstellung und Slot-Verteilung. Ändert nicht die Anzahl der Zähler.
|
||||
Wertebereich: Ganzzahl: 1–10.
|
||||
Standardwert: 10
|
||||
Anwendung: Neustart erforderlich. TURA_SPECIAL-Builds verwenden eine separate Slot-Zuweisung.</value></data>
|
||||
<data name="GenesisTooltip_iperlCheckErrorsToStopTextBox" xml:space="preserve"><value>IperlCheckErrorsToStop
|
||||
|
||||
Zweck: Anzahl fehlgeschlagener Zähler, ab der das Prüfergebnis angezeigt wird.
|
||||
Verwendung: GenesisCommunicationSeq: Aktivität iPERL_check bei Builds mit IPERL. Der Benutzer kann fortfahren oder abbrechen; kein allgemeiner Grenzwert für Slot-Fehler.
|
||||
Wertebereich: Ganzzahl: 1–40. Schwelle: Fehleranzahl >= Einstellung.
|
||||
Standardwert: 10
|
||||
Anwendung: Gilt bei der nächsten entsprechenden Prüfung, nicht bei normalen Slot-Aktivitäten.</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>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@@ -2503,4 +2503,52 @@
|
||||
<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>
|
||||
<data name="GenesisTooltip_nameTextBox" xml:space="preserve"><value>Name
|
||||
|
||||
Purpose: Identifies the component referenced by procedure tests.
|
||||
Used in: Component lookup and procedure Method selection.
|
||||
Allowed values: Use a unique component name.
|
||||
Default: SmartCommunicationGenesis.
|
||||
Application: Restart required after renaming; review procedure references.</value></data>
|
||||
<data name="GenesisTooltip_commTimeoutTextBox" xml:space="preserve"><value>CommTimeout
|
||||
|
||||
Purpose: Cancellation deadline for each Genesis GCI request.
|
||||
Used in: GciBridge requests made by GenesisCommunicationForm (connect, login, registers and related slot operations).
|
||||
Allowed values: Integer: 500–5000 ms.
|
||||
Default: 1800 ms.
|
||||
Application: Read at the start of each meter activity. Each request/attempt gets its own deadline; a multi-step test can take longer. Cancellation is cooperative: the underlying driver must honor the token. Legacy callers outside this flow retain their original timeouts.</value></data>
|
||||
<data name="GenesisTooltip_maxCommRetriesTextBox" xml:space="preserve"><value>MaxCommRetries
|
||||
|
||||
Purpose: Stored communication retry setting.
|
||||
Used in: Not consumed by the current Genesis slot/GCI workflow; does not cause failed slot operations to retry.
|
||||
Allowed values: Integer: 1–10.
|
||||
Default: 4.
|
||||
Application: Stored for compatibility; no slot/GCI retry effect.</value></data>
|
||||
<data name="GenesisTooltip_delayBetweenRetriesTextBox" xml:space="preserve"><value>DelayBetweenRetries
|
||||
|
||||
Purpose: Stored delay between communication retries.
|
||||
Used in: Not consumed by the current Genesis slot/GCI workflow; does not control the form closing delay.
|
||||
Allowed values: Integer: 0–5000 ms.
|
||||
Default: 0 ms.
|
||||
Application: Stored for compatibility; no slot/GCI delay effect.</value></data>
|
||||
<data name="GenesisTooltip_nrThreadsTextBox" xml:space="preserve"><value>NrThreads
|
||||
|
||||
Purpose: Number of worker threads for processing configured Genesis heads.
|
||||
Used in: GenesisCommunicationForm: worker creation and slot distribution. Does not change the number of configured meters.
|
||||
Allowed values: Integer: 1–10.
|
||||
Default: 10.
|
||||
Application: Restart required. TURA_SPECIAL builds use a separate slot scheduling branch.</value></data>
|
||||
<data name="GenesisTooltip_iperlCheckErrorsToStopTextBox" xml:space="preserve"><value>IperlCheckErrorsToStop
|
||||
|
||||
Purpose: Number of failed meters that triggers a check-result prompt.
|
||||
Used in: GenesisCommunicationSeq: iPERL_check activity, when compiled with IPERL. User may Continue or Abort. Not a general slot-error stop limit.
|
||||
Allowed values: Integer: 1–40.
|
||||
Default: 10. Threshold uses failed count >= setting.
|
||||
Application: Applied to the next applicable check; no effect on ordinary slot activities.</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>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@@ -268,4 +268,52 @@
|
||||
<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>
|
||||
<data name="GenesisTooltip_nameTextBox" xml:space="preserve"><value>Name
|
||||
|
||||
Účel: Identifikuje komponent, na ktorý odkazujú testy procedúry.
|
||||
Použitie: Vyhľadanie komponentu a výber Method v procedúre.
|
||||
Rozsah hodnôt: Jedinečný názov komponentu.
|
||||
Prednastavená hodnota: SmartCommunicationGenesis
|
||||
Uplatnenie: Po premenovaní je potrebný reštart; skontrolujte odkazy v procedúrach.</value></data>
|
||||
<data name="GenesisTooltip_commTimeoutTextBox" xml:space="preserve"><value>CommTimeout
|
||||
|
||||
Účel: Časový limit pre požiadavku na zrušenie jednotlivého Genesis GCI volania.
|
||||
Použitie: Volania GciBridge z GenesisCommunicationForm (pripojenie, login, registre a súvisiace slot operácie).
|
||||
Rozsah hodnôt: Celé číslo: 500–5000 ms.
|
||||
Prednastavená hodnota: 1800 ms.
|
||||
Uplatnenie: Načíta sa na začiatku aktivity vodomera. Každé volanie/pokus má vlastný limit; viac krokov môže trvať dlhšie. Zrušenie musí podporovať spodná vrstva ovládača. Ostatní používatelia bridge zachovajú pôvodné limity.</value></data>
|
||||
<data name="GenesisTooltip_maxCommRetriesTextBox" xml:space="preserve"><value>MaxCommRetries
|
||||
|
||||
Účel: Uložené nastavenie opakovania komunikácie.
|
||||
Použitie: Aktuálny tok Genesis slot/GCI ho nečíta; nespúšťa opakovanie neúspešnej operácie.
|
||||
Rozsah hodnôt: Celé číslo: 1–10.
|
||||
Prednastavená hodnota: 4
|
||||
Uplatnenie: Zachované pre kompatibilitu; bez vplyvu na opakovanie slot/GCI.</value></data>
|
||||
<data name="GenesisTooltip_delayBetweenRetriesTextBox" xml:space="preserve"><value>DelayBetweenRetries
|
||||
|
||||
Účel: Uložená pauza medzi opakovaniami komunikácie.
|
||||
Použitie: Aktuálny tok Genesis slot/GCI ju nečíta; neriadi oneskorenie zatvorenia formulára.
|
||||
Rozsah hodnôt: Celé číslo: 0–5000 ms.
|
||||
Prednastavená hodnota: 0 ms
|
||||
Uplatnenie: Zachované pre kompatibilitu; bez vplyvu na pauzy slot/GCI.</value></data>
|
||||
<data name="GenesisTooltip_nrThreadsTextBox" xml:space="preserve"><value>NrThreads
|
||||
|
||||
Účel: Počet pracovných vlákien pre nakonfigurované hlavy Genesis.
|
||||
Použitie: GenesisCommunicationForm: vytvorenie vlákien a rozdelenie slotov. Nemení počet vodomerov.
|
||||
Rozsah hodnôt: Celé číslo: 1–10.
|
||||
Prednastavená hodnota: 10
|
||||
Uplatnenie: Vyžaduje reštart. Zostavenia TURA_SPECIAL používajú samostatné prideľovanie slotov.</value></data>
|
||||
<data name="GenesisTooltip_iperlCheckErrorsToStopTextBox" xml:space="preserve"><value>IperlCheckErrorsToStop
|
||||
|
||||
Účel: Počet neúspešných vodomerov, pri ktorom sa zobrazí výsledok kontroly.
|
||||
Použitie: GenesisCommunicationSeq: aktivita iPERL_check pri zostavení s IPERL. Používateľ môže pokračovať alebo prerušiť; nejde o všeobecný limit chýb slotov.
|
||||
Rozsah hodnôt: Celé číslo: 1–40. Prah: počet neúspešných >= nastavenie.
|
||||
Prednastavená hodnota: 10
|
||||
Uplatnenie: Použije sa pri nasledujúcej príslušnej kontrole; nie pri bežných slot aktivitách.</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>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using CordonelPreadjustmentUi;
|
||||
using CordonelPreadjustmentUi;
|
||||
using CordonelPreadjustmentUi.Processes.Itinerary;
|
||||
using GenesisCordonelInterface.API;
|
||||
using GenesisCordonelInterface.Core;
|
||||
@@ -37,6 +37,41 @@ using UdsWriterType = TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writer;
|
||||
|
||||
namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
{
|
||||
/// <summary>Per-async-flow Genesis request timeout; absent for legacy callers.</summary>
|
||||
public static class GenesisRequestTimeout
|
||||
{
|
||||
private static readonly AsyncLocal<int?> current = new AsyncLocal<int?>();
|
||||
private static readonly ILog timeoutLog = LogManager.GetLogger(typeof(GenesisRequestTimeout));
|
||||
|
||||
public static IDisposable Begin(int milliseconds)
|
||||
{
|
||||
if (milliseconds < 500 || milliseconds > 5000) throw new ArgumentOutOfRangeException(nameof(milliseconds));
|
||||
var previous = current.Value;
|
||||
current.Value = milliseconds;
|
||||
return new Scope(previous);
|
||||
}
|
||||
|
||||
public static CancellationTokenSource CreateDeadline(CancellationToken callerToken, string operation)
|
||||
{
|
||||
var milliseconds = current.Value;
|
||||
if (!milliseconds.HasValue) return null;
|
||||
var source = CancellationTokenSource.CreateLinkedTokenSource(callerToken);
|
||||
timeoutLog.DebugFormat("Genesis CommTimeout applied: Operation={0}, TimeoutMs={1}", operation, milliseconds.Value);
|
||||
source.Token.Register(() => timeoutLog.WarnFormat("Genesis request cancellation requested: Operation={0}, TimeoutMs={1}, CallerCancelled={2}", operation, milliseconds.Value, callerToken.IsCancellationRequested));
|
||||
source.CancelAfter(milliseconds.Value);
|
||||
return source;
|
||||
}
|
||||
|
||||
private sealed class Scope : IDisposable
|
||||
{
|
||||
private readonly int? previous;
|
||||
private bool disposed;
|
||||
public Scope(int? previous) { this.previous = previous; }
|
||||
public void Dispose() { if (!disposed) { current.Value = previous; disposed = true; } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// TBF bridge component for integration with the sibling GCI project.
|
||||
/// The component can be linked to UniDataStorage reader and writer components.
|
||||
@@ -288,6 +323,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
GciPublicModels.GciInitSlotRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "InitSlotAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
EnsureExternalInterface();
|
||||
|
||||
if (request == null)
|
||||
@@ -369,6 +406,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
GciPublicModels.GciInitSlotRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "UpdateSlotAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
EnsureExternalInterface();
|
||||
|
||||
if (request == null)
|
||||
@@ -447,6 +486,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
int slotId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "GetSlotAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
EnsureExternalInterface();
|
||||
|
||||
if (slotId <= 0)
|
||||
@@ -523,6 +564,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
int slot,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "CleanSlotAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
EnsureExternalInterface();
|
||||
|
||||
if (slot <= 0)
|
||||
@@ -599,6 +642,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
public async Task<GciPublicModels.GciCleanAllSlotsResult> CleanAllSlotsAsync(
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "CleanAllSlotsAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
EnsureExternalInterface();
|
||||
|
||||
GciPublicModels.GciCleanAllSlotsResult result =
|
||||
@@ -671,6 +716,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
int slotId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "GetPcbIdAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
EnsureExternalInterface();
|
||||
|
||||
if (slotId <= 0)
|
||||
@@ -788,6 +835,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
int slot,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "ConnectAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
EnsureExternalInterface();
|
||||
|
||||
if (slot <= 0)
|
||||
@@ -820,6 +869,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
int slotId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "LoginAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
EnsureExternalInterface();
|
||||
|
||||
if (slotId <= 0)
|
||||
@@ -891,6 +942,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
int slotId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "DisconnectAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
EnsureExternalInterface();
|
||||
|
||||
if (slotId <= 0)
|
||||
@@ -970,6 +1023,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
string password,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "SetPasswordAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
EnsureExternalInterface();
|
||||
|
||||
if (slotId <= 0)
|
||||
@@ -1046,6 +1101,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
string registerName,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "ReadRegisterAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
EnsureExternalInterface();
|
||||
|
||||
if (slotId <= 0)
|
||||
@@ -1136,6 +1193,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
bool refreshSystemState = false,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "WriteRegisterAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
EnsureExternalInterface();
|
||||
|
||||
if (slotId <= 0)
|
||||
@@ -1239,6 +1298,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
string pcbId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "GetPasswordAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
EnsureReader();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(pcbId))
|
||||
@@ -1317,6 +1378,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
int meterSize,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "GetPreAdjustmentCalibrationParamsAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
EnsureReader();
|
||||
|
||||
if (meterSize < 0)
|
||||
@@ -1504,6 +1567,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
/// <returns>Complete workflow result including all retry statistics and operation results.</returns>
|
||||
public async Task<GciFullLoginResult> ConnectFullPassLoginWithRetryAsync(int slotId, CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "ConnectFullPassLoginWithRetryAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
EnsureExternalInterface();
|
||||
EnsureReader();
|
||||
|
||||
@@ -1605,6 +1670,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
IEnumerable<GciPublicModels.MeterBatchDebugStatus> selectedSlots,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "PreAdjustment_DetectAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
const string operation = nameof(PreAdjustment_DetectAsync);
|
||||
|
||||
try
|
||||
@@ -1678,6 +1745,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
int slot,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "PreAdjustment_PreparationAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
const string operation = nameof(PreAdjustment_PreparationAsync);
|
||||
|
||||
try
|
||||
@@ -1758,6 +1827,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
int slot,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "PreAdjustment_AmplitudeTestAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
const string operation = nameof(PreAdjustment_AmplitudeTestAsync);
|
||||
|
||||
try
|
||||
@@ -1838,6 +1909,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
int slot,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "PreAdjustment_TemperatureCalibrationAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
const string operation = nameof(PreAdjustment_TemperatureCalibrationAsync);
|
||||
|
||||
try
|
||||
@@ -1945,6 +2018,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
int slot,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "PreAdjustment_OffsetTestAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
const string operation = nameof(PreAdjustment_OffsetTestAsync);
|
||||
|
||||
try
|
||||
@@ -2025,6 +2100,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
|
||||
int slot,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "PreAdjustment_CompletionAsync");
|
||||
token = deadline == null ? token : deadline.Token;
|
||||
const string operation = nameof(PreAdjustment_CompletionAsync);
|
||||
|
||||
try
|
||||
|
||||
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,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);
|
||||
@@ -3937,6 +3931,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
|
||||
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 +3949,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 +3960,48 @@ 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()
|
||||
{
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CalculateQ3Calibration(double refVolume, double refTime)
|
||||
{
|
||||
GetQ3Calibration(refVolume, refTime, q3CalibInitial, ref isChQ3CalibValid, ref q3CalibCh);
|
||||
GetQ3Calibration(refVolume, refTime, q3CalibInitial, 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)
|
||||
{
|
||||
log.Debug("=== Q3 CALIBRATION START ===");
|
||||
|
||||
@@ -3991,7 +4021,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)}");
|
||||
|
||||
if (_rawStartEndByChannel == null)
|
||||
{
|
||||
@@ -4081,25 +4111,13 @@ 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;
|
||||
isChQ3CalibValid[iChannel] = diffPercent <= 5.0 && !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]} 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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -198,7 +198,7 @@ namespace TBF.Rig
|
||||
new TestMethods.FlyingStartFirstRepetWithMassColl.HeatMeters.Factory(),
|
||||
new TestMethods.FlyingStartTankCollection.Single.Factory(),
|
||||
new TestMethods.FlyingStartTankCollection.Compound.Factory(),
|
||||
//new TestMethods.GenesisCommunication.GenesisHead.Factory(),
|
||||
new TestMethods.GenesisCommunication.Factory(),
|
||||
new TestMethods.GrabImage.Factory(),
|
||||
new TestMethods.iPerlCommunication.TestMethodFactory(), /// iPerlCommunication
|
||||
new TestMethods.LeakTest.Factory(),
|
||||
|
||||
@@ -12,6 +12,8 @@ using TBF.Rig;
|
||||
using TBF.Rig.GenericDevices;
|
||||
using TBF.Boxes;
|
||||
using TBF.Resources;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
|
||||
using TBF.Rig.Sequences;
|
||||
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||
using TBF.UiBridge;
|
||||
|
||||
@@ -464,6 +466,11 @@ namespace TBF.Rig.TestMethods.FlyingStart
|
||||
}
|
||||
while (!e.Contains(Event.TestCompleted) && !e.Contains(Event.Next)); /// 'Next' button is enabled in Debug version only
|
||||
|
||||
|
||||
//------------------------------------------------
|
||||
Bridge.OnActivity(this, Strings.Test_in_calculation);
|
||||
//------------------------------------------------
|
||||
|
||||
/// Measurement loop end
|
||||
StopRecordingStatistics();
|
||||
|
||||
@@ -532,7 +539,7 @@ namespace TBF.Rig.TestMethods.FlyingStart
|
||||
tstRslt.TimeBtwnMassMsrmnts = 0;
|
||||
tstRslt.ConstMasterRaw = outPath.FlowMeter.LtrPerPulse; /// Uncorrected master flowmeter coefficient
|
||||
tstRslt.VolumeMaster = tstRslt.ConstMasterRaw * tstRslt.PulsesMaster; /// [l] volume from the master flow meter
|
||||
double flowMID = 3.6 * tstRslt.VolumeMaster / tstRslt.TestTime; /// [m3/h]
|
||||
double flowMID = tstRslt.TestTime == 0 ? 0 : 3.6 * tstRslt.VolumeMaster / tstRslt.TestTime; /// [m3/h]
|
||||
|
||||
/// Corrected data
|
||||
tstRslt.MassStart = 0;
|
||||
@@ -542,7 +549,7 @@ namespace TBF.Rig.TestMethods.FlyingStart
|
||||
|
||||
/// Main result calculation
|
||||
tstRslt.VolumeCTV = tstRslt.ConstMasterCorr * tstRslt.PulsesMaster; /// [l] 1000.0f is because density is in [kg/m3]
|
||||
tstRslt.Flow = 3.6 * tstRslt.VolumeCTV / tstRslt.TestTime;
|
||||
tstRslt.Flow = tstRslt.TestTime==0 ? 0 : 3.6 * tstRslt.VolumeCTV / tstRslt.TestTime;
|
||||
tstRslt.ErrorMaster = 0.0; /// Not available without a mass measurement
|
||||
tstRslt.ConstMaster = tstRslt.ConstMasterCorr;
|
||||
|
||||
@@ -660,6 +667,7 @@ namespace TBF.Rig.TestMethods.FlyingStart
|
||||
TestMethods.iPerlCommunication.iPerlHead.IperlHead iPerl = regReader as TestMethods.iPerlCommunication.iPerlHead.IperlHead;
|
||||
GenericDevices.IRegReaderLiveCamera cameraRoi = regReader as GenericDevices.IRegReaderLiveCamera;
|
||||
//GenesisHead Genesis = regReader as GenesisHead;
|
||||
TBF.Rig.RegisterReaders.GenesisRegReader.implementations.GenesisSmartReader GenesisSmart = regReader as TBF.Rig.RegisterReaders.GenesisRegReader.implementations.GenesisSmartReader;
|
||||
|
||||
if (meterRslt != null && regReader != null)
|
||||
{
|
||||
@@ -711,6 +719,24 @@ namespace TBF.Rig.TestMethods.FlyingStart
|
||||
Genesis.Log(" MeterError = " + calError.ToString() + " %");
|
||||
}
|
||||
else*/
|
||||
if (GenesisSmart != null)
|
||||
{
|
||||
log.Debug("GenesisSmart - store data on end!");
|
||||
int CH1=0, CH2=1, CH3=2;
|
||||
|
||||
CalculateMeterResults(meterRslt, GenesisSmart, tstRslt,GenesisSmart.TimestampSecStart, GenesisSmart.TimestampSecEnd, GenesisSmart.VolumeLtrStart, GenesisSmart.VolumeLtrEnd);
|
||||
|
||||
//Init Calculate Calibration
|
||||
GenesisSmart.RefVolume = meterRslt.VolumeRef;
|
||||
GenesisSmart.RefTime = meterRslt.TestTime;
|
||||
|
||||
//Store Raw Calibration Data to database
|
||||
WaterMeterParentCopy(i, CH1,testName,meterRslt,GenesisSmart,tstRslt);
|
||||
WaterMeterParentCopy(i, CH2,testName,meterRslt,GenesisSmart,tstRslt);
|
||||
WaterMeterParentCopy(i, CH3,testName,meterRslt,GenesisSmart,tstRslt);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dstrReader != null)
|
||||
{
|
||||
@@ -918,6 +944,217 @@ namespace TBF.Rig.TestMethods.FlyingStart
|
||||
// }
|
||||
// }
|
||||
|
||||
private const int CountCh = 3;
|
||||
private static void WaterMeterParentCopy(int i, int iCH, string testName, MeterTestRslt meterRslt,
|
||||
GenesisSmartReader genesisSmart,
|
||||
TestRslt tstRslt)
|
||||
{
|
||||
try
|
||||
{
|
||||
WaterMeter waterMeterParent = BatchRslts.Batch.WaterMeters[i];
|
||||
int wmNrChX = (i * CountCh) + BatchRslts.WMPositionsCount + iCH + 1;
|
||||
String sSerialNr =
|
||||
(string.IsNullOrEmpty(waterMeterParent.SerialNr) ? (i + 1).ToString() : waterMeterParent.SerialNr) +
|
||||
"_CH" + (iCH + 1);
|
||||
WaterMeter chXWaterMeter = null;
|
||||
//------ add new water meter to batch ------
|
||||
if (BatchRslts.Batch.WaterMeters.Count < wmNrChX || BatchRslts.Batch.WaterMeters[wmNrChX - 1] == null)
|
||||
{
|
||||
log.Debug("add new water meter to batch, CH = " + (iCH + 1) + " CH = " + wmNrChX);
|
||||
chXWaterMeter = new WaterMeter()
|
||||
{
|
||||
MeterTestRslts = new List<MeterTestRslt>(),
|
||||
};
|
||||
//This will delete each setting before
|
||||
chXWaterMeter.CopyContentFrom(waterMeterParent);
|
||||
|
||||
chXWaterMeter.Batch = BatchRslts.Batch;
|
||||
chXWaterMeter.WaterMeterData = waterMeterParent.WaterMeterData;
|
||||
|
||||
chXWaterMeter.SerialNr = sSerialNr;
|
||||
chXWaterMeter.WMPosition = wmNrChX;
|
||||
chXWaterMeter.Q3Channel = iCH + 1;
|
||||
chXWaterMeter.YearOfProduction = waterMeterParent.YearOfProduction;
|
||||
chXWaterMeter.Disabled = false;
|
||||
|
||||
BatchRslts.Batch.WaterMeters.Add(chXWaterMeter);
|
||||
}
|
||||
else
|
||||
{
|
||||
chXWaterMeter = BatchRslts.Batch.WaterMeters[wmNrChX - 1];
|
||||
|
||||
chXWaterMeter.Batch = BatchRslts.Batch;
|
||||
chXWaterMeter.WaterMeterData = waterMeterParent.WaterMeterData;
|
||||
chXWaterMeter.SerialNr = sSerialNr;
|
||||
chXWaterMeter.WMPosition = wmNrChX;
|
||||
chXWaterMeter.Q3Channel = iCH + 1;
|
||||
chXWaterMeter.YearOfProduction = waterMeterParent.YearOfProduction;
|
||||
chXWaterMeter.Disabled = false;
|
||||
}
|
||||
//~------ add new water meter to batch ------~
|
||||
|
||||
//create copy of meterRslt and add to additionalResultsByChannel
|
||||
MeterTestRslt meterTestRsltChX = new MeterTestRslt(chXWaterMeter, meterRslt.TestRslt,
|
||||
(CompoundMeterId)meterRslt.CompoundMeterId);
|
||||
meterTestRsltChX.Q3Channel = iCH + 1;
|
||||
chXWaterMeter.MeterTestRslts.Add(meterTestRsltChX);
|
||||
MeterTestRslt chanelXMeterRslt =
|
||||
BatchRslts.GetMeterTestRslt(testName, wmNrChX - 1, Common.CompoundMeterId.Single);
|
||||
|
||||
if (chanelXMeterRslt != null)
|
||||
{
|
||||
chanelXMeterRslt?.CopyContentFrom(meterRslt);
|
||||
if (iCH == 0)
|
||||
{
|
||||
chanelXMeterRslt.Q3Channel = 1;
|
||||
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt,
|
||||
genesisSmart.TimestampSecStartRawCh1,
|
||||
genesisSmart.TimestampSecEndRawCh1, genesisSmart.VolumeLtrStartRawCh1,
|
||||
genesisSmart.VolumeLtrEndRawCh1);
|
||||
|
||||
try
|
||||
{
|
||||
TestRsltCalibFactor testRsltCalibFactor = null;
|
||||
if (tstRslt.GetCalibrationFactors(waterMeterParent).Count > 0)
|
||||
{
|
||||
testRsltCalibFactor = tstRslt.GetCalibrationFactors(waterMeterParent)[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
//store in table
|
||||
testRsltCalibFactor = new TestRsltCalibFactor();
|
||||
tstRslt.GetCalibrationFactors(waterMeterParent).Add(testRsltCalibFactor);
|
||||
testRsltCalibFactor.CalibFactorIndex = 1;
|
||||
}
|
||||
|
||||
testRsltCalibFactor.TestRslt = chanelXMeterRslt.TestRslt;
|
||||
testRsltCalibFactor.TimeStart = genesisSmart.TimestampSecStartRawCh1;
|
||||
testRsltCalibFactor.TimeEnd = genesisSmart.TimestampSecEndRawCh1;
|
||||
testRsltCalibFactor.VolumeStart = genesisSmart.VolumeLtrStartRawCh1;
|
||||
testRsltCalibFactor.VolumeEnd = genesisSmart.VolumeLtrEndRawCh1;
|
||||
testRsltCalibFactor.Error = chanelXMeterRslt.Error;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Error in store to DB result set CH1", ex);
|
||||
}
|
||||
}
|
||||
else if (iCH == 1)
|
||||
{
|
||||
chanelXMeterRslt.Q3Channel = 2;
|
||||
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt,
|
||||
genesisSmart.TimestampSecStartRawCh2,
|
||||
genesisSmart.TimestampSecEndRawCh2, genesisSmart.VolumeLtrStartRawCh2,
|
||||
genesisSmart.VolumeLtrEndRawCh2);
|
||||
|
||||
try
|
||||
{
|
||||
TestRsltCalibFactor testRsltCalibFactor = null;
|
||||
if (tstRslt.GetCalibrationFactors(waterMeterParent).Count > 1)
|
||||
{
|
||||
testRsltCalibFactor = tstRslt.GetCalibrationFactors(waterMeterParent)[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
//store in table
|
||||
testRsltCalibFactor = new TestRsltCalibFactor();
|
||||
tstRslt.GetCalibrationFactors(waterMeterParent).Add(testRsltCalibFactor);
|
||||
testRsltCalibFactor.CalibFactorIndex = 2;
|
||||
}
|
||||
|
||||
testRsltCalibFactor.TestRslt = chanelXMeterRslt.TestRslt;
|
||||
testRsltCalibFactor.TimeStart = genesisSmart.TimestampSecStartRawCh2;
|
||||
testRsltCalibFactor.TimeEnd = genesisSmart.TimestampSecEndRawCh2;
|
||||
testRsltCalibFactor.VolumeStart = genesisSmart.VolumeLtrStartRawCh2;
|
||||
testRsltCalibFactor.VolumeEnd = genesisSmart.VolumeLtrEndRawCh2;
|
||||
testRsltCalibFactor.Error = chanelXMeterRslt.Error;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Error in store to DB result set CH2", ex);
|
||||
}
|
||||
}
|
||||
else if (iCH == 2)
|
||||
{
|
||||
chanelXMeterRslt.Q3Channel = 3;
|
||||
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt,
|
||||
genesisSmart.TimestampSecStartRawCh3,
|
||||
genesisSmart.TimestampSecEndRawCh3, genesisSmart.VolumeLtrStartRawCh3,
|
||||
genesisSmart.VolumeLtrEndRawCh3);
|
||||
try
|
||||
{
|
||||
TestRsltCalibFactor testRsltCalibFactor = null;
|
||||
if (tstRslt.GetCalibrationFactors(waterMeterParent).Count > 2)
|
||||
{
|
||||
testRsltCalibFactor = tstRslt.GetCalibrationFactors(waterMeterParent)[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
//store in table
|
||||
testRsltCalibFactor = new TestRsltCalibFactor();
|
||||
tstRslt.GetCalibrationFactors(waterMeterParent).Add(testRsltCalibFactor);
|
||||
testRsltCalibFactor.CalibFactorIndex = 3;
|
||||
}
|
||||
|
||||
testRsltCalibFactor.TestRslt = chanelXMeterRslt.TestRslt;
|
||||
testRsltCalibFactor.TimeStart = genesisSmart.TimestampSecStartRawCh3;
|
||||
testRsltCalibFactor.TimeEnd = genesisSmart.TimestampSecEndRawCh3;
|
||||
testRsltCalibFactor.VolumeStart = genesisSmart.VolumeLtrStartRawCh3;
|
||||
testRsltCalibFactor.VolumeEnd = genesisSmart.VolumeLtrEndRawCh3;
|
||||
testRsltCalibFactor.Error = chanelXMeterRslt.Error;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Error in store to DB result set CH3", ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (!genesisSmart.EnableShowChanels)
|
||||
{
|
||||
chXWaterMeter.Disabled = !genesisSmart.EnableShowChanels;
|
||||
//meterTestRsltChX.TestDone = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
chXWaterMeter.Disabled = false;
|
||||
meterTestRsltChX.TestDone = true;
|
||||
}
|
||||
|
||||
meterTestRsltChX.Passed = meterRslt.Passed;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Error in WaterMeterParentCopy", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CalculateMeterResults(MeterTestRslt meterRslt, IRegReaderDatastream dstrReader, TestRslt tstRslt, double dstrReaderTimestampSecStart, double dstrReaderTimestampSecEnd, double dstrReaderVolumeLtrStart, double dstrReaderVolumeLtrEnd)
|
||||
{
|
||||
try
|
||||
{
|
||||
meterRslt.TimestampStart = dstrReaderTimestampSecStart;
|
||||
meterRslt.TimestampEnd = !dstrReader.NoSamples
|
||||
? dstrReaderTimestampSecEnd
|
||||
: (dstrReaderTimestampSecStart + tstRslt.TestTime);
|
||||
meterRslt.TestTime = meterRslt.TimestampEnd - meterRslt.TimestampStart;
|
||||
meterRslt.VolumeStart = dstrReaderVolumeLtrStart; /// liter
|
||||
meterRslt.VolumeEnd = dstrReaderVolumeLtrEnd; /// liter
|
||||
meterRslt.VolumeMeter =
|
||||
Math.Abs(dstrReaderVolumeLtrEnd - dstrReaderVolumeLtrStart);
|
||||
meterRslt.VolumeRef = tstRslt.TestTime == 0
|
||||
? tstRslt.VolumeCTV
|
||||
: tstRslt.VolumeCTV * meterRslt.TestTime / tstRslt.TestTime;
|
||||
meterRslt.PulsesMaster = tstRslt.TestTime == 0
|
||||
? tstRslt.PulsesMaster
|
||||
: tstRslt.PulsesMaster * meterRslt.TestTime / tstRslt.TestTime;
|
||||
meterRslt.Error = Formulas.ErrorFromVolumes(meterRslt.VolumeMeter,
|
||||
meterRslt.VolumeRef); // Error based on volume difference
|
||||
}catch(Exception ex)
|
||||
{
|
||||
log.Error($"Error in calculate meter results, meterRslt.Name:{meterRslt.Name()} Calculation Bug Detail:", ex);
|
||||
}
|
||||
}
|
||||
|
||||
IList<Event> Simulate(Config.Entities.Test test, int repetitionNr, bool isLastRepetition,
|
||||
Compound.TestParams compoundTestParams,
|
||||
@@ -943,6 +1180,14 @@ namespace TBF.Rig.TestMethods.FlyingStart
|
||||
else if (test.Name.ToLower().Contains("q1")) MakeSimulated(test, 1, 0, -5.1f);
|
||||
else MakeSimulated(test, 1, 0, 0.9f);
|
||||
|
||||
//TODO bumi do simulate foe Q3 Calibration
|
||||
///
|
||||
/// Single meters
|
||||
///
|
||||
SimulateQ3CalibrationData(test,1, 0, -5.1f);
|
||||
|
||||
|
||||
|
||||
Bridge.OnTestProgress(this, new TestProgressEventArgs(test, repetitionNr, Progress.Completed));
|
||||
Bridge.OnTestCompleted(this, new TestCompletedEventArgs(test.Name, BatchRslts.GetTestRslt(Results.Utils.GetTestName(test.Name, 1, 1), 0)));
|
||||
allResults.Info(TestResult2CsvLine(test.Name, 0)); /// Append the results to the CSV-file
|
||||
@@ -958,5 +1203,46 @@ namespace TBF.Rig.TestMethods.FlyingStart
|
||||
|
||||
return new List<Event> { TestAndLogUiCmdStop(test, e) ? Event.UiCmdStop : Event.Done };
|
||||
}
|
||||
|
||||
private void SimulateQ3CalibrationData(Test test,int repetitionNr, int part, float errorPctBase)
|
||||
{
|
||||
string testName = test.Name;
|
||||
string fullTestName = Results.Utils.GetTestName(test.Name, test.Repeats, repetitionNr);
|
||||
|
||||
Results.Entities.TestRslt tstRslt = ProcessData.BatchRslts.GetTestRslt(fullTestName, part);
|
||||
Results.Utils.GetCounterStates(tstRslt, Program.LocalSettings.Counters);
|
||||
|
||||
|
||||
for (int i = 0; i < BatchRslts.WMPositionsCount; i++)
|
||||
{
|
||||
if (!test.IsPartCompatible(Utils.PartNr(i + 1, BatchRslts.Batch.Compound))) continue;
|
||||
|
||||
Results.Entities.MeterTestRslt meterRslt =
|
||||
BatchRslts.GetMeterTestRslt(testName, i, Common.CompoundMeterId.Single);
|
||||
GenericDevices.IRegReader regReader = sensPath.RegisterReaders[i];
|
||||
TBF.Rig.RegisterReaders.GenesisRegReader.implementations.GenesisSmartReader GenesisSmart =
|
||||
regReader as TBF.Rig.RegisterReaders.GenesisRegReader.implementations.GenesisSmartReader;
|
||||
|
||||
|
||||
if (GenesisSmart != null)
|
||||
{
|
||||
log.Debug("GenesisSmart - store data on end!");
|
||||
int CH1 = 0, CH2 = 1, CH3 = 2;
|
||||
|
||||
CalculateMeterResults(meterRslt, GenesisSmart, tstRslt, GenesisSmart.TimestampSecStart,
|
||||
GenesisSmart.TimestampSecEnd, GenesisSmart.VolumeLtrStart, GenesisSmart.VolumeLtrEnd);
|
||||
|
||||
//Init Calculate Calibration
|
||||
GenesisSmart.RefVolume = meterRslt.VolumeRef;
|
||||
GenesisSmart.RefTime = meterRslt.TestTime;
|
||||
|
||||
//Store Raw Calibration Data to database
|
||||
WaterMeterParentCopy(i, CH1, testName, meterRslt, GenesisSmart, tstRslt);
|
||||
WaterMeterParentCopy(i, CH2, testName, meterRslt, GenesisSmart, tstRslt);
|
||||
WaterMeterParentCopy(i, CH3, testName, meterRslt, GenesisSmart, tstRslt);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ using TBF.Rig.GenericDevices;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
|
||||
using TBF.Rig.RegisterReaders.PoseidonReader;
|
||||
using TBF.Rig.RegisterReaders.PoseidonReader.implementations;
|
||||
using TBF.Rig.Sequences;
|
||||
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
|
||||
using TBF.UiBridge;
|
||||
|
||||
@@ -1106,12 +1107,13 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
|
||||
bUpgradeCountOfMeters = true;
|
||||
int CH1=0, CH2=1, CH3=2;
|
||||
|
||||
//BatchRslts.Batch.WaterMeters.Add(ch1WaterMeter); //[i + BatchRslts.WMPositionsCount + CH1 + 1]
|
||||
//BatchRslts.Batch.WaterMeters.Add(ch1WaterMeter); //[i + BatchRslts.WMPositionsCount + CH2 + 1]
|
||||
//BatchRslts.Batch.WaterMeters.Add(ch1WaterMeter); //[i + BatchRslts.WMPositionsCount + CH3 + 1]
|
||||
|
||||
CalculateMeterResults(meterRslt, GenesisSmart, tstRslt,GenesisSmart.TimestampSecStart, GenesisSmart.TimestampSecEnd, GenesisSmart.VolumeLtrStart, GenesisSmart.VolumeLtrEnd);
|
||||
|
||||
//Init Calculate Calibration
|
||||
GenesisSmart.RefVolume = meterRslt.VolumeRef;
|
||||
GenesisSmart.RefTime = meterRslt.TestTime;
|
||||
|
||||
//Store Raw Calibration Data to database
|
||||
WaterMeterParentCopy(i, CH1,testName,meterRslt,GenesisSmart,tstRslt);
|
||||
WaterMeterParentCopy(i, CH2,testName,meterRslt,GenesisSmart,tstRslt);
|
||||
WaterMeterParentCopy(i, CH3,testName,meterRslt,GenesisSmart,tstRslt);
|
||||
@@ -1179,6 +1181,7 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
|
||||
}
|
||||
#if IPERL
|
||||
meterRslt.WaterMeter.CalibFactor = iPerl.CalibFactor;
|
||||
meterRslt.WaterMeter.CalibFactorNominal = iPerl.CalibFactorNominal;
|
||||
meterRslt.FlipMode = iPerl.ConfigStruct != null && iPerl.ConfigStruct.FlipMode.HasValue
|
||||
? (int?)iPerl.ConfigStruct.FlipMode.Value
|
||||
: null;
|
||||
@@ -1375,91 +1378,212 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
|
||||
GenesisSmartReader genesisSmart,
|
||||
TestRslt tstRslt)
|
||||
{
|
||||
WaterMeter waterMeterParent = BatchRslts.Batch.WaterMeters[i];
|
||||
int wmNrChX = (i*CountCh) + BatchRslts.WMPositionsCount + iCH + 1;
|
||||
String sSerialNr = waterMeterParent.SerialNr + "_CH" + (iCH + 1);
|
||||
WaterMeter chXWaterMeter = null;
|
||||
//------ add new water meter to batch ------
|
||||
if (BatchRslts.Batch.WaterMeters.Count < wmNrChX || BatchRslts.Batch.WaterMeters[wmNrChX-1] == null)
|
||||
try
|
||||
{
|
||||
log.Debug("add new water meter to batch, CH = " + (iCH + 1) + " CH = " + wmNrChX);
|
||||
chXWaterMeter = new WaterMeter()
|
||||
WaterMeter waterMeterParent = BatchRslts.Batch.WaterMeters[i];
|
||||
int wmNrChX = (i * CountCh) + BatchRslts.WMPositionsCount + iCH + 1;
|
||||
String sSerialNr =
|
||||
(string.IsNullOrEmpty(waterMeterParent.SerialNr) ? (i+1).ToString() : waterMeterParent.SerialNr) +
|
||||
"_CH" + (iCH + 1);
|
||||
WaterMeter chXWaterMeter = null;
|
||||
//------ add new water meter to batch ------
|
||||
if (BatchRslts.Batch.WaterMeters.Count < wmNrChX || BatchRslts.Batch.WaterMeters[wmNrChX - 1] == null)
|
||||
{
|
||||
Batch = BatchRslts.Batch,
|
||||
WaterMeterData = waterMeterParent.WaterMeterData,
|
||||
MeterTestRslts = new List<MeterTestRslt>(),
|
||||
SerialNr = sSerialNr,
|
||||
WMPosition = wmNrChX,
|
||||
YearOfProduction = 0,
|
||||
Disabled = false,
|
||||
};
|
||||
chXWaterMeter.CopyContentFrom(waterMeterParent);
|
||||
log.Debug("add new water meter to batch, CH = " + (iCH + 1) + " CH = " + wmNrChX);
|
||||
chXWaterMeter = new WaterMeter()
|
||||
{
|
||||
MeterTestRslts = new List<MeterTestRslt>(),
|
||||
};
|
||||
//This will delete each setting before
|
||||
chXWaterMeter.CopyContentFrom(waterMeterParent);
|
||||
|
||||
chXWaterMeter.SerialNr = sSerialNr;
|
||||
chXWaterMeter.WMPosition = wmNrChX;
|
||||
chXWaterMeter.YearOfProduction = waterMeterParent.YearOfProduction;
|
||||
chXWaterMeter.Disabled = false;
|
||||
chXWaterMeter.Batch = BatchRslts.Batch;
|
||||
chXWaterMeter.WaterMeterData = waterMeterParent.WaterMeterData;
|
||||
|
||||
BatchRslts.Batch.WaterMeters.Add(chXWaterMeter);
|
||||
}
|
||||
else
|
||||
{
|
||||
chXWaterMeter = BatchRslts.Batch.WaterMeters[wmNrChX-1];
|
||||
}
|
||||
//~------ add new water meter to batch ------~
|
||||
|
||||
//create copy of meterRslt and add to additionalResultsByChannel
|
||||
MeterTestRslt meterTestRsltChX = new MeterTestRslt(chXWaterMeter, meterRslt.TestRslt, (CompoundMeterId)meterRslt.CompoundMeterId);
|
||||
chXWaterMeter.MeterTestRslts.Add(meterTestRsltChX);
|
||||
MeterTestRslt chanelXMeterRslt = BatchRslts.GetMeterTestRslt(testName, wmNrChX-1, Common.CompoundMeterId.Single);
|
||||
|
||||
if (chanelXMeterRslt != null)
|
||||
{
|
||||
chanelXMeterRslt?.CopyContentFrom(meterRslt);
|
||||
if (iCH == 0)
|
||||
{
|
||||
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt, genesisSmart.TimestampSecStartRawCh1,
|
||||
genesisSmart.TimestampSecEndRawCh1, genesisSmart.VolumeLtrStartRawCh1, genesisSmart.VolumeLtrEndRawCh1);
|
||||
}
|
||||
else if (iCH == 1)
|
||||
{
|
||||
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt, genesisSmart.TimestampSecStartRawCh2,
|
||||
genesisSmart.TimestampSecEndRawCh2, genesisSmart.VolumeLtrStartRawCh2, genesisSmart.VolumeLtrEndRawCh2);
|
||||
}
|
||||
else if (iCH == 2)
|
||||
{
|
||||
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt, genesisSmart.TimestampSecStartRawCh3,
|
||||
genesisSmart.TimestampSecEndRawCh3, genesisSmart.VolumeLtrStartRawCh3, genesisSmart.VolumeLtrEndRawCh3);
|
||||
}
|
||||
chXWaterMeter.SerialNr = sSerialNr;
|
||||
chXWaterMeter.WMPosition = wmNrChX;
|
||||
chXWaterMeter.Q3Channel = iCH+1;
|
||||
chXWaterMeter.YearOfProduction = waterMeterParent.YearOfProduction;
|
||||
chXWaterMeter.Disabled = false;
|
||||
|
||||
if (!genesisSmart.EnableShowChanels)
|
||||
{
|
||||
chXWaterMeter.Disabled = !genesisSmart.EnableShowChanels;
|
||||
meterTestRsltChX.TestDone = false;
|
||||
|
||||
BatchRslts.Batch.WaterMeters.Add(chXWaterMeter);
|
||||
}
|
||||
else
|
||||
{
|
||||
meterTestRsltChX.TestDone = true;
|
||||
}
|
||||
chXWaterMeter = BatchRslts.Batch.WaterMeters[wmNrChX - 1];
|
||||
|
||||
meterTestRsltChX.Passed = meterRslt.Passed;
|
||||
|
||||
chXWaterMeter.Batch = BatchRslts.Batch;
|
||||
chXWaterMeter.WaterMeterData = waterMeterParent.WaterMeterData;
|
||||
chXWaterMeter.SerialNr = sSerialNr;
|
||||
chXWaterMeter.WMPosition = wmNrChX;
|
||||
chXWaterMeter.Q3Channel = iCH+1;
|
||||
chXWaterMeter.YearOfProduction = waterMeterParent.YearOfProduction;
|
||||
chXWaterMeter.Disabled = false;
|
||||
}
|
||||
//~------ add new water meter to batch ------~
|
||||
|
||||
//create copy of meterRslt and add to additionalResultsByChannel
|
||||
MeterTestRslt meterTestRsltChX = new MeterTestRslt(chXWaterMeter, meterRslt.TestRslt,
|
||||
(CompoundMeterId)meterRslt.CompoundMeterId);
|
||||
meterTestRsltChX.Q3Channel = iCH + 1;
|
||||
chXWaterMeter.MeterTestRslts.Add(meterTestRsltChX);
|
||||
MeterTestRslt chanelXMeterRslt =
|
||||
BatchRslts.GetMeterTestRslt(testName, wmNrChX - 1, Common.CompoundMeterId.Single);
|
||||
|
||||
if (chanelXMeterRslt != null)
|
||||
{
|
||||
chanelXMeterRslt?.CopyContentFrom(meterRslt);
|
||||
if (iCH == 0)
|
||||
{
|
||||
chanelXMeterRslt.Q3Channel = 1;
|
||||
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt,
|
||||
genesisSmart.TimestampSecStartRawCh1,
|
||||
genesisSmart.TimestampSecEndRawCh1, genesisSmart.VolumeLtrStartRawCh1,
|
||||
genesisSmart.VolumeLtrEndRawCh1);
|
||||
|
||||
try
|
||||
{
|
||||
TestRsltCalibFactor testRsltCalibFactor = null;
|
||||
if (tstRslt.GetCalibrationFactors(waterMeterParent).Count > 0)
|
||||
{
|
||||
testRsltCalibFactor = tstRslt.GetCalibrationFactors(waterMeterParent)[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
//store in table
|
||||
testRsltCalibFactor = new TestRsltCalibFactor();
|
||||
tstRslt.GetCalibrationFactors(waterMeterParent).Add(testRsltCalibFactor);
|
||||
testRsltCalibFactor.CalibFactorIndex = 1;
|
||||
}
|
||||
|
||||
testRsltCalibFactor.TestRslt = chanelXMeterRslt.TestRslt;
|
||||
testRsltCalibFactor.TimeStart = genesisSmart.TimestampSecStartRawCh1;
|
||||
testRsltCalibFactor.TimeEnd = genesisSmart.TimestampSecEndRawCh1;
|
||||
testRsltCalibFactor.VolumeStart = genesisSmart.VolumeLtrStartRawCh1;
|
||||
testRsltCalibFactor.VolumeEnd = genesisSmart.VolumeLtrEndRawCh1;
|
||||
testRsltCalibFactor.Error = chanelXMeterRslt.Error;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Error in store to DB result set CH1", ex);
|
||||
}
|
||||
}
|
||||
else if (iCH == 1)
|
||||
{
|
||||
chanelXMeterRslt.Q3Channel = 2;
|
||||
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt,
|
||||
genesisSmart.TimestampSecStartRawCh2,
|
||||
genesisSmart.TimestampSecEndRawCh2, genesisSmart.VolumeLtrStartRawCh2,
|
||||
genesisSmart.VolumeLtrEndRawCh2);
|
||||
try
|
||||
{
|
||||
|
||||
TestRsltCalibFactor testRsltCalibFactor = null;
|
||||
if (tstRslt.GetCalibrationFactors(waterMeterParent).Count > 1)
|
||||
{
|
||||
testRsltCalibFactor = tstRslt.GetCalibrationFactors(waterMeterParent)[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
//store in table
|
||||
testRsltCalibFactor = new TestRsltCalibFactor();
|
||||
tstRslt.GetCalibrationFactors(waterMeterParent).Add(testRsltCalibFactor);
|
||||
testRsltCalibFactor.CalibFactorIndex = 2;
|
||||
}
|
||||
|
||||
testRsltCalibFactor.TestRslt = chanelXMeterRslt.TestRslt;
|
||||
testRsltCalibFactor.TimeStart = genesisSmart.TimestampSecStartRawCh2;
|
||||
testRsltCalibFactor.TimeEnd = genesisSmart.TimestampSecEndRawCh2;
|
||||
testRsltCalibFactor.VolumeStart = genesisSmart.VolumeLtrStartRawCh2;
|
||||
testRsltCalibFactor.VolumeEnd = genesisSmart.VolumeLtrEndRawCh2;
|
||||
testRsltCalibFactor.Error = chanelXMeterRslt.Error;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Error in store to DB result set CH2", ex);
|
||||
}
|
||||
}
|
||||
else if (iCH == 2)
|
||||
{
|
||||
chanelXMeterRslt.Q3Channel = 3;
|
||||
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt,
|
||||
genesisSmart.TimestampSecStartRawCh3,
|
||||
genesisSmart.TimestampSecEndRawCh3, genesisSmart.VolumeLtrStartRawCh3,
|
||||
genesisSmart.VolumeLtrEndRawCh3);
|
||||
|
||||
try
|
||||
{
|
||||
TestRsltCalibFactor testRsltCalibFactor = null;
|
||||
if (tstRslt.GetCalibrationFactors(waterMeterParent).Count > 2)
|
||||
{
|
||||
testRsltCalibFactor = tstRslt.GetCalibrationFactors(waterMeterParent)[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
//store in table
|
||||
testRsltCalibFactor = new TestRsltCalibFactor();
|
||||
tstRslt.GetCalibrationFactors(waterMeterParent).Add(testRsltCalibFactor);
|
||||
testRsltCalibFactor.CalibFactorIndex = 3;
|
||||
}
|
||||
|
||||
testRsltCalibFactor.TestRslt = chanelXMeterRslt.TestRslt;
|
||||
testRsltCalibFactor.TimeStart = genesisSmart.TimestampSecStartRawCh3;
|
||||
testRsltCalibFactor.TimeEnd = genesisSmart.TimestampSecEndRawCh3;
|
||||
testRsltCalibFactor.VolumeStart = genesisSmart.VolumeLtrStartRawCh3;
|
||||
testRsltCalibFactor.VolumeEnd = genesisSmart.VolumeLtrEndRawCh3;
|
||||
testRsltCalibFactor.Error = chanelXMeterRslt.Error;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("Error in store to DB result set CH3", ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (!genesisSmart.EnableShowChanels)
|
||||
{
|
||||
chXWaterMeter.Disabled = !genesisSmart.EnableShowChanels;
|
||||
//meterTestRsltChX.TestDone = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
chXWaterMeter.Disabled = false;
|
||||
meterTestRsltChX.TestDone = true;
|
||||
}
|
||||
|
||||
meterTestRsltChX.Passed = meterRslt.Passed;
|
||||
}
|
||||
}catch(Exception ex)
|
||||
{
|
||||
log.Error("Error in WaterMeterParentCopy", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CalculateMeterResults(MeterTestRslt meterRslt, IRegReaderDatastream dstrReader, TestRslt tstRslt, double dstrReaderTimestampSecStart, double dstrReaderTimestampSecEnd, double dstrReaderVolumeLtrStart, double dstrReaderVolumeLtrEnd)
|
||||
{
|
||||
meterRslt.TimestampStart = dstrReaderTimestampSecStart;
|
||||
meterRslt.TimestampEnd = !dstrReader.NoSamples
|
||||
? dstrReaderTimestampSecEnd
|
||||
: (dstrReaderTimestampSecStart + tstRslt.TestTime);
|
||||
meterRslt.TestTime = meterRslt.TimestampEnd - meterRslt.TimestampStart;
|
||||
meterRslt.VolumeStart = dstrReaderVolumeLtrStart; /// liter
|
||||
meterRslt.VolumeEnd = dstrReaderVolumeLtrEnd; /// liter
|
||||
meterRslt.VolumeMeter =
|
||||
Math.Abs(dstrReaderVolumeLtrEnd - dstrReaderVolumeLtrStart);
|
||||
meterRslt.VolumeRef = tstRslt.TestTime==0? tstRslt.VolumeCTV : tstRslt.VolumeCTV * meterRslt.TestTime / tstRslt.TestTime;
|
||||
meterRslt.PulsesMaster = tstRslt.TestTime == 0? tstRslt.PulsesMaster :
|
||||
tstRslt.PulsesMaster * meterRslt.TestTime / tstRslt.TestTime;
|
||||
try
|
||||
{
|
||||
meterRslt.TimestampStart = dstrReaderTimestampSecStart;
|
||||
meterRslt.TimestampEnd = !dstrReader.NoSamples
|
||||
? dstrReaderTimestampSecEnd
|
||||
: (dstrReaderTimestampSecStart + tstRslt.TestTime);
|
||||
meterRslt.TestTime = meterRslt.TimestampEnd - meterRslt.TimestampStart;
|
||||
meterRslt.VolumeStart = dstrReaderVolumeLtrStart; /// liter
|
||||
meterRslt.VolumeEnd = dstrReaderVolumeLtrEnd; /// liter
|
||||
meterRslt.VolumeMeter = Math.Abs(dstrReaderVolumeLtrEnd - dstrReaderVolumeLtrStart);
|
||||
meterRslt.VolumeRef = tstRslt.TestTime == 0
|
||||
? tstRslt.VolumeCTV
|
||||
: tstRslt.VolumeCTV * meterRslt.TestTime / tstRslt.TestTime;
|
||||
meterRslt.PulsesMaster = tstRslt.TestTime == 0
|
||||
? tstRslt.PulsesMaster
|
||||
: tstRslt.PulsesMaster * meterRslt.TestTime / tstRslt.TestTime;
|
||||
meterRslt.Error = Formulas.ErrorFromVolumes(meterRslt.VolumeMeter,
|
||||
meterRslt.VolumeRef); // Error based on volume difference
|
||||
}catch(Exception ex)
|
||||
{
|
||||
log.Error($"Error in calculate meter results, meterRslt.Name:{meterRslt.Name()} Calculation Bug Detail:", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1503,6 +1627,12 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
|
||||
MakeSimulated(test, repetitionNr, test.Part, errorPctBase + repetitionNr * 0.1f);
|
||||
}
|
||||
|
||||
//TODO bumi do simulate foe Q3 Calibration
|
||||
///
|
||||
/// Single meters
|
||||
///
|
||||
SimulateQ3CalibrationData(test,1, 0, -5.1f);
|
||||
|
||||
Bridge.OnTestProgress(this, new TestProgressEventArgs(test, repetitionNr, Progress.Completed));
|
||||
Bridge.OnTestCompleted(this, new TestCompletedEventArgs(test.Name, BatchRslts.GetTestRslt(Results.Utils.GetTestName(test.Name, 1, 1), 0)));
|
||||
allResults.Info(TestResult2CsvLine(test.Name, 0)); /// Append the results to the CSV-file
|
||||
@@ -1519,6 +1649,64 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
|
||||
return new List<Event> { TestAndLogUiCmdStop(test, e) ? Event.UiCmdStop : Event.Done };
|
||||
}
|
||||
|
||||
private void SimulateQ3CalibrationData(Test test,int repetitionNr, int part, float errorPctBase)
|
||||
{
|
||||
string testName = test.Name;
|
||||
string fullTestName = Results.Utils.GetTestName(test.Name, test.Repeats, repetitionNr);
|
||||
|
||||
Results.Entities.TestRslt tstRslt = ProcessData.BatchRslts.GetTestRslt(fullTestName, part);
|
||||
Results.Utils.GetCounterStates(tstRslt, Program.LocalSettings.Counters);
|
||||
|
||||
for (int i = 0; i < BatchRslts.WMPositionsCount; i++)
|
||||
{
|
||||
if (WaterMeters.Count > i && WaterMeters[i] != null)
|
||||
WaterMeters[i].SerialNr = "Simul_" + (i + 1).ToString();
|
||||
}
|
||||
|
||||
for (int i = 0; i < BatchRslts.WMPositionsCount; i++)
|
||||
{
|
||||
if (!test.IsPartCompatible(Utils.PartNr(i + 1, BatchRslts.Batch.Compound))) continue;
|
||||
|
||||
Results.Entities.MeterTestRslt meterRslt =
|
||||
BatchRslts.GetEachMeterTestRslt(testName, i, Common.CompoundMeterId.Single);
|
||||
if (meterRslt == null) continue;
|
||||
if (meterRslt?.WaterMeter == null) continue;
|
||||
if (string.IsNullOrEmpty(meterRslt.WaterMeter.SerialNr))
|
||||
{
|
||||
meterRslt.WaterMeter.SerialNr = "Simul_" + (i + 1).ToString();
|
||||
meterRslt.WaterMeter.SerialNrAux = "Simul_" + (i + 1).ToString();
|
||||
log.Debug("Simul_SerialNr: " + meterRslt.WaterMeter.SerialNr);
|
||||
}
|
||||
|
||||
GenericDevices.IRegReader regReader = sensPath.RegisterReaders[i];
|
||||
TBF.Rig.RegisterReaders.GenesisRegReader.implementations.GenesisSmartReader GenesisSmart =
|
||||
regReader as TBF.Rig.RegisterReaders.GenesisRegReader.implementations.GenesisSmartReader;
|
||||
|
||||
|
||||
if (GenesisSmart != null)
|
||||
{
|
||||
log.Debug("GenesisSmart - store data on end!");
|
||||
int CH1 = 0, CH2 = 1, CH3 = 2;
|
||||
|
||||
CalculateMeterResults(meterRslt, GenesisSmart, tstRslt, GenesisSmart.TimestampSecStart,
|
||||
GenesisSmart.TimestampSecEnd, GenesisSmart.VolumeLtrStart, GenesisSmart.VolumeLtrEnd);
|
||||
|
||||
//Init Calculate Calibration
|
||||
GenesisSmart.RefVolume = meterRslt.VolumeRef;
|
||||
GenesisSmart.RefTime = meterRslt.TestTime;
|
||||
|
||||
//Store Raw Calibration Data to database
|
||||
WaterMeterParentCopy(i, CH1, testName, meterRslt, GenesisSmart, tstRslt);
|
||||
log.Debug($"GenesisSmart - i:{i}, CH1:{CH1}, testName:{testName}, meterRslt:{meterRslt.Id}, GenesisSmart:{GenesisSmart.Name}, tstRslt:{tstRslt.Id}");
|
||||
WaterMeterParentCopy(i, CH2, testName, meterRslt, GenesisSmart, tstRslt);
|
||||
log.Debug($"GenesisSmart - i:{i}, CH2:{CH2}, testName:{testName}, meterRslt:{meterRslt.Id}, GenesisSmart:{GenesisSmart.Name}, tstRslt:{tstRslt.Id}");
|
||||
WaterMeterParentCopy(i, CH3, testName, meterRslt, GenesisSmart, tstRslt);
|
||||
log.Debug($"GenesisSmart - i:{i}, CH3:{CH3}, testName:{testName}, meterRslt:{meterRslt.Id}, GenesisSmart:{GenesisSmart.Name}, tstRslt:{tstRslt.Id}");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
IList<Event> Simulate2(Config.Entities.Test test, int repetitionNr, bool isLastRepetition,
|
||||
Compound.CombinedTestParams compoundTestParams,
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
///
|
||||
/// Copyright (c) 2015-2019 Sensus Metering Systems
|
||||
/// Author: Milan Hanajík
|
||||
///
|
||||
using System;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
|
||||
|
||||
namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
{
|
||||
/// <summary>Tracks one completion per worker, activity and group.</summary>
|
||||
public sealed class GenesisWorkerGroupCompletion
|
||||
{
|
||||
private readonly int workerCount;
|
||||
private readonly System.Collections.Generic.HashSet<int> completed = new System.Collections.Generic.HashSet<int>();
|
||||
private int activity, group;
|
||||
private bool released;
|
||||
public GenesisWorkerGroupCompletion(int workerCount)
|
||||
{
|
||||
if (workerCount < 1 || workerCount > 10) throw new ArgumentOutOfRangeException(nameof(workerCount));
|
||||
this.workerCount = workerCount;
|
||||
}
|
||||
public void Begin(int activityStep, int groupNumber)
|
||||
{
|
||||
lock (completed) { activity = activityStep; group = groupNumber; released = false; completed.Clear(); }
|
||||
}
|
||||
public bool Complete(int activityStep, int groupNumber, int worker)
|
||||
{
|
||||
lock (completed)
|
||||
{
|
||||
if (released || activityStep != activity || groupNumber != group || worker < 0 || worker >= workerCount) return false;
|
||||
if (!completed.Add(worker) || completed.Count != workerCount) return false;
|
||||
released = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public static System.Collections.Generic.IEnumerable<int> BoardIndexes(int worker, int workers, int boards)
|
||||
{
|
||||
if (workers < 1 || workers > 10 || worker < 0 || worker >= workers || boards < 0) throw new ArgumentOutOfRangeException();
|
||||
for (int index = worker; index < boards; index += workers) yield return index;
|
||||
}
|
||||
}
|
||||
|
||||
public class CommCompletedEventArgs : EventArgs
|
||||
{
|
||||
public bool WorkerGroupCompleted;
|
||||
public int ActivityStep;
|
||||
public int Group;
|
||||
public int ThreadId;
|
||||
public int WMNr0; /// 0-based water meter position
|
||||
public GenesisSmartReader Ihead;
|
||||
public Results.Entities.WaterMeter Wm;
|
||||
public string CommMessage;
|
||||
public CommErr CommErr;
|
||||
|
||||
public CommCompletedEventArgs(int threadId, int wmNr0, GenesisSmartReader ihead, Results.Entities.WaterMeter wm, string commMessage, CommErr commErr)
|
||||
{
|
||||
this.ThreadId = threadId;
|
||||
this.WMNr0 = wmNr0;
|
||||
this.Ihead = ihead;
|
||||
this.Wm = wm;
|
||||
this.CommMessage = commMessage;
|
||||
this.CommErr = commErr;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Thread={0} WMNr0={1} IHead={2} WM={3} CommMsg={4} CommErr={5}",
|
||||
ThreadId,
|
||||
WMNr0,
|
||||
(Ihead != null) ? Ihead.Name : "null",
|
||||
(Wm != null) ? Wm.WMPosition : -1,
|
||||
(CommMessage != null) ? CommMessage : "null",
|
||||
CommErr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
{
|
||||
public static class GenesisCalibrationFactors
|
||||
{
|
||||
// Preserve the special branch default only when no channel has been configured.
|
||||
public const ushort DefaultFactor = 15625;
|
||||
|
||||
public static bool TryParse(string text1, string text2, string text3,
|
||||
out double[] factors, out string error)
|
||||
{
|
||||
factors = null;
|
||||
error = null;
|
||||
var text = new[] { text1, text2, text3 };
|
||||
if (Array.TrueForAll(text, string.IsNullOrWhiteSpace))
|
||||
{
|
||||
factors = new double[] { DefaultFactor, DefaultFactor, DefaultFactor };
|
||||
return true;
|
||||
}
|
||||
|
||||
var parsed = new double[3];
|
||||
for (int channel = 0; channel < parsed.Length; channel++)
|
||||
{
|
||||
ushort value;
|
||||
if (!ushort.TryParse(text[channel]?.Trim(), NumberStyles.None,
|
||||
CultureInfo.InvariantCulture, out value) || value == 0)
|
||||
{
|
||||
error = "Genesis calibration Text" + (channel + 1) + " must be an integer from 1 to 65535. Set all three channels.";
|
||||
return false;
|
||||
}
|
||||
parsed[channel] = value;
|
||||
}
|
||||
factors = parsed;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+2903
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -32,15 +32,15 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
|
||||
System.Windows.Forms.Form modelessDlg;
|
||||
///
|
||||
delegate void iPerlCommFormDlgt(GenesisCommunicationSeq myRef, iPerlCommunication.TestMethod method, Test test, iPerlCommunicationParams testParams);
|
||||
delegate void iPerlCommFormDlgt(GenesisCommunicationSeq myRef, TestMethod method, Test test, iPerlCommunicationParams testParams);
|
||||
///
|
||||
void OpenIPerlCommForm(GenesisCommunicationSeq myRef, iPerlCommunication.TestMethod method, Test test, iPerlCommunicationParams testParams)
|
||||
void OpenIPerlCommForm(GenesisCommunicationSeq myRef, TestMethod method, Test test, iPerlCommunicationParams testParams)
|
||||
{
|
||||
|
||||
//TODO solve this wia SmartCommunicationForm
|
||||
throw new NotImplementedException();
|
||||
//myRef.modelessDlg = new iPerlCommunicationForm(method, test, testParams);
|
||||
//myRef.modelessDlg.Show();
|
||||
|
||||
myRef.modelessDlg = new GenesisCommunicationForm(method, test, testParams);
|
||||
myRef.modelessDlg.Show();
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
processDataLoggingOp = new TBF.Rig.Operations.ProcessDataLoggingOp(processDataLogger, this, false);
|
||||
|
||||
string cmd;
|
||||
if (testParams.Activity.ToLower().Equals(cmd = iPerlCommunicationForm.GetDefaultQ2CorrectionsStr.ToLower()))
|
||||
if (testParams.Activity.ToLower().Equals(cmd = GenesisCommunicationForm.GetDefaultQ2CorrectionsStr.ToLower()))
|
||||
{
|
||||
TestProgressEventArgs.SetEstimatedTimes(new int[] { 0, 0, 0, 30, 0, 30, 0, 0 });
|
||||
Bridge.OnTestProgress(this, new TestProgressEventArgs(test, Progress.JustStarted));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
///
|
||||
///
|
||||
/// Copyright (c) 2015-2022 Sensus Slovensko a.s.
|
||||
///
|
||||
|
||||
@@ -58,6 +58,9 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
testMethodCfg.UseWebService = tmpCfg.UseWebService;
|
||||
testMethodCfg.BaseUrl = tmpCfg.BaseUrl;
|
||||
testMethodCfg.RelativeUrl = tmpCfg.RelativeUrl;
|
||||
log.InfoFormat("Genesis runtime configuration applied: Name={0}, CommTimeout={1}ms, MaxRetries={2}, DelayBetweenRetries={3}ms, ErrorsToStop={4}",
|
||||
Name, testMethodCfg.CommTimeout, testMethodCfg.MaxCommRetries,
|
||||
testMethodCfg.DelayBetweenRetries, testMethodCfg.IperlCheckErrorsToStop);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -80,6 +83,12 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
: base(cfg)
|
||||
{
|
||||
testMethodCfg = cfg as TestMethodCfg;
|
||||
if (testMethodCfg != null)
|
||||
log.InfoFormat("Genesis component configuration loaded: Name={0}, CommTimeout={1}ms, MaxRetries={2}, DelayBetweenRetries={3}ms, NrThreads={4}, ErrorsToStop={5}",
|
||||
testMethodCfg.Name, testMethodCfg.CommTimeout, testMethodCfg.MaxCommRetries,
|
||||
testMethodCfg.DelayBetweenRetries, testMethodCfg.NrThreads, testMethodCfg.IperlCheckErrorsToStop);
|
||||
else
|
||||
log.Error("Genesis component received an incompatible or null configuration.");
|
||||
CreateMilestonesAndConditions();
|
||||
}
|
||||
|
||||
|
||||
@@ -33,13 +33,13 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
/// Private parameterless constructor invoked by all other (public) constructors
|
||||
TestMethodCfg()
|
||||
{
|
||||
Name = "SmartCommunication";
|
||||
Name = "SmartCommunicationGenesis";
|
||||
ParentName = string.Empty;
|
||||
CommTimeout = 1800; /// ms
|
||||
MaxCommRetries = 4;
|
||||
WaitTimeAfterFailure = 2200;
|
||||
PassThroughWaitTime = 1500;
|
||||
NrThreads = 2; /// 1, 2 or 4 threads
|
||||
NrThreads = 10; /// 1, 2 or 4 threads
|
||||
IperlCheckErrorsToStop = 10;
|
||||
MciTimeoutMs = 4000; // ms, NFC interface
|
||||
BaudRate = 57600; // NFC Interface
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
///
|
||||
///
|
||||
/// Copyright (c) 2015-2020 Sensus Slovensko a.s.
|
||||
///
|
||||
|
||||
@@ -12,15 +12,21 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
{
|
||||
public partial class TestMethodCfgCtrl : Configs.ConfigCtrlUtils, IComponentCfgCtrl
|
||||
{
|
||||
public bool ShowMore { get { return false; } }
|
||||
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(TestMethodCfgCtrl));
|
||||
|
||||
iPerlCommunication.TestMethodCfg config;
|
||||
public bool ShowMore { get { return false; } }
|
||||
|
||||
TestMethodCfg config;
|
||||
public IComponentCfg Config
|
||||
{
|
||||
get { return config as IComponentCfg; }
|
||||
set
|
||||
{
|
||||
config = value as iPerlCommunication.TestMethodCfg;
|
||||
config = value as TestMethodCfg;
|
||||
if (config == null)
|
||||
log.ErrorFormat("Genesis Properties configuration rejected: expected {0}, received {1}", typeof(TestMethodCfg).FullName, value == null ? "null" : value.GetType().FullName);
|
||||
else
|
||||
LogConfiguration("loaded");
|
||||
Redraw();
|
||||
}
|
||||
}
|
||||
@@ -28,6 +34,15 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
public TestMethodCfgCtrl()
|
||||
{
|
||||
InitializeComponent();
|
||||
InitializeSettingToolTips();
|
||||
dfltQ2corrFactorsGroupBox.Visible = false;
|
||||
useWebServiceGroupBox.Visible = false;
|
||||
var factorHint = new System.Windows.Forms.Label
|
||||
{
|
||||
AutoSize = true, Location = dfltQ2corrFactorsGroupBox.Location,
|
||||
Text = "Q3 factors: Water meters / Text1, Text2, Text3 (CH1, CH2, CH3)."
|
||||
};
|
||||
Controls.Add(factorHint);
|
||||
}
|
||||
|
||||
private void EntryFormCfgCtrl_Load(object sender, EventArgs e)
|
||||
@@ -42,7 +57,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
void Redraw()
|
||||
{
|
||||
if (config == null) return; /// Control was not loaded, settings were not changed
|
||||
classNameLabel.Text = config.Factory.ClassName;
|
||||
classNameLabel.Text = config.Factory == null ? "TestMethods.GenesisCommunication" : config.Factory.ClassName;
|
||||
nameTextBox.Text = config.Name;
|
||||
commTimeoutTextBox.Text = config.CommTimeout.ToString();
|
||||
maxCommRetriesTextBox.Text = config.MaxCommRetries.ToString();
|
||||
@@ -50,22 +65,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
nrThreadsTextBox.Text = config.NrThreads.ToString();
|
||||
iperlCheckErrorsToStopTextBox.Text = config.IperlCheckErrorsToStop.ToString();
|
||||
|
||||
textBox15rl.Text = config.DfltQ2c_15_rl.ToString();
|
||||
textBox15lr.Text = config.DfltQ2c_15_lr.ToString();
|
||||
textBox20rl.Text = config.DfltQ2c_20_rl.ToString();
|
||||
textBox20lr.Text = config.DfltQ2c_20_lr.ToString();
|
||||
textBox25_63rl.Text = config.DfltQ2c_25_63_rl.ToString();
|
||||
textBox25_63lr.Text = config.DfltQ2c_25_63_lr.ToString();
|
||||
textBox25_10rl.Text = config.DfltQ2c_25_10_rl.ToString();
|
||||
textBox25_10lr.Text = config.DfltQ2c_25_10_lr.ToString();
|
||||
textBox32rl.Text = config.DfltQ2c_32_rl.ToString();
|
||||
textBox32lr.Text = config.DfltQ2c_32_lr.ToString();
|
||||
textBox40rl.Text = config.DfltQ2c_40_rl.ToString();
|
||||
textBox40lr.Text = config.DfltQ2c_40_lr.ToString();
|
||||
|
||||
useWebServiceCheckBox.Checked = config.UseWebService;
|
||||
baseUrlTextBox.Text = config.BaseUrl;
|
||||
relativeUrlTextBox.Text = config.RelativeUrl;
|
||||
}
|
||||
|
||||
public void Unlock()
|
||||
@@ -77,28 +77,18 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
nrThreadsTextBox.Enabled = true;
|
||||
iperlCheckErrorsToStopTextBox.Enabled = true;
|
||||
|
||||
textBox15rl.Enabled = true;
|
||||
textBox15lr.Enabled = true;
|
||||
textBox20rl.Enabled = true;
|
||||
textBox20lr.Enabled = true;
|
||||
textBox25_63rl.Enabled = true;
|
||||
textBox25_63lr.Enabled = true;
|
||||
textBox25_10rl.Enabled = true;
|
||||
textBox25_10lr.Enabled = true;
|
||||
textBox32rl.Enabled = true;
|
||||
textBox32lr.Enabled = true;
|
||||
textBox40rl.Enabled = true;
|
||||
textBox40lr.Enabled = true;
|
||||
|
||||
useWebServiceCheckBox.Enabled = true;
|
||||
ManageCheckGroupBox(useWebServiceCheckBox, useWebServiceGroupBox);
|
||||
baseUrlTextBox.Enabled = useWebServiceCheckBox.Enabled;
|
||||
relativeUrlTextBox.Enabled = useWebServiceCheckBox.Enabled;
|
||||
}
|
||||
|
||||
public CfgUpdateFlags VerifyCfg(ref string message)
|
||||
{
|
||||
CfgUpdateFlags flags = CfgUpdateFlags.None;
|
||||
if (config == null)
|
||||
{
|
||||
message += Environment.NewLine + "Genesis configuration is not loaded.";
|
||||
log.Error("Genesis Properties validation failed: configuration is not loaded.");
|
||||
return CfgUpdateFlags.Error;
|
||||
}
|
||||
|
||||
int dummy;
|
||||
if (!int.TryParse(commTimeoutTextBox.Text, out dummy) || dummy < 500 || dummy > 5000)
|
||||
@@ -114,79 +104,21 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
if (!int.TryParse(delayBetweenRetriesTextBox.Text, out dummy) || dummy < 0 || dummy > 5000)
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + "'Comm. timeout' should be in range 0 .. 5000";
|
||||
message += Environment.NewLine + "'Delay between retries' should be in range 0 .. 5000";
|
||||
}
|
||||
if (!int.TryParse(nrThreadsTextBox.Text, out dummy) || (dummy != 1 && dummy != 2 && dummy != 4))
|
||||
if (!int.TryParse(nrThreadsTextBox.Text, out dummy) || (dummy < 1 || dummy > 10))
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + "'Nr. threads' should be 1, 2 or 4";
|
||||
message += Environment.NewLine + "'Nr. threads' should be in range 1 .. 10";
|
||||
}
|
||||
if (!int.TryParse(iperlCheckErrorsToStopTextBox.Text, out dummy) || ((dummy < 1) && (dummy > 40)))
|
||||
if (!int.TryParse(iperlCheckErrorsToStopTextBox.Text, out dummy) || ((dummy < 1) || (dummy > 40)))
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + string.Format(Strings.Invalid_0, iperlCheckErrorsToStopLabel.Text);
|
||||
}
|
||||
|
||||
if (!int.TryParse(textBox15rl.Text, out dummy) || dummy < -50 || dummy > 50)
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + "Default Q2 correction factor DN15 RL should be in range -50 .. 50";
|
||||
}
|
||||
if (!int.TryParse(textBox15lr.Text, out dummy) || dummy < -50 || dummy > 50)
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + "Default Q2 correction factor DN15 LR should be in range -50 .. 50";
|
||||
}
|
||||
if (!int.TryParse(textBox20rl.Text, out dummy) || dummy < -50 || dummy > 50)
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + "Default Q2 correction factor DN20 RL should be in range -50 .. 50";
|
||||
}
|
||||
if (!int.TryParse(textBox20lr.Text, out dummy) || dummy < -50 || dummy > 50)
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + "Default Q2 correction factor DN20 LR should be in range -50 .. 50";
|
||||
}
|
||||
if (!int.TryParse(textBox25_63rl.Text, out dummy) || dummy < -50 || dummy > 50)
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + "Default Q2 correction factor DN25 Q3 6.3 RL should be in range -50 .. 50";
|
||||
}
|
||||
if (!int.TryParse(textBox25_63lr.Text, out dummy) || dummy < -50 || dummy > 50)
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + "Default Q2 correction factor DN25 Q3 6.3 LR should be in range -50 .. 50";
|
||||
}
|
||||
if (!int.TryParse(textBox25_10rl.Text, out dummy) || dummy < -50 || dummy > 50)
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + "Default Q2 correction factor DN25 Q3 10 RL should be in range -50 .. 50";
|
||||
}
|
||||
if (!int.TryParse(textBox25_10lr.Text, out dummy) || dummy < -50 || dummy > 50)
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + "Default Q2 correction factor DN25 Q3 10 LR should be in range -50 .. 50";
|
||||
}
|
||||
if (!int.TryParse(textBox32rl.Text, out dummy) || dummy < -50 || dummy > 50)
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + "Default Q2 correction factor DN32 RL should be in range -50 .. 50";
|
||||
}
|
||||
if (!int.TryParse(textBox32lr.Text, out dummy) || dummy < -50 || dummy > 50)
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + "Default Q2 correction factor DN32 LR should be in range -50 .. 50";
|
||||
}
|
||||
if (!int.TryParse(textBox40rl.Text, out dummy) || dummy < -50 || dummy > 50)
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + "Default Q2 correction factor DN40 RL should be in range -50 .. 50";
|
||||
}
|
||||
if (!int.TryParse(textBox40lr.Text, out dummy) || dummy < -50 || dummy > 50)
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + "Default Q2 correction factor DN40 LR should be in range -50 .. 50";
|
||||
}
|
||||
if ((flags & CfgUpdateFlags.Error) != 0)
|
||||
log.WarnFormat("Genesis Properties validation failed: {0}", message);
|
||||
|
||||
return flags;
|
||||
}
|
||||
@@ -195,7 +127,8 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
{
|
||||
CfgUpdateFlags flags = CfgUpdateFlags.None;
|
||||
|
||||
if (config == null) return CfgUpdateFlags.Error; /// Control was not loaded, settings were not changed
|
||||
string message = string.Empty;
|
||||
if ((VerifyCfg(ref message) & CfgUpdateFlags.Error) != 0) return CfgUpdateFlags.Error;
|
||||
|
||||
if (config.Name != nameTextBox.Text)
|
||||
{
|
||||
@@ -203,26 +136,11 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
|
||||
}
|
||||
|
||||
var CommTimeout = config.CommTimeout;;
|
||||
var CommTimeout = config.CommTimeout;
|
||||
var MaxCommRetries = config.MaxCommRetries;
|
||||
var DelayBetweenRetries = config.DelayBetweenRetries;
|
||||
var NrThreads = config.NrThreads;
|
||||
var IperlCheckErrorsToStop = config.IperlCheckErrorsToStop;
|
||||
var DfltQ2c_15_rl = config.DfltQ2c_15_rl;
|
||||
var DfltQ2c_15_lr = config.DfltQ2c_15_lr;
|
||||
var DfltQ2c_20_rl = config.DfltQ2c_20_rl;
|
||||
var DfltQ2c_20_lr = config.DfltQ2c_20_lr;
|
||||
var DfltQ2c_25_63_rl = config.DfltQ2c_25_63_rl;
|
||||
var DfltQ2c_25_63_lr = config.DfltQ2c_25_63_lr;
|
||||
var DfltQ2c_25_10_rl = config.DfltQ2c_25_10_rl;
|
||||
var DfltQ2c_25_10_lr = config.DfltQ2c_25_10_lr;
|
||||
var DfltQ2c_32_rl = config.DfltQ2c_32_rl;
|
||||
var DfltQ2c_32_lr = config.DfltQ2c_32_lr;
|
||||
var DfltQ2c_40_rl = config.DfltQ2c_40_rl;
|
||||
var DfltQ2c_40_lr = config.DfltQ2c_40_lr;
|
||||
var UseWebService = config.UseWebService;
|
||||
var BaseUrl = config.BaseUrl;
|
||||
var RelativeUrl = config.RelativeUrl;
|
||||
|
||||
flags |= UpdateDifferent(ref CommTimeout, commTimeoutTextBox.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
flags |= UpdateDifferent(ref MaxCommRetries, maxCommRetriesTextBox.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
@@ -230,22 +148,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
flags |= UpdateDifferent(ref NrThreads, nrThreadsTextBox.Text, CfgUpdateFlags.RestartRqrd | CfgUpdateFlags.AnyChange);
|
||||
flags |= UpdateDifferent(ref IperlCheckErrorsToStop, iperlCheckErrorsToStopTextBox.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
|
||||
flags |= UpdateDifferent(ref DfltQ2c_15_rl, textBox15rl.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
flags |= UpdateDifferent(ref DfltQ2c_15_lr, textBox15lr.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
flags |= UpdateDifferent(ref DfltQ2c_20_rl, textBox20rl.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
flags |= UpdateDifferent(ref DfltQ2c_20_lr, textBox20lr.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
flags |= UpdateDifferent(ref DfltQ2c_25_63_rl, textBox25_63rl.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
flags |= UpdateDifferent(ref DfltQ2c_25_63_lr, textBox25_63lr.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
flags |= UpdateDifferent(ref DfltQ2c_25_10_rl, textBox25_10rl.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
flags |= UpdateDifferent(ref DfltQ2c_25_10_lr, textBox25_10lr.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
flags |= UpdateDifferent(ref DfltQ2c_32_rl, textBox32rl.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
flags |= UpdateDifferent(ref DfltQ2c_32_lr, textBox32lr.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
flags |= UpdateDifferent(ref DfltQ2c_40_rl, textBox40rl.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
flags |= UpdateDifferent(ref DfltQ2c_40_lr, textBox40lr.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
|
||||
flags |= UpdateDifferent(ref UseWebService, useWebServiceCheckBox.Checked, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
flags |= UpdateDifferent(ref BaseUrl, baseUrlTextBox.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
flags |= UpdateDifferent(ref RelativeUrl, relativeUrlTextBox.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
|
||||
|
||||
|
||||
config.CommTimeout = CommTimeout;
|
||||
@@ -253,30 +156,44 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
config.DelayBetweenRetries = DelayBetweenRetries;
|
||||
config.NrThreads = NrThreads;
|
||||
config.IperlCheckErrorsToStop = IperlCheckErrorsToStop;
|
||||
config.DfltQ2c_15_rl = DfltQ2c_15_rl;
|
||||
config.DfltQ2c_15_lr = DfltQ2c_15_lr;
|
||||
config.DfltQ2c_20_rl = DfltQ2c_20_rl;
|
||||
config.DfltQ2c_20_lr = DfltQ2c_20_lr;
|
||||
config.DfltQ2c_25_63_rl = DfltQ2c_25_63_rl;
|
||||
config.DfltQ2c_25_63_lr = DfltQ2c_25_63_lr;
|
||||
config.DfltQ2c_25_10_rl = DfltQ2c_25_10_rl;
|
||||
config.DfltQ2c_25_10_lr = DfltQ2c_25_10_lr;
|
||||
config.DfltQ2c_32_rl = DfltQ2c_32_rl;
|
||||
config.DfltQ2c_32_lr = DfltQ2c_32_lr;
|
||||
config.DfltQ2c_40_rl = DfltQ2c_40_rl;
|
||||
config.DfltQ2c_40_lr = DfltQ2c_40_lr;
|
||||
config.UseWebService = UseWebService;
|
||||
config.BaseUrl = BaseUrl;
|
||||
config.RelativeUrl = RelativeUrl;
|
||||
|
||||
LogConfiguration("updated; flags=" + flags);
|
||||
|
||||
if ((flags & CfgUpdateFlags.InvokeCfgChange) != 0)
|
||||
{
|
||||
iPerlCommunication.TestMethod.OnCfgChange(this, new CfgChangeArgs(CfgChangeCmd.CfgChange, config));
|
||||
TestMethod.OnCfgChange(this, new CfgChangeArgs(CfgChangeCmd.CfgChange, config));
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
private ToolTip settingsToolTip;
|
||||
|
||||
private void InitializeSettingToolTips()
|
||||
{
|
||||
if (components == null) components = new System.ComponentModel.Container();
|
||||
settingsToolTip = new ToolTip(components) { AutoPopDelay = 30000, InitialDelay = 400, ReshowDelay = 100, ShowAlways = true };
|
||||
settingsToolTip.SetToolTip(nameTextBox, Strings.ResourceManager.GetString("GenesisTooltip_nameTextBox", Strings.Culture));
|
||||
settingsToolTip.SetToolTip(nameLabel, settingsToolTip.GetToolTip(nameTextBox));
|
||||
settingsToolTip.SetToolTip(commTimeoutTextBox, Strings.ResourceManager.GetString("GenesisTooltip_commTimeoutTextBox", Strings.Culture));
|
||||
settingsToolTip.SetToolTip(commTimeoutLabel, settingsToolTip.GetToolTip(commTimeoutTextBox));
|
||||
settingsToolTip.SetToolTip(maxCommRetriesTextBox, Strings.ResourceManager.GetString("GenesisTooltip_maxCommRetriesTextBox", Strings.Culture));
|
||||
settingsToolTip.SetToolTip(maxNrRetriesLabel, settingsToolTip.GetToolTip(maxCommRetriesTextBox));
|
||||
settingsToolTip.SetToolTip(delayBetweenRetriesTextBox, Strings.ResourceManager.GetString("GenesisTooltip_delayBetweenRetriesTextBox", Strings.Culture));
|
||||
settingsToolTip.SetToolTip(delayBetweenRetriesLabel, settingsToolTip.GetToolTip(delayBetweenRetriesTextBox));
|
||||
settingsToolTip.SetToolTip(nrThreadsTextBox, Strings.ResourceManager.GetString("GenesisTooltip_nrThreadsTextBox", Strings.Culture));
|
||||
settingsToolTip.SetToolTip(nrThreadsLabel, settingsToolTip.GetToolTip(nrThreadsTextBox));
|
||||
settingsToolTip.SetToolTip(iperlCheckErrorsToStopTextBox, Strings.ResourceManager.GetString("GenesisTooltip_iperlCheckErrorsToStopTextBox", Strings.Culture));
|
||||
settingsToolTip.SetToolTip(iperlCheckErrorsToStopLabel, settingsToolTip.GetToolTip(iperlCheckErrorsToStopTextBox));
|
||||
}
|
||||
|
||||
private void LogConfiguration(string action)
|
||||
{
|
||||
log.InfoFormat("Genesis Properties {0}: Name={1}, CommTimeout={2}ms, MaxRetries={3}, DelayBetweenRetries={4}ms, NrThreads={5}, ErrorsToStop={6}; Q3 source=WaterMeterData.Text1-Text3; legacy fields preserved",
|
||||
action, config.Name, config.CommTimeout, config.MaxCommRetries,
|
||||
config.DelayBetweenRetries, config.NrThreads, config.IperlCheckErrorsToStop);
|
||||
}
|
||||
|
||||
private void ManageCheckGroupBox(CheckBox chk, GroupBox grp)
|
||||
{
|
||||
/// Make sure the CheckBox isn't in the GroupBox. This will only happen the first time.
|
||||
|
||||
@@ -10,7 +10,6 @@ using Config.Entities;
|
||||
using TBF.Resources;
|
||||
using TBF.Rig.Generic;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication;
|
||||
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
|
||||
|
||||
namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
{
|
||||
@@ -31,7 +30,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
|
||||
public override void InitializeAll()
|
||||
{
|
||||
Activity = iPerlCommunicationForm.ReadConfigurationStr;
|
||||
Activity = GenesisCommunicationForm.SlotInitializeStr;
|
||||
SimultWithPrevious = false;
|
||||
SimultWithNext = false;
|
||||
}
|
||||
@@ -51,62 +50,79 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
|
||||
if (i == 0)
|
||||
{
|
||||
var retVal = new List<string>();
|
||||
retVal.Add(iPerlCommunicationForm.ReadConfigurationStr);
|
||||
retVal.Add(iPerlCommunicationConstants.ReadAdditionalCommonParametersStr);
|
||||
retVal.Add(iPerlCommunicationForm.ReadSerialNrStr);
|
||||
retVal.Add(string.Format("{0} A0", iPerlCommunicationForm.SetTestModeStr));
|
||||
retVal.Add(string.Format("{0} A4", iPerlCommunicationForm.SetTestModeStr));
|
||||
retVal.Add(iPerlCommunicationForm.ReadCalibrationStr);
|
||||
retVal.Add(iPerlCommunicationForm.ReadCalibrationV4Str);
|
||||
retVal.Add(iPerlCommunicationForm.NormalizeCalibrationFactorStr);
|
||||
retVal.Add(iPerlCommunicationForm.NormalizeCalibrationV4FactorsStr);
|
||||
retVal.Add(iPerlCommunicationForm.GetDefaultQ2CorrectionsStr);
|
||||
retVal.Add(iPerlCommunicationForm.ReadQ2CorrectionStr);
|
||||
retVal.Add(iPerlCommunicationForm.ResetQ2CorrectionStr);
|
||||
retVal.Add(iPerlCommunicationForm.WriteDefaultQ2CorrectionsStr);
|
||||
retVal.Add(iPerlCommunicationForm.InitOrReadQ2CorrectionsStr);
|
||||
retVal.Add(iPerlCommunicationForm.WriteCalibrationFactorStr);
|
||||
retVal.Add(iPerlCommunicationForm.WriteCalibrationV4FactorsStr);
|
||||
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionStr);
|
||||
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionAltStr);
|
||||
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionGreeceStr);
|
||||
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionRLStr);
|
||||
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionLRStr);
|
||||
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionIncl05Str);
|
||||
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionAltIncl05Str);
|
||||
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionPlusIncl05Str);
|
||||
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionPlusAltIncl05Str);
|
||||
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionGreeceIncl05Str);
|
||||
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionRLIncl05Str);
|
||||
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionLRIncl05Str);
|
||||
retVal.Add(iPerlCommunicationSeq.Q2correctedFromCmd + "Qx");
|
||||
retVal.Add(iPerlCommunicationSeq.StrictQ2ErrorCheckStr + "Qx");
|
||||
retVal.Add(iPerlCommunicationSeq.Q2correctionCheckCmd);
|
||||
retVal.Add(iPerlCommunicationSeq.IperlCheckCmd);
|
||||
retVal.Add(iPerlCommunicationForm.UpdateBothQ2FactorsTestRLOnlyStr);
|
||||
retVal.Add(iPerlCommunicationForm.UpdateBothQ2FactorsTestLROnlyStr);
|
||||
retVal.Add(iPerlCommunicationForm.UpdateQ2CorrectionsStr);
|
||||
retVal.Add(iPerlCommunicationForm.ConditnlUpdateQ2CorrectionsStr);
|
||||
retVal.Add(iPerlCommunicationForm.ConditnlUpdateQ2CorrRLStr);
|
||||
retVal.Add(iPerlCommunicationForm.ConditnlUpdateQ2CorrLRStr);
|
||||
retVal.Add("Q2 corrected from Q2adj");
|
||||
retVal.Add("Q2 correction check Q2bc Q2ac");
|
||||
retVal.Add(iPerlCommunicationForm.SetActiveModeStr);
|
||||
retVal.Add(iPerlCommunicationForm.SetIdleModeStr);
|
||||
retVal.Add("---");
|
||||
retVal.Add(iPerlCommunicationForm.Reset2HzCorrectionStr);
|
||||
retVal.Add(iPerlCommunicationForm.Write2HzCorrectionStr);
|
||||
retVal.Add(iPerlCommunicationForm.DewaReworkRLStr);
|
||||
retVal.Add(iPerlCommunicationForm.DewaReworkLRStr);
|
||||
retVal.Add(iPerlCommunicationForm.StartTestingSealedMetersStr);
|
||||
retVal.Add(iPerlCommunicationForm.EndTestingSealedMetersStr);
|
||||
retVal.Add(string.Format("{0} if enabled", iPerlCommunicationForm.ReadConfigurationStr));
|
||||
retVal.Add(string.Format("{0} 80", iPerlCommunicationForm.SetTestModeStr));
|
||||
retVal.Add("iPerl_check prevWorkStep direction q2factors");
|
||||
for (iPerlCommunication.ConditionID id = iPerlCommunication.ConditionID.A; id < iPerlCommunication.ConditionID.Count; id++)
|
||||
{
|
||||
retVal.Add(string.Format(iPerlCommunication.SequenceConditionOp.ConditionNameFmt, id));
|
||||
}
|
||||
|
||||
retVal.Add(GenesisCommunicationForm.SlotInitializeStr);
|
||||
retVal.Add(GenesisCommunicationForm.SlotUpdateStr);
|
||||
retVal.Add(GenesisCommunicationForm.SlotConnectStr);
|
||||
retVal.Add(GenesisCommunicationForm.SlotPCBSlotStr);
|
||||
retVal.Add(GenesisCommunicationForm.SlotSetPasswordStr);
|
||||
retVal.Add(GenesisCommunicationForm.SlotLoginStr);
|
||||
retVal.Add(GenesisCommunicationForm.SlotGroupedLoginStr);
|
||||
retVal.Add(GenesisCommunicationForm.SlotSetTestModeStr);
|
||||
retVal.Add(GenesisCommunicationForm.SlotSetActiveModeStr);
|
||||
retVal.Add(GenesisCommunicationForm.SlotDisconnectStr);
|
||||
retVal.Add(GenesisCommunicationForm.PrepareSlotQ3CalibrationStr);
|
||||
retVal.Add(GenesisCommunicationForm.WriteSlotQ3CalibrationStr);
|
||||
retVal.Add(GenesisCommunicationForm.CheckMeterPrepareStr);
|
||||
retVal.Add(GenesisCommunicationForm.HoldSlotStr);
|
||||
|
||||
|
||||
|
||||
// retVal.Add(GenesisCommunicationForm.ReadConfigurationStr);
|
||||
// retVal.Add(GenesisCommunicationForm.ReadSerialNrStr);
|
||||
// retVal.Add(string.Format("{0} A0", GenesisCommunicationForm.SetTestModeStr));
|
||||
// retVal.Add(string.Format("{0} A4", GenesisCommunicationForm.SetTestModeStr));
|
||||
// retVal.Add(GenesisCommunicationForm.ReadCalibrationStr);
|
||||
// retVal.Add(GenesisCommunicationForm.ReadCalibrationV4Str);
|
||||
// retVal.Add(GenesisCommunicationForm.NormalizeCalibrationFactorStr);
|
||||
// retVal.Add(GenesisCommunicationForm.NormalizeCalibrationV4FactorsStr);
|
||||
// retVal.Add(GenesisCommunicationForm.GetDefaultQ2CorrectionsStr);
|
||||
// retVal.Add(GenesisCommunicationForm.ReadQ2CorrectionStr);
|
||||
// retVal.Add(GenesisCommunicationForm.ResetQ2CorrectionStr);
|
||||
// retVal.Add(GenesisCommunicationForm.WriteDefaultQ2CorrectionsStr);
|
||||
// retVal.Add(GenesisCommunicationForm.InitOrReadQ2CorrectionsStr);
|
||||
// retVal.Add(GenesisCommunicationForm.WriteCalibrationFactorStr);
|
||||
// retVal.Add(GenesisCommunicationForm.WriteCalibrationV4FactorsStr);
|
||||
// retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionStr);
|
||||
// retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionAltStr);
|
||||
// retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionGreeceStr);
|
||||
// retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionRLStr);
|
||||
// retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionLRStr);
|
||||
// retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionIncl05Str);
|
||||
// retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionAltIncl05Str);
|
||||
// retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionPlusIncl05Str);
|
||||
// retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionPlusAltIncl05Str);
|
||||
// retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionGreeceIncl05Str);
|
||||
// retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionRLIncl05Str);
|
||||
// retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionLRIncl05Str);
|
||||
// retVal.Add(iPerlCommunicationSeq.Q2correctedFromCmd + "Qx");
|
||||
// retVal.Add(iPerlCommunicationSeq.StrictQ2ErrorCheckStr + "Qx");
|
||||
// retVal.Add(iPerlCommunicationSeq.Q2correctionCheckCmd);
|
||||
// retVal.Add(iPerlCommunicationSeq.IperlCheckCmd);
|
||||
// retVal.Add(GenesisCommunicationForm.UpdateBothQ2FactorsTestRLOnlyStr);
|
||||
// retVal.Add(GenesisCommunicationForm.UpdateBothQ2FactorsTestLROnlyStr);
|
||||
// retVal.Add(GenesisCommunicationForm.UpdateQ2CorrectionsStr);
|
||||
// retVal.Add(GenesisCommunicationForm.ConditnlUpdateQ2CorrectionsStr);
|
||||
// retVal.Add(GenesisCommunicationForm.ConditnlUpdateQ2CorrRLStr);
|
||||
// retVal.Add(GenesisCommunicationForm.ConditnlUpdateQ2CorrLRStr);
|
||||
// retVal.Add("Q2 corrected from Q2adj");
|
||||
// retVal.Add("Q2 correction check Q2bc Q2ac");
|
||||
// retVal.Add(GenesisCommunicationForm.SetActiveModeStr);
|
||||
// retVal.Add(GenesisCommunicationForm.SetIdleModeStr);
|
||||
// retVal.Add("---");
|
||||
// retVal.Add(GenesisCommunicationForm.Reset2HzCorrectionStr);
|
||||
// retVal.Add(GenesisCommunicationForm.Write2HzCorrectionStr);
|
||||
// retVal.Add(GenesisCommunicationForm.DewaReworkRLStr);
|
||||
// retVal.Add(GenesisCommunicationForm.DewaReworkLRStr);
|
||||
// retVal.Add(GenesisCommunicationForm.StartTestingSealedMetersStr);
|
||||
// retVal.Add(GenesisCommunicationForm.EndTestingSealedMetersStr);
|
||||
// retVal.Add(string.Format("{0} if enabled", GenesisCommunicationForm.ReadConfigurationStr));
|
||||
// retVal.Add(string.Format("{0} 80", GenesisCommunicationForm.SetTestModeStr));
|
||||
// retVal.Add("iPerl_check prevWorkStep direction q2factors");
|
||||
// for (ConditionID id = ConditionID.A; id < ConditionID.Count; id++)
|
||||
// {
|
||||
// retVal.Add(string.Format(SequenceConditionOp.ConditionNameFmt, id));
|
||||
// }
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@@ -730,7 +730,7 @@ namespace TBF.Rig.TestMethods.StandingStart
|
||||
State.Create(string.Format("{0}({1}) : Delay for meters stabilization <T flow stab. - start = {2}s> ", test.Method, test.Name, delay))
|
||||
.AddOperation(checkUiOp)
|
||||
.AddOperations(readTempPressOps)
|
||||
.AddOperation(testInProgress)
|
||||
//.AddOperation(testInProgress)
|
||||
.AddOperation(new Operations.TimerOp(delay)) //...develop: step 5
|
||||
.EnterState();
|
||||
do
|
||||
|
||||
@@ -48,6 +48,8 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
public const int EndOptoDataCount = OptoDataBufferSize - StartOptoDataCount;
|
||||
public const int StartEndFilterSamplesCount2 = 1; //20 /// StartEndFilterSamplesCount = 2 * StartEndFilterSamplesCount2 + 1
|
||||
public const int FeatureVectorSize = 9;
|
||||
/// <summary>Default raw ViewCalibration value which represents 100 % according to the iPerl ASIC protocol.</summary>
|
||||
public const double DefaultCalibFactorNominal = 4096.0;
|
||||
|
||||
private const int StartSampleDelaySec = 5;
|
||||
private const int EndSampleDelayCount = 2;
|
||||
@@ -94,6 +96,15 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
public string QuantityUnits { get; set; }
|
||||
|
||||
public double CalibTarget { get { return iperlHeadCfg.ProcParams.CalibTarget; } }
|
||||
public double CalibFactorNominal
|
||||
{
|
||||
get
|
||||
{
|
||||
return iperlHeadCfg != null && iperlHeadCfg.CalibFactorNominal > 0.0
|
||||
? iperlHeadCfg.CalibFactorNominal
|
||||
: DefaultCalibFactorNominal;
|
||||
}
|
||||
}
|
||||
public ushort FactorLimitLo { get { return (ushort)iperlHeadCfg.ProcParams.FactorLimitLo; } }
|
||||
public ushort FactorLimitHi { get { return (ushort)iperlHeadCfg.ProcParams.FactorLimitHi; } }
|
||||
public Counting InitFlowDir { get { return (iperlHeadCfg != null && iperlHeadCfg.ProcParams != null) ? iperlHeadCfg.ProcParams.Counting : Counting.Arbitrary; } }
|
||||
@@ -157,6 +168,27 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Converts the raw two-byte ViewCalibration factor to its percentage representation.</summary>
|
||||
public static double CalculateCalibFactorPercentage(ushort rawCalibFactor)
|
||||
{
|
||||
return CalculateCalibFactorPercentage(rawCalibFactor, DefaultCalibFactorNominal);
|
||||
}
|
||||
|
||||
public static double CalculateCalibFactorPercentage(ushort rawCalibFactor, double nominalCalibFactor)
|
||||
{
|
||||
return nominalCalibFactor > 0.0 ? rawCalibFactor * 100.0 / nominalCalibFactor : 0.0;
|
||||
}
|
||||
|
||||
public double CalibFactorPercentage
|
||||
{
|
||||
get { return CalculateCalibFactorPercentage(CalibFactor, CalibFactorNominal); }
|
||||
}
|
||||
|
||||
public double CalibFactorCorrectionPercentage
|
||||
{
|
||||
get { return CalibFactorPercentage - 100.0; }
|
||||
}
|
||||
|
||||
public ushort OrigCalibFactorLNA;
|
||||
public ushort CalibFactorLNA { get { return (CalibrationStructV4 != null) ? CalibrationStructV4.CalibrationLNA : (ushort)0; } }
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
public int MuxBoardNr; /// 0 = use RfidComPort(Nr), otherwise mux. board nr. 1 .. 4
|
||||
public int Group; /// Number written to QuidoRS to connct the watermeter to RfidComPort, 1 .. 10
|
||||
public CommunicationInterface CommunicationInterface; /// Communication Interface: RFID or NFC
|
||||
public double CalibFactorNominal; /// Raw calibration factor representing 100 % in the results calculation
|
||||
|
||||
/// <summary> Procedure parameters </summary>
|
||||
[XmlIgnore]
|
||||
@@ -49,6 +50,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
ProcParams = CreateProcParamsProvider() as ProcParams;
|
||||
CommunicationInterface = CommunicationInterface.RFID;
|
||||
HeadCommunicationComPortNr = 0;
|
||||
CalibFactorNominal = IperlHead.DefaultCalibFactorNominal;
|
||||
}
|
||||
|
||||
public IperlHeadCfg(IComponentFactory factory)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/// Copyright (c) 2015-2017 Sensus Metering Systems
|
||||
///
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Windows.Forms;
|
||||
using Common;
|
||||
@@ -33,6 +34,10 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
private void WaterMeterCfgCtrl_Load(object sender, EventArgs e)
|
||||
{
|
||||
nameLabel.Text = Strings.Name;
|
||||
calibFactorNominalLabel.Text = GetLocalizedString("IperlCalibFactorNominal", "Default calibration factor:");
|
||||
string calibFactorNominalTooltip = GetLocalizedString("IperlCalibFactorNominalTooltip", "Raw calibration factor representing 100 %. This value is used to calculate 'iPerl CalibFactor (%)' in the results configuration.");
|
||||
calibFactorNominalToolTip.SetToolTip(calibFactorNominalLabel, calibFactorNominalTooltip);
|
||||
calibFactorNominalToolTip.SetToolTip(calibFactorNominalTextBox, calibFactorNominalTooltip);
|
||||
classNameLabel.Text = config.Factory.ClassName;
|
||||
Redraw();
|
||||
}
|
||||
@@ -55,6 +60,10 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
muxBoardNrTextBox.Text = config.MuxBoardNr.ToString();
|
||||
groupTextBox.Text = config.Group.ToString();
|
||||
comboBoxCommunicationInterface.SelectedItem = config.CommunicationInterface.ToString();
|
||||
double nominalCalibFactor = config.CalibFactorNominal > 0.0
|
||||
? config.CalibFactorNominal
|
||||
: IperlHead.DefaultCalibFactorNominal;
|
||||
calibFactorNominalTextBox.Text = nominalCalibFactor.ToString(CultureInfo.CurrentCulture);
|
||||
tabPage2.Controls.Add(new IperlHeadTestCtrl(config));
|
||||
}
|
||||
|
||||
@@ -71,6 +80,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
muxBoardNrTextBox.Enabled = true;
|
||||
groupTextBox.Enabled = true;
|
||||
comboBoxCommunicationInterface.Enabled = true;
|
||||
calibFactorNominalTextBox.Enabled = true;
|
||||
}
|
||||
|
||||
public CfgUpdateFlags VerifyCfg(ref string message)
|
||||
@@ -127,6 +137,13 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
message += Environment.NewLine + string.Format(Strings.Invalid_0, groupLabel.Text);
|
||||
}
|
||||
|
||||
double nominalCalibFactor;
|
||||
if (!TryParseDouble(calibFactorNominalTextBox.Text, out nominalCalibFactor) || nominalCalibFactor <= 0.0)
|
||||
{
|
||||
flags |= CfgUpdateFlags.Error;
|
||||
message += Environment.NewLine + string.Format(Strings.Invalid_0, calibFactorNominalLabel.Text);
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
@@ -155,8 +172,22 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
config.Group = int.Parse(groupTextBox.Text);
|
||||
config.CommunicationInterface = (CommunicationInterface)comboBoxCommunicationInterface.SelectedIndex;
|
||||
config.HeadCommunicationComPortNr = int.Parse(headPortNrTextBox.Text);
|
||||
double nominalCalibFactor;
|
||||
if (TryParseDouble(calibFactorNominalTextBox.Text, out nominalCalibFactor))
|
||||
config.CalibFactorNominal = nominalCalibFactor;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
private static bool TryParseDouble(string value, out double result)
|
||||
{
|
||||
return double.TryParse(value, NumberStyles.Float, CultureInfo.CurrentCulture, out result) ||
|
||||
double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out result);
|
||||
}
|
||||
|
||||
private static string GetLocalizedString(string resourceName, string fallback)
|
||||
{
|
||||
return Strings.ResourceManager.GetString(resourceName, Strings.Culture) ?? fallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+26
@@ -31,6 +31,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.tabControl1 = new System.Windows.Forms.TabControl();
|
||||
this.tabPage1 = new System.Windows.Forms.TabPage();
|
||||
this.label4 = new System.Windows.Forms.Label();
|
||||
@@ -60,6 +61,9 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
this.groupBox2 = new System.Windows.Forms.GroupBox();
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
this.headPortNrTextBox = new System.Windows.Forms.TextBox();
|
||||
this.calibFactorNominalLabel = new System.Windows.Forms.Label();
|
||||
this.calibFactorNominalTextBox = new System.Windows.Forms.TextBox();
|
||||
this.calibFactorNominalToolTip = new System.Windows.Forms.ToolTip(this.components);
|
||||
this.tabControl1.SuspendLayout();
|
||||
this.tabPage1.SuspendLayout();
|
||||
this.groupBox1.SuspendLayout();
|
||||
@@ -80,6 +84,8 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
// tabPage1
|
||||
//
|
||||
this.tabPage1.Controls.Add(this.groupBox2);
|
||||
this.tabPage1.Controls.Add(this.calibFactorNominalTextBox);
|
||||
this.tabPage1.Controls.Add(this.calibFactorNominalLabel);
|
||||
this.tabPage1.Controls.Add(this.label4);
|
||||
this.tabPage1.Controls.Add(this.label3);
|
||||
this.tabPage1.Controls.Add(this.groupBox1);
|
||||
@@ -364,6 +370,23 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
this.groupBox2.TabIndex = 26;
|
||||
this.groupBox2.TabStop = false;
|
||||
this.groupBox2.Text = "Head Communication";
|
||||
//
|
||||
// calibFactorNominalLabel
|
||||
//
|
||||
this.calibFactorNominalLabel.AutoSize = true;
|
||||
this.calibFactorNominalLabel.Location = new System.Drawing.Point(290, 76);
|
||||
this.calibFactorNominalLabel.Name = "calibFactorNominalLabel";
|
||||
this.calibFactorNominalLabel.Size = new System.Drawing.Size(142, 16);
|
||||
this.calibFactorNominalLabel.TabIndex = 27;
|
||||
this.calibFactorNominalLabel.Text = "Default calibration factor:";
|
||||
//
|
||||
// calibFactorNominalTextBox
|
||||
//
|
||||
this.calibFactorNominalTextBox.Enabled = false;
|
||||
this.calibFactorNominalTextBox.Location = new System.Drawing.Point(290, 96);
|
||||
this.calibFactorNominalTextBox.Name = "calibFactorNominalTextBox";
|
||||
this.calibFactorNominalTextBox.Size = new System.Drawing.Size(92, 22);
|
||||
this.calibFactorNominalTextBox.TabIndex = 28;
|
||||
//
|
||||
// label2
|
||||
//
|
||||
@@ -437,5 +460,8 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
private System.Windows.Forms.GroupBox groupBox2;
|
||||
private System.Windows.Forms.TextBox headPortNrTextBox;
|
||||
private System.Windows.Forms.Label label2;
|
||||
private System.Windows.Forms.Label calibFactorNominalLabel;
|
||||
private System.Windows.Forms.TextBox calibFactorNominalTextBox;
|
||||
private System.Windows.Forms.ToolTip calibFactorNominalToolTip;
|
||||
}
|
||||
}
|
||||
|
||||
+35
-1
@@ -1237,11 +1237,25 @@
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Diagnostic\WriterDiagnosticResult.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Enums.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Factory.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Formatters\PayloadMapping.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Formatters\PayloadReferenceAnalyzer.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Formatters\PayloadTemplateDefinition.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Formatters\PayloadTemplateInspector.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Formatters\PayloadTemplateNode.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Formatters\XmlPayloadGenerator.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Formatters\XmlPayloadRequestBuilder.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Interfaces\DataWriteRequest.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Interfaces\IDataStorageWriter.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Types.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\UI\XmlPayloadViewerDlg.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\UI\XmlPayloadViewerDlg.designer.cs">
|
||||
<DependentUpon>XmlPayloadViewerDlg.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Writer.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\WriterCfg.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Writers\XmlFileWriter.cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Writers\CsvWriter .cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Writers\DatabaseWriter .cs" />
|
||||
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Writers\JsonWriter.cs" />
|
||||
@@ -1296,6 +1310,12 @@
|
||||
<Compile Include="Rig\Output\DB\ResultsWriter\ResultsWriterResultsDlg.Designer.cs">
|
||||
<DependentUpon>ResultsWriterResultsDlg.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Rig\Output\DB\ResultsWriter\XmlDestinationPickerDlg.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Rig\Output\DB\ResultsWriter\XmlDestinationPickerDlg.Designer.cs">
|
||||
<DependentUpon>XmlDestinationPickerDlg.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Rig\Output\DB\SaveDiverterCorrections\Factory.cs" />
|
||||
<Compile Include="Rig\Output\DB\SaveDiverterCorrections\SaveDiverterCorr.cs" />
|
||||
<Compile Include="Rig\Output\DB\SaveDiverterCorrections\SaveDiverterCorrCfg.cs" />
|
||||
@@ -4633,4 +4653,18 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
<ItemGroup>
|
||||
<Compile Include="Rig\RegisterReaders\GenesisRegReader\communication\GciBridgeClient.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\GenesisRegReader\communication\IGciBridgeClient.cs" />
|
||||
<Compile Include="Rig\RegisterReaders\GenesisRegReader\communication\common\LedState.cs" />
|
||||
<Compile Include="Rig\TestMethods\GenesisCommunication\GenesisCommunicationForm.cs" />
|
||||
<Compile Include="Rig\TestMethods\GenesisCommunication\GenesisCommunicationForm.designer.cs" />
|
||||
<EmbeddedResource Include="Rig\TestMethods\GenesisCommunication\GenesisCommunicationForm.resx" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Rig\TestMethods\GenesisCommunication\CommCompletedEventArgs.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Rig\TestMethods\GenesisCommunication\GenesisCalibrationFactors.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TBF.Rig.TestMethods.GenesisCommunication;
|
||||
|
||||
namespace TBFTests
|
||||
{
|
||||
[TestClass]
|
||||
[TestCategory("GenesisParallelScheduling")]
|
||||
public class GenesisParallelSchedulingTests
|
||||
{
|
||||
[DataTestMethod]
|
||||
[DataRow(1)] [DataRow(2)] [DataRow(3)] [DataRow(4)] [DataRow(5)]
|
||||
[DataRow(6)] [DataRow(7)] [DataRow(8)] [DataRow(9)] [DataRow(10)]
|
||||
public void EveryBoardIsAssignedExactlyOnceForAllSupportedWorkerCounts(int workers)
|
||||
{
|
||||
foreach (int boards in new[] { 0, 1, 2, 7, 10 })
|
||||
{
|
||||
var actual = Enumerable.Range(0, workers)
|
||||
.SelectMany(worker => GenesisWorkerGroupCompletion.BoardIndexes(worker, workers, boards)).OrderBy(x => x).ToArray();
|
||||
CollectionAssert.AreEqual(Enumerable.Range(0, boards).ToArray(), actual);
|
||||
}
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(1)] [DataRow(2)] [DataRow(3)] [DataRow(4)] [DataRow(5)]
|
||||
[DataRow(6)] [DataRow(7)] [DataRow(8)] [DataRow(9)] [DataRow(10)]
|
||||
public void GroupAdvancesOnlyAfterEveryDistinctWorkerFinishes(int count)
|
||||
{
|
||||
var completion = new GenesisWorkerGroupCompletion(count);
|
||||
completion.Begin(0, 1);
|
||||
Assert.IsFalse(completion.Complete(0, 2, 0));
|
||||
Assert.IsFalse(completion.Complete(1, 1, 0));
|
||||
Assert.IsFalse(completion.Complete(0, 1, -1));
|
||||
Assert.IsFalse(completion.Complete(0, 1, count));
|
||||
for (int worker = 0; worker < count; worker++)
|
||||
{
|
||||
Assert.AreEqual(worker == count - 1, completion.Complete(0, 1, worker));
|
||||
Assert.IsFalse(completion.Complete(0, 1, worker), "Duplicate completion must not advance the group.");
|
||||
}
|
||||
completion.Begin(0, 2);
|
||||
Assert.IsFalse(completion.Complete(0, 1, 0), "Late event from the previous group.");
|
||||
for (int worker = count - 1; worker >= 0; worker--)
|
||||
Assert.AreEqual(worker == 0, completion.Complete(0, 2, worker));
|
||||
completion.Begin(1, 1);
|
||||
Assert.IsFalse(completion.Complete(0, 2, 0));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MultipleActivitiesAndGroupsRequireAllWorkersIncludingIdleWorkers()
|
||||
{
|
||||
var completion = new GenesisWorkerGroupCompletion(10);
|
||||
for (int activity = 0; activity < 3; activity++)
|
||||
for (int group = 1; group <= 10; group++)
|
||||
{
|
||||
completion.Begin(activity, group);
|
||||
Assert.IsFalse(completion.Complete(activity - 1, group, 0));
|
||||
Assert.IsFalse(completion.Complete(activity, group - 1, 0));
|
||||
// Also models HOLD ON: no board requests, but each worker must finish.
|
||||
for (int worker = 0; worker < 10; worker++)
|
||||
Assert.AreEqual(worker == 9, completion.Complete(activity, group, worker));
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TenWorkersCanRunConcurrentlyAndSlowTenthWorkerHoldsGroup()
|
||||
{
|
||||
var completion = new GenesisWorkerGroupCompletion(10);
|
||||
completion.Begin(0, 1);
|
||||
using (var started = new CountdownEvent(10))
|
||||
using (var firstNine = new CountdownEvent(9))
|
||||
using (var run = new ManualResetEventSlim(false))
|
||||
using (var slow = new ManualResetEventSlim(false))
|
||||
{
|
||||
int advances = 0;
|
||||
var threads = Enumerable.Range(0, 10).Select(worker => new Thread(() =>
|
||||
{
|
||||
started.Signal();
|
||||
run.Wait();
|
||||
if (worker == 9) slow.Wait();
|
||||
if (completion.Complete(0, 1, worker)) Interlocked.Increment(ref advances);
|
||||
if (worker != 9) firstNine.Signal();
|
||||
}) { IsBackground = true }).ToArray();
|
||||
foreach (var thread in threads) thread.Start();
|
||||
try
|
||||
{
|
||||
Assert.IsTrue(started.Wait(5000), "All ten workers must start before any finishes.");
|
||||
run.Set();
|
||||
Assert.IsTrue(firstNine.Wait(5000));
|
||||
Assert.AreEqual(0, Volatile.Read(ref advances), "Nine completions must not release a ten-worker group.");
|
||||
slow.Set();
|
||||
}
|
||||
finally
|
||||
{
|
||||
run.Set(); slow.Set();
|
||||
foreach (var thread in threads) thread.Join(5000);
|
||||
}
|
||||
Assert.AreEqual(1, advances);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parallel_Group1OneToTen_Group2One_AllTenCallsOverlap()
|
||||
{
|
||||
VerifyProcessingScenario(10, 10, 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serial_Group1One_Group2OneToTen_NoCallsOverlap()
|
||||
{
|
||||
VerifyProcessingScenario(10, 1, 10);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Combined_FiveGroup1Boards_TwoGroup2Groups_ParallelWithinSerialBetween()
|
||||
{
|
||||
VerifyProcessingScenario(10, 5, 2);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Combined_FourWorkers_TenBoards_ThreeGroups_EveryMeterRunsOnce()
|
||||
{
|
||||
VerifyProcessingScenario(4, 10, 3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serial_OneWorker_TenGroup1Boards_AllCallsRunOnce()
|
||||
{
|
||||
VerifyProcessingScenario(1, 10, 1);
|
||||
}
|
||||
|
||||
// Hardware-free harness using the production assignment and completion helpers.
|
||||
// Calls are held at a rendezvous, so overlap is proven without timing guesses.
|
||||
private static void VerifyProcessingScenario(int workers, int boards, int groups)
|
||||
{
|
||||
var completion = new GenesisWorkerGroupCompletion(workers);
|
||||
completion.Begin(0, 1);
|
||||
var sync = new object();
|
||||
int currentGroup = 1, active = 0, advances = 0;
|
||||
bool abort = false;
|
||||
var calls = new int[groups, boards];
|
||||
var finished = new int[groups];
|
||||
var peaks = new int[groups];
|
||||
var failures = new System.Collections.Generic.List<Exception>();
|
||||
int parallelism = Math.Min(workers, boards);
|
||||
var rendezvous = Enumerable.Range(0, groups).Select(_ => new CountdownEvent(parallelism)).ToArray();
|
||||
var threads = Enumerable.Range(0, workers).Select(worker => new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int group = 1; group <= groups; group++)
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
while (currentGroup != group && !abort)
|
||||
if (!Monitor.Wait(sync, 10000)) throw new TimeoutException("Group did not advance.");
|
||||
if (abort) return;
|
||||
}
|
||||
bool firstCall = true;
|
||||
foreach (int board in GenesisWorkerGroupCompletion.BoardIndexes(worker, workers, boards))
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
for (int previous = 0; previous < group - 1; previous++)
|
||||
Assert.AreEqual(boards, finished[previous], "Next Group 2 started before previous group completed.");
|
||||
calls[group - 1, board]++;
|
||||
active++;
|
||||
peaks[group - 1] = Math.Max(peaks[group - 1], active);
|
||||
}
|
||||
if (firstCall)
|
||||
{
|
||||
rendezvous[group - 1].Signal();
|
||||
Assert.IsTrue(rendezvous[group - 1].Wait(10000), "Assigned workers did not enter calls concurrently.");
|
||||
firstCall = false;
|
||||
}
|
||||
lock (sync) { active--; finished[group - 1]++; }
|
||||
}
|
||||
if (completion.Complete(0, group, worker))
|
||||
{
|
||||
lock (sync)
|
||||
{
|
||||
Assert.AreEqual(boards, finished[group - 1]);
|
||||
Assert.AreEqual(0, active);
|
||||
advances++;
|
||||
completion.Begin(0, group + 1);
|
||||
currentGroup++;
|
||||
Monitor.PulseAll(sync);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
lock (sync) { failures.Add(error); abort = true; Monitor.PulseAll(sync); }
|
||||
}
|
||||
}) { IsBackground = true }).ToArray();
|
||||
foreach (var thread in threads) thread.Start();
|
||||
bool allJoined = true;
|
||||
foreach (var thread in threads) allJoined &= thread.Join(15000);
|
||||
Assert.IsTrue(allJoined, "Workers did not terminate.");
|
||||
foreach (var item in rendezvous) item.Dispose();
|
||||
Assert.AreEqual(0, failures.Count, string.Join("\n", failures.Select(x => x.ToString())));
|
||||
Assert.AreEqual(groups, advances);
|
||||
for (int group = 0; group < groups; group++)
|
||||
{
|
||||
Assert.AreEqual(parallelism, peaks[group], "Unexpected maximum concurrent calls.");
|
||||
for (int board = 0; board < boards; board++)
|
||||
Assert.AreEqual(1, calls[group, board], "Meter was skipped or called more than once.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
using System;
|
||||
using System.Data.SQLite;
|
||||
using System.IO;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Results.Entities;
|
||||
using Results.Entities.helpers;
|
||||
using GenesisCalibrationFactors = TBF.Rig.TestMethods.GenesisCommunication.GenesisCalibrationFactors;
|
||||
|
||||
namespace TBFTests
|
||||
{
|
||||
[TestClass]
|
||||
public class GenesisRecoveryTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void TextFieldsPreserveConfiguredFactors()
|
||||
{
|
||||
double[] factors; string error;
|
||||
Assert.IsTrue(GenesisCalibrationFactors.TryParse("17969", "17969", "17969", out factors, out error));
|
||||
CollectionAssert.AreEqual(new double[] { 17969, 17969, 17969 }, factors);
|
||||
Assert.IsTrue(GenesisCalibrationFactors.TryParse(" 17969 ", "18000", "19000", out factors, out error));
|
||||
CollectionAssert.AreEqual(new double[] { 17969, 18000, 19000 }, factors);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IncompleteOrInvalidFactorsAreRejectedBeforeWriting()
|
||||
{
|
||||
foreach (var invalid in new[] { null, "", "0", "-1", "65536", "NaN", "1.5", "bad" })
|
||||
{
|
||||
double[] factors; string error;
|
||||
Assert.IsFalse(GenesisCalibrationFactors.TryParse("17969", invalid, "17969", out factors, out error));
|
||||
Assert.IsNull(factors);
|
||||
StringAssert.Contains(error, "Text2");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void EmptyLegacyConfigurationKeepsDefault()
|
||||
{
|
||||
double[] factors; string error;
|
||||
Assert.IsTrue(GenesisCalibrationFactors.TryParse(null, " ", "", out factors, out error));
|
||||
CollectionAssert.AreEqual(new double[] { 15625, 15625, 15625 }, factors);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MultipleMetersHaveIndependentStableChannelRecords()
|
||||
{
|
||||
var test = new TestRslt();
|
||||
var first = new WaterMeter { WMPosition = 1 };
|
||||
var second = new WaterMeter { WMPosition = 2 };
|
||||
var a = test.GetCalibrationFactors(first);
|
||||
var b = test.GetCalibrationFactors(second);
|
||||
a[0].BaseCalibFactor = 17969;
|
||||
b[0].BaseCalibFactor = 19000;
|
||||
Assert.AreEqual(6, test.CalibFactorResultsToSave.Count);
|
||||
Assert.AreSame(a[0], test.GetCalibrationFactors(first)[0]);
|
||||
Assert.AreEqual(17969, a[0].BaseCalibFactor);
|
||||
Assert.AreEqual(19000, b[0].BaseCalibFactor);
|
||||
for (int i = 0; i < 3; i++) Assert.AreEqual(i + 1, b[i].CalibFactorIndex);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GenesisFactoryAndProcedureActivitiesAreAvailable()
|
||||
{
|
||||
var factory = TBF.Rig.TbfComponents.CmpntFactoryFromClassName("TestMethods.GenesisCommunication");
|
||||
Assert.IsNotNull(factory);
|
||||
var config = factory.DefaultConfig() as TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg;
|
||||
Assert.IsNotNull(config);
|
||||
Assert.AreEqual(10, config.NrThreads);
|
||||
var parameters = new TBF.Rig.TestMethods.GenesisCommunication.iPerlCommunicationParams(true);
|
||||
CollectionAssert.Contains(new System.Collections.Generic.List<string>(parameters.ParamValues(0)), "Prepare Q3 Calibration Slot");
|
||||
Assert.IsNotNull(TBF.Rig.TbfComponents.CmpntFactoryFromClassName("TestMethods.iPerlCommunication"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MappedCalibrationRecordsRoundTripForTwoMeters()
|
||||
{
|
||||
string path = Path.Combine(Path.GetTempPath(), "genesis-roundtrip-" + Guid.NewGuid() + ".sqlite");
|
||||
var previousType = Results.DB.DbType;
|
||||
var previousConnection = Results.DB.ConnectionString;
|
||||
var previousFactory = Results.DB.SessionFactory;
|
||||
try
|
||||
{
|
||||
Results.DB.DbType = Common.DBType.SQLite;
|
||||
Results.DB.ConnectionString = path;
|
||||
using (var factory = Results.DB.CreateSessionFactory(true))
|
||||
{
|
||||
int testId;
|
||||
using (var session = factory.OpenSession())
|
||||
using (var transaction = session.BeginTransaction())
|
||||
{
|
||||
var test = new TestRslt();
|
||||
session.Save(test);
|
||||
testId = test.Id;
|
||||
for (int meter = 1; meter <= 2; meter++)
|
||||
foreach (var factor in test.GetCalibrationFactors(new WaterMeter { WMPosition = meter }))
|
||||
{
|
||||
factor.BaseCalibFactor = 17969 + meter;
|
||||
factor.CalculatedCalibFactor = 18000 + meter;
|
||||
factor.Stored = true;
|
||||
factor.IsCalibFactorValid = true;
|
||||
session.Save(factor);
|
||||
}
|
||||
transaction.Commit();
|
||||
}
|
||||
DatabaseMigrationHelper.EnsureSchema(Common.DBType.SQLite, path);
|
||||
using (var session = factory.OpenSession())
|
||||
{
|
||||
var rows = TestRsltCalibFactorHelper.GetByTestRsltId(session, testId);
|
||||
Assert.AreEqual(6, rows.Count);
|
||||
foreach (var row in rows)
|
||||
{
|
||||
Assert.AreEqual(17969 + row.WaterMeterPosition, row.BaseCalibFactor);
|
||||
Assert.IsTrue(row.Stored);
|
||||
Assert.IsTrue(row.IsCalibFactorValid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Results.DB.DbType = previousType;
|
||||
Results.DB.ConnectionString = previousConnection;
|
||||
Results.DB.SessionFactory = previousFactory;
|
||||
SQLiteConnection.ClearAllPools();
|
||||
if (File.Exists(path)) File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
// This test creates a temporary schema at runtime; no IDE data source exists.
|
||||
// ReSharper disable SqlResolve
|
||||
[TestMethod]
|
||||
public void SQLiteEnsureCreatesAndUpgradesWithoutLosingExistingData()
|
||||
{
|
||||
string path = Path.Combine(Path.GetTempPath(), "genesis-migration-" + Guid.NewGuid() + ".sqlite");
|
||||
try
|
||||
{
|
||||
using (var connection = new SQLiteConnection("Data Source=" + path))
|
||||
{
|
||||
connection.Open();
|
||||
Execute(connection, "CREATE TABLE WaterMeterData(Id INTEGER PRIMARY KEY)");
|
||||
Execute(connection, "CREATE TABLE WaterMeter(Id INTEGER PRIMARY KEY, CalibFactorNominal REAL NOT NULL DEFAULT 4096)");
|
||||
Execute(connection, "CREATE TABLE MeterTestRslt(Id INTEGER PRIMARY KEY, FlipMode INT NULL)");
|
||||
Execute(connection, "INSERT INTO WaterMeter(Id, CalibFactorNominal) VALUES(1, 17969)");
|
||||
Execute(connection, "INSERT INTO MeterTestRslt(Id, FlipMode) VALUES(1, 7)");
|
||||
// Simulate a partially migrated database from a previous special build.
|
||||
Execute(connection, "CREATE TABLE TestRsltCalibFactor(Id INTEGER PRIMARY KEY, TestRsltId INT)");
|
||||
Execute(connection, "INSERT INTO TestRsltCalibFactor(Id, TestRsltId) VALUES(1, 12)");
|
||||
}
|
||||
DatabaseMigrationHelper.EnsureSchema(Common.DBType.SQLite, path);
|
||||
DatabaseMigrationHelper.EnsureSchema(Common.DBType.SQLite, path);
|
||||
using (var connection = new SQLiteConnection("Data Source=" + path))
|
||||
{
|
||||
connection.Open();
|
||||
Assert.AreEqual(17969d, Convert.ToDouble(Scalar(connection, "SELECT CalibFactorNominal FROM WaterMeter WHERE Id=1")));
|
||||
Assert.AreEqual(7L, Convert.ToInt64(Scalar(connection, "SELECT FlipMode FROM MeterTestRslt WHERE Id=1")));
|
||||
Assert.AreEqual(0L, Convert.ToInt64(Scalar(connection, "SELECT Q3Channel FROM WaterMeter WHERE Id=1")));
|
||||
Assert.AreEqual(12L, Convert.ToInt64(Scalar(connection, "SELECT TestRsltId FROM TestRsltCalibFactor WHERE Id=1")));
|
||||
Execute(connection, "UPDATE TestRsltCalibFactor SET WaterMeterPosition=2, CalibFactorIndex=3, BaseCalibFactor=17969, IsCalibFactorValid=1, Stored=1 WHERE Id=1");
|
||||
Assert.AreEqual(17969L, Convert.ToInt64(Scalar(connection, "SELECT BaseCalibFactor FROM TestRsltCalibFactor WHERE WaterMeterPosition=2 AND CalibFactorIndex=3")));
|
||||
Assert.AreEqual(0L, Convert.ToInt64(Scalar(connection, "SELECT COUNT(*) FROM MeterTestCalibFactorRslt")));
|
||||
}
|
||||
}
|
||||
finally { SQLiteConnection.ClearAllPools(); if (File.Exists(path)) File.Delete(path); }
|
||||
}
|
||||
|
||||
// ReSharper restore SqlResolve
|
||||
|
||||
[TestMethod]
|
||||
public void GenesisPropertiesLoadUpdateAndPreserveLegacySettings()
|
||||
{
|
||||
var cfg = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg(new TBF.Rig.TestMethods.GenesisCommunication.Factory());
|
||||
cfg.Name = "Genesis test";
|
||||
cfg.DfltQ2c_15_rl = 17969;
|
||||
cfg.UseWebService = true;
|
||||
cfg.BaseUrl = "legacy-base";
|
||||
cfg.RelativeUrl = "legacy-path";
|
||||
// Exercise the existing XML contract before opening Properties.
|
||||
using (var writer = new StringWriter())
|
||||
{
|
||||
TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg.Serializer.Serialize(writer, cfg);
|
||||
using (var reader = new StringReader(writer.ToString()))
|
||||
cfg = (TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg)TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg.Serializer.Deserialize(reader);
|
||||
}
|
||||
// Name is stored in the component entity, outside the XML payload.
|
||||
cfg.Name = "Genesis test";
|
||||
using (var ctrl = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfgCtrl())
|
||||
{
|
||||
ctrl.Config = cfg;
|
||||
Assert.AreSame(cfg, ctrl.Config);
|
||||
Assert.AreEqual("Genesis test", ctrl.Controls.Find("nameTextBox", true)[0].Text);
|
||||
Assert.AreEqual("1800", ctrl.Controls.Find("commTimeoutTextBox", true)[0].Text);
|
||||
string message = "";
|
||||
Assert.AreEqual(Common.CfgUpdateFlags.None, ctrl.VerifyCfg(ref message), message);
|
||||
ctrl.Unlock();
|
||||
ctrl.Controls.Find("commTimeoutTextBox", true)[0].Text = "2300";
|
||||
bool notified = false;
|
||||
EventHandler<TBF.Rig.CfgChangeArgs> handler = (sender, args) => { notified = true; };
|
||||
TBF.Rig.TestMethods.GenesisCommunication.TestMethod.CfgChangeHandler += handler;
|
||||
try { ctrl.UpdateCfg(); }
|
||||
finally { TBF.Rig.TestMethods.GenesisCommunication.TestMethod.CfgChangeHandler -= handler; }
|
||||
Assert.IsTrue(notified);
|
||||
Assert.AreEqual(2300, cfg.CommTimeout);
|
||||
Assert.AreEqual(17969, cfg.DfltQ2c_15_rl);
|
||||
Assert.IsTrue(cfg.UseWebService);
|
||||
Assert.AreEqual("legacy-base", cfg.BaseUrl);
|
||||
Assert.AreEqual("legacy-path", cfg.RelativeUrl);
|
||||
ctrl.Controls.Find("iperlCheckErrorsToStopTextBox", true)[0].Text = "41";
|
||||
ctrl.Controls.Find("commTimeoutTextBox", true)[0].Text = "2400";
|
||||
Assert.AreNotEqual(0, (int)(ctrl.UpdateCfg() & Common.CfgUpdateFlags.Error));
|
||||
Assert.AreEqual(2300, cfg.CommTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GenesisPropertiesValidateAllNumericBoundariesAndExposeTooltips()
|
||||
{
|
||||
var cfg = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg(new TBF.Rig.TestMethods.GenesisCommunication.Factory());
|
||||
using (var ctrl = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfgCtrl())
|
||||
{
|
||||
ctrl.Config = cfg;
|
||||
ctrl.Unlock();
|
||||
var tips = (System.Windows.Forms.ToolTip)typeof(TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfgCtrl)
|
||||
.GetField("settingsToolTip", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(ctrl);
|
||||
var names = new[] { "commTimeoutTextBox", "maxCommRetriesTextBox", "delayBetweenRetriesTextBox", "nrThreadsTextBox", "iperlCheckErrorsToStopTextBox" };
|
||||
var minima = new[] { 500, 1, 0, 1, 1 };
|
||||
var maxima = new[] { 5000, 10, 5000, 10, 40 };
|
||||
for (int i = 0; i < names.Length; i++)
|
||||
{
|
||||
var field = ctrl.Controls.Find(names[i], true)[0];
|
||||
string original = field.Text;
|
||||
Assert.IsFalse(string.IsNullOrWhiteSpace(tips.GetToolTip(field)));
|
||||
Assert.IsTrue(tips.GetToolTip(field).Split('\n').Length >= 6);
|
||||
foreach (var value in new[] { minima[i], maxima[i] })
|
||||
{
|
||||
field.Text = value.ToString();
|
||||
string message = "";
|
||||
Assert.AreEqual(Common.CfgUpdateFlags.None, ctrl.VerifyCfg(ref message), names[i] + message);
|
||||
}
|
||||
foreach (var value in new[] { (minima[i] - 1).ToString(), (maxima[i] + 1).ToString(), "bad", "", "1.5" })
|
||||
{
|
||||
field.Text = value;
|
||||
string message = "";
|
||||
Assert.AreNotEqual(0, (int)(ctrl.VerifyCfg(ref message) & Common.CfgUpdateFlags.Error), names[i] + ":" + value);
|
||||
}
|
||||
field.Text = original;
|
||||
}
|
||||
ctrl.Controls.Find("commTimeoutTextBox", true)[0].Text = "2500";
|
||||
ctrl.Controls.Find("maxCommRetriesTextBox", true)[0].Text = "6";
|
||||
ctrl.Controls.Find("delayBetweenRetriesTextBox", true)[0].Text = "300";
|
||||
ctrl.Controls.Find("nrThreadsTextBox", true)[0].Text = "3";
|
||||
ctrl.Controls.Find("iperlCheckErrorsToStopTextBox", true)[0].Text = "2";
|
||||
var flags = ctrl.UpdateCfg();
|
||||
Assert.AreNotEqual(0, (int)(flags & Common.CfgUpdateFlags.RestartRqrd));
|
||||
Assert.AreEqual(2500, cfg.CommTimeout);
|
||||
Assert.AreEqual(6, cfg.MaxCommRetries);
|
||||
Assert.AreEqual(300, cfg.DelayBetweenRetries);
|
||||
Assert.AreEqual(3, cfg.NrThreads);
|
||||
Assert.AreEqual(2, cfg.IperlCheckErrorsToStop);
|
||||
StringAssert.Contains(tips.GetToolTip(ctrl.Controls.Find("commTimeoutTextBox", true)[0]), "Genesis");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GenesisRuntimeReceivesLiveSettingsButKeepsRestartOnlyThreadCount()
|
||||
{
|
||||
var factory = new TBF.Rig.TestMethods.GenesisCommunication.Factory();
|
||||
var runtimeCfg = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg(factory);
|
||||
var editedCfg = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg(factory);
|
||||
var component = new TBF.Rig.TestMethods.GenesisCommunication.TestMethod(runtimeCfg);
|
||||
var eventField = typeof(TBF.Rig.TestMethods.GenesisCommunication.TestMethod).GetField("CfgChangeHandler",
|
||||
System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
|
||||
var originalHandlers = eventField.GetValue(null);
|
||||
try
|
||||
{
|
||||
component.StartChangeHandler();
|
||||
using (var ctrl = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfgCtrl())
|
||||
{
|
||||
ctrl.Config = editedCfg;
|
||||
ctrl.Unlock();
|
||||
ctrl.Controls.Find("commTimeoutTextBox", true)[0].Text = "2500";
|
||||
ctrl.Controls.Find("maxCommRetriesTextBox", true)[0].Text = "6";
|
||||
ctrl.Controls.Find("delayBetweenRetriesTextBox", true)[0].Text = "300";
|
||||
ctrl.Controls.Find("iperlCheckErrorsToStopTextBox", true)[0].Text = "2";
|
||||
ctrl.Controls.Find("nrThreadsTextBox", true)[0].Text = "3";
|
||||
ctrl.UpdateCfg();
|
||||
Assert.AreEqual(2500, runtimeCfg.CommTimeout);
|
||||
Assert.AreEqual(6, runtimeCfg.MaxCommRetries);
|
||||
Assert.AreEqual(300, runtimeCfg.DelayBetweenRetries);
|
||||
Assert.AreEqual(2, runtimeCfg.IperlCheckErrorsToStop);
|
||||
Assert.AreEqual(10, runtimeCfg.NrThreads);
|
||||
Assert.AreEqual(3, editedCfg.NrThreads);
|
||||
}
|
||||
}
|
||||
finally { eventField.SetValue(null, originalHandlers); }
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GenesisTooltipsFollowUiCultureAndFallbackToEnglish()
|
||||
{
|
||||
var original = System.Threading.Thread.CurrentThread.CurrentUICulture;
|
||||
try
|
||||
{
|
||||
var cultures = new[] { "en-US", "sk-SK", "cs-CZ", "de-DE", "fr-FR" };
|
||||
var headings = new[] { "Default:", "Prednastaven\u00e1 hodnota:", "V\u00fdchoz\u00ed hodnota:", "Standardwert:", "Default:" };
|
||||
for (int i = 0; i < cultures.Length; i++)
|
||||
{
|
||||
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(cultures[i]);
|
||||
using (var ctrl = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfgCtrl())
|
||||
{
|
||||
var tips = (System.Windows.Forms.ToolTip)typeof(TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfgCtrl)
|
||||
.GetField("settingsToolTip", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(ctrl);
|
||||
foreach (var field in new[] { "nameTextBox", "commTimeoutTextBox", "maxCommRetriesTextBox", "delayBetweenRetriesTextBox", "nrThreadsTextBox", "iperlCheckErrorsToStopTextBox" })
|
||||
StringAssert.Contains(tips.GetToolTip(ctrl.Controls.Find(field, true)[0]), headings[i], cultures[i] + field);
|
||||
var timeout = tips.GetToolTip(ctrl.Controls.Find("commTimeoutTextBox", true)[0]);
|
||||
StringAssert.Contains(timeout, "500\u20135000 ms");
|
||||
StringAssert.Contains(timeout, "1800 ms");
|
||||
Assert.AreEqual(timeout, tips.GetToolTip(ctrl.Controls.Find("commTimeoutLabel", true)[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
finally { System.Threading.Thread.CurrentThread.CurrentUICulture = original; }
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async System.Threading.Tasks.Task GenesisTimeoutChangesCancellationDeadlineAndPreservesLegacyCallers()
|
||||
{
|
||||
Assert.IsNull(TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.CreateDeadline(System.Threading.CancellationToken.None, "legacy"));
|
||||
using (TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.Begin(5000))
|
||||
using (var longDeadline = TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.CreateDeadline(System.Threading.CancellationToken.None, "long-test"))
|
||||
{
|
||||
using (TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.Begin(500))
|
||||
using (var shortDeadline = TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.CreateDeadline(System.Threading.CancellationToken.None, "short-test"))
|
||||
{
|
||||
try
|
||||
{
|
||||
await System.Threading.Tasks.Task.Delay(3000, shortDeadline.Token);
|
||||
Assert.Fail("Expected the configured 500 ms deadline to cancel the operation.");
|
||||
}
|
||||
catch (System.OperationCanceledException) { Assert.IsTrue(shortDeadline.IsCancellationRequested); }
|
||||
Assert.IsFalse(longDeadline.IsCancellationRequested);
|
||||
}
|
||||
using (var restored = TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.CreateDeadline(System.Threading.CancellationToken.None, "restored-test"))
|
||||
{
|
||||
await System.Threading.Tasks.Task.Delay(700);
|
||||
Assert.IsFalse(restored.IsCancellationRequested);
|
||||
}
|
||||
using (var caller = new System.Threading.CancellationTokenSource())
|
||||
using (var linked = TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.CreateDeadline(caller.Token, "caller-test"))
|
||||
{
|
||||
caller.Cancel();
|
||||
Assert.IsTrue(linked.IsCancellationRequested);
|
||||
}
|
||||
}
|
||||
Assert.IsNull(TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.CreateDeadline(System.Threading.CancellationToken.None, "legacy-after"));
|
||||
}
|
||||
|
||||
private static void Execute(SQLiteConnection connection, string sql)
|
||||
{
|
||||
using (var command = connection.CreateCommand()) { command.CommandText = sql; command.ExecuteNonQuery(); }
|
||||
}
|
||||
|
||||
private static object Scalar(SQLiteConnection connection, string sql)
|
||||
{
|
||||
using (var command = connection.CreateCommand()) { command.CommandText = sql; return command.ExecuteScalar(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Results.Entities;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.iPerlHead;
|
||||
|
||||
namespace TBFTests.Rig.TestMethods.iPerlCommunication.iPerlHead
|
||||
{
|
||||
[TestClass]
|
||||
public class IperlHeadCalibrationTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void CalculateCalibFactorPercentage_UsesProtocolNominalFactor()
|
||||
{
|
||||
Assert.AreEqual(100.0,
|
||||
IperlHead.CalculateCalibFactorPercentage(4096), 0.000000001);
|
||||
Assert.AreEqual(93.310546875,
|
||||
IperlHead.CalculateCalibFactorPercentage(3822), 0.000000001);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CalculateCalibFactorPercentage_UsesConfiguredNominalFactor()
|
||||
{
|
||||
Assert.AreEqual(76.44,
|
||||
IperlHead.CalculateCalibFactorPercentage(3822, 5000.0), 0.000000001);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WaterMeterCalibFactorPercentage_UsesSavedNominalFactor()
|
||||
{
|
||||
var waterMeter = new WaterMeter
|
||||
{
|
||||
CalibFactor = 3822,
|
||||
CalibFactorNominal = 5000.0
|
||||
};
|
||||
|
||||
Assert.AreEqual(76.44, waterMeter.CalibFactorPercentage, 0.000000001);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void WaterMeterCalibFactorCorrectionPercentage_IsPercentageMinusOneHundred()
|
||||
{
|
||||
var waterMeter = new WaterMeter
|
||||
{
|
||||
CalibFactor = 3822,
|
||||
CalibFactorNominal = 4096.0
|
||||
};
|
||||
|
||||
Assert.AreEqual(-6.689453125, waterMeter.CalibFactorCorrectionPercentage, 0.000000001);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.props')" />
|
||||
<PropertyGroup>
|
||||
@@ -63,6 +63,13 @@
|
||||
<Reference Include="Moq, Version=4.20.70.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Moq.4.20.70\lib\net462\Moq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NHibernate">
|
||||
<HintPath>..\packages\NHibernate.4.0.4.4000\lib\net40\NHibernate.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Data" />
|
||||
<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" />
|
||||
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
|
||||
@@ -155,6 +162,7 @@
|
||||
<Compile Include="Rig\RegisterReaders\AllyReader\integration\AllyHardwareIntegrationTest.cs" />
|
||||
<Compile Include="Rig\TestMethods\AllyCalibration\TestMethodConfigTest.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\common\OptoTelegramRawTest.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\iPerlHead\IperlHeadCalibrationTest.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\FakeSerialDriver.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\IperlResponseFactory.cs" />
|
||||
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\RadioServiceTest.cs" />
|
||||
@@ -206,10 +214,12 @@
|
||||
</Choose>
|
||||
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<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'))" />
|
||||
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.props'))" />
|
||||
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.2.2.10\build\net46\MSTest.TestAdapter.targets'))" />
|
||||
</Target>
|
||||
@@ -221,4 +231,11 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
<ItemGroup><Compile Include="GenesisRecoveryTests.cs" /><Compile Include="GenesisParallelSchedulingTests.cs" /><Compile Include="Rig\RegisterReaders\GenesisRegReader\GenesisReadDataRegressionTests.cs" /></ItemGroup>
|
||||
<!-- Project dependencies may copy an older SQLite interop DLL with a newer timestamp. -->
|
||||
<Target Name="EnsureMatchingSQLiteInterop" AfterTargets="Build">
|
||||
<Copy SourceFiles="@(SQLiteInteropFiles)"
|
||||
DestinationFiles="@(SQLiteInteropFiles -> '$(OutDir)%(RecursiveDir)%(Filename)%(Extension)')"
|
||||
SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Castle.Core" version="5.1.1" targetFramework="net472" />
|
||||
<package id="JetBrains.Annotations" version="2023.3.0" targetFramework="net472" />
|
||||
@@ -14,4 +14,7 @@
|
||||
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
|
||||
<package id="System.ValueTuple" version="4.5.0" targetFramework="net472" />
|
||||
<package id="log4net" version="2.0.15" targetFramework="net472" />
|
||||
<package id="NHibernate" version="4.0.4.4000" targetFramework="net48" />
|
||||
<package id="Stub.System.Data.SQLite.Core.NetFramework" version="1.0.119.0" targetFramework="net48" />
|
||||
<package id="System.Data.SQLite.Core" version="1.0.119.0" targetFramework="net48" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user