Compare commits

...
Author SHA1 Message Date
michal b8f6af7112 Fix point 4. - take % validation from procedure to Q3 Calib factor CH1-3 verification
Implement error limit configuration for Q3 calibration factors.

- Add default and configurable error limits for Q3 calibration validation.
- Introduce `CalculateQ3CalibrationWithErrorLimits` for flexible validation handling.
- Overhaul Q3 workflow to respect configured limits, with fallback to defaults.
- Extend tests for calibration, including boundary and invalid inputs.
2026-09-16 10:23:04 +02:00
michal 684513ee59 Fix Genesis scheduling and synchronization for up to 10 workers 2026-09-10 22:09:30 +02:00
michal 52bd0d01ce Restore Genesis communication and Q3 calibration from special branches
A – Registration and execution
- Register GenesisCommunication Factory in the component list.
- Separate the Genesis form and sequence from iPerl communication.

B – Communication activities
- Restore initialization, connection, PCB reading, password and login.
- Include grouped login, mode switching and disconnection.
- Support processing up to 10 slots.

C1 – Input calibration factors
- Read three factors from Water meters / Text1–Text3.
- Validate integer values in the range 1–65535.
- Preserve the default of 15625 when all three fields are empty.

D – Q3 calibration
- Connect the Prepare Q3 → measurement → Write Q3 workflow.
- Add channel processing to FlyingStart and FlyingStartMassCollection.
- Reset previous measurement data and validate calculated factors.
- Mark factors as stored only after StoreCalibration succeeds.

E – Results and database
- Store calibration factors separately for each meter and channel.
- Add result entities, mappings and Q3 data.
- Extend DB.cs / EnsureSchema to create and update the schema.
- Preserve compatibility with the existing binary format.

Validation:
- Debug build and 18 tests passed.
- Simulated communication runs follow the same activity sequence.
- The complete Q3 workflow has not yet been verified on hardware.

Known limitation:
- An inherited mismatch in simulated responses and error propagation
  can produce an incorrect OK result; this change does not fix it.
2026-09-09 15:04:49 +02:00
michal 88b8bc5534 fix precision correct factor (Mexico), added diff, correction in percentage in Result Configuration 2026-09-07 14:07:07 +02:00
marekf d3c8813012 <Fix>: Correct test-dependent result evaluation in ResultsWriter, increase revision to 3.9.3145.101
Cause:

- ResultsWriter evaluated selected test-dependent items without the actual TestID.
- This caused values such as Test passed(), timestamps, flow, pressure, temperature, conductivity and error data to be empty or incorrect.

Solution:

1. Fixed test-aware result evaluation
   - Uses published regular meter test results.
   - Passes mtr.Name() as TestID to item.Print(wm, testId).

2. Restored correct test result mapping
   - Test-dependent values are now resolved from the correct TestRslt / MeterTestRslt.

3. Increased revision
   - Updated revision to 3.9.3145.101.
2026-09-03 10:00:40 +02:00
marekf 054075a341 <Feat>: Add XML result generation with file and MSSQL output support, increase revision to 3.9.3145.100
Cause:

- ResultsWriter required a generic way to generate customer-specific XML result data from TBF measurement results.
- The customer reference XML contains example runtime values and repeated result structures, so it cannot be used directly as the generated output.
- TBF result variables must be explicitly mapped to destinations in the customer XML structure.
- The generated XML result data must support two output targets:
  - direct creation of an XML file,
  - delivery of the XML payload to a Microsoft SQL stored procedure.
- Preview generation must allow the XML structure and configured mappings to be verified without executing the production database write.
- Increase revision to 3.9.3145.100.

Solution:

1. Added XML reference analysis
   - Creates a clean base XML structure.
   - Extracts the repeating result prototype.
   - Prevents sample runtime values from the customer reference XML from leaking into generated results.
2. Added configurable TBF-to-XML result mapping
   - Allows explicit mapping of TBF result variables to customer XML destinations.
   - Supports one-time and repeating XML destinations.
   - Keeps the mapping independent of the semantic meaning of customer XML attribute names.
3. Added XML destination viewer and configurator
   - Shows the current mapping.
   - Highlights repeating destinations.
   - Identifies already used one-time destinations.
4. Added runtime XML result generation
   - Uses the configured TBF-to-XML mappings.
   - Uses measurement procedure result data.
   - Builds the output from the clean base XML and repeating XML prototype.
   - Generates repeated result records according to the executed measurement procedure.
5. Added simulation-based Preview request
   - Generates a complete XML payload using simulation values.
   - Allows XML structure and mapping verification before production execution.
   - Does not execute the production stored procedure.
6. Added direct XML file output support to UniDataStorageWriter
   - Supports File / .xml as a physical output target.
   - Creates the generated XML result file in the configured output directory.
7. Added Microsoft SQL stored-procedure XML output
   - Supports Microsoft SQL / StoredProcedure as a physical output target.
   - Passes the generated XML payload through the configured stored procedure parameter.
8. Added optional XML payload archiving
   - Allows generated XML payloads to be stored in the configured Payload archive.
   - Can be used together with the Microsoft SQL stored-procedure output.
9. Kept ResultsWriter independent of the physical output target
   - ResultsWriter generates the result payload.
   - UniDataStorageWriter decides how and where the payload is physically written.
   - The same ResultsWriter XML generation mechanism is therefore used for both XML file and MSSQL outputs.
10. Increased revision
   - Updated revision to 3.9.3145.100.
2026-09-02 12:05:10 +02:00
marekf a0d53f9ec9 <Fix>: RefPulses carry-over by restoring testInProgress behavior in StandingStart, increase revision to 3.9.3145
Cause:
1. Background: Restore/ensure testInProgress behavior before the DataEntry sequence and explicitly prevent to reset the pulse.
2. Increase revision to 3.9.3145

Solution:
1. Deleted .AddOperation(testInProgress) calling
2. Increased revision to 3.9.3145
2026-08-25 13:49:49 +02:00
michal dfa8693be9 Added table columns TestRslt -> ConductMean, .... 2026-08-25 10:38:08 +02:00
marekf 1e45f48c75 <Update>: Version to 3.9.3144.0 in AssemblyInfo files 2026-08-25 08:23:27 +02:00
marekf d833eca584 <Merge_Fix>: System.Data.SQLite, System.Data.SQLite.Core, Stub.System.Data.SQLite.Core.NetFramework 2026-08-24 15:36:50 +02:00
97 changed files with 21608 additions and 1558 deletions
+9
View File
@@ -189,6 +189,15 @@ namespace Results
return null; 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> /// <summary>
/// Returns true when all tests were done /// Returns true when all tests were done
+61
View File
@@ -273,8 +273,16 @@ namespace Results
log.Debug(tstRslt.ToString(1)); log.Debug(tstRslt.ToString(1));
} }
// Save batch, TestRslt, WaterMeter, MeterTestRslt, etc.
session.SaveOrUpdate(batch); session.SaveOrUpdate(batch);
// Important: after this, TestRslt.Id should be generated
session.Flush();
// Optional table support
SolveSaveCalibFactors(batch, session);
//Commit - store results
transaction.Commit(); transaction.Commit();
} }
catch (Exception exc) catch (Exception exc)
@@ -295,6 +303,51 @@ namespace Results
return true; 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) public static Batch LoadBatch(int batchNr)
{ {
@@ -321,6 +374,14 @@ namespace Results
batch.WaterMeters = session.QueryOver<WaterMeter>() batch.WaterMeters = session.QueryOver<WaterMeter>()
.Where(x => (x.Batch.Id == batch.Id)) .Where(x => (x.Batch.Id == batch.Id))
.List(); .List();
foreach (var tstRslt in batch.TestRslts)
{
tstRslt.CalibFactorResultsToSave = TestRsltCalibFactorHelper.GetByTestRsltId( session, tstRslt.Id);
}
} }
return (batches.Count > 0) ? batches[0] : null; 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;
}
}
}
+6 -1
View File
@@ -59,7 +59,11 @@ namespace Results.Entities
#endif #endif
public virtual WaterMeter WaterMeter { get; set; } /// reference to the WaterMeter entity 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 /// Wrappers
public virtual string Name() { return TestRslt.Name(); } public virtual string Name() { return TestRslt.Name(); }
@@ -99,6 +103,7 @@ namespace Results.Entities
public virtual WaterMeterData WaterMeterData() { return WaterMeter.WaterMeterData; } public virtual WaterMeterData WaterMeterData() { return WaterMeter.WaterMeterData; }
public virtual Batch Batch() { return WaterMeter.Batch; } public virtual Batch Batch() { return WaterMeter.Batch; }
public virtual bool IsPilotRslt() public virtual bool IsPilotRslt()
{ {
return (CompoundMeterId == (byte)Common.CompoundMeterId.Single) || return (CompoundMeterId == (byte)Common.CompoundMeterId.Single) ||
+44 -1
View File
@@ -11,6 +11,32 @@ namespace Results.Entities
{ {
public class TestRslt 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 /// Identity
public virtual int Id { get; protected set; } public virtual int Id { get; protected set; }
public virtual Batch Batch { get; set; } public virtual Batch Batch { get; set; }
@@ -152,6 +178,11 @@ namespace Results.Entities
public virtual int Counter4 { get; set; } public virtual int Counter4 { get; set; }
public virtual int Counter5 { 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 /// Wrappers
public virtual string Name() { return Common.Utils.GetTestName(TestData.Name, TestData.Repeats, RepetitionNr); } 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 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; MethodClass = string.Empty;
Remark = 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() : this()
{ {
Batch = batch; Batch = batch;
TestData = testData; TestData = testData;
Part = part; Part = part;
RepetitionNr = repetitionNr; RepetitionNr = repetitionNr;
CalibFactorResultsToSave = calibFactor != null ? new List<TestRsltCalibFactor>(calibFactor) : new List<TestRsltCalibFactor>();
} }
public virtual void CopyContentFrom(TestRslt src) public virtual void CopyContentFrom(TestRslt src)
+39
View File
@@ -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;
}
}
}
+22 -2
View File
@@ -63,6 +63,19 @@ namespace Results.Entities
#if IPERL #if IPERL
public virtual double OrigCalibFactor { get; set; } /// Original iPerl calibration factor used during the test 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 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 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 CalibFactorLNA { get; set; } /// iPerl LNA calibration factor used during the test
public virtual double Q2ErrWOCorrection { get; set; } 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 LastRecordIsNok { get; set; } /// Not mapped to DB !!! Previous record verification result
public virtual bool PrintLabel { get; set; } /// Not mapped to DB !!! 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 WaterMeterData WaterMeterData { get; set; }
public virtual Batch Batch { get; set; } public virtual Batch Batch { get; set; }
public virtual IList<MeterTestRslt> MeterTestRslts { get; set; } public virtual IList<MeterTestRslt> MeterTestRslts { get; set; }
@@ -493,6 +509,7 @@ namespace Results.Entities
FWVersion = string.Empty; FWVersion = string.Empty;
#endif #endif
PrintLabel = true; PrintLabel = true;
Q3Channel = 0; //No Q3 calibration by default
} }
@@ -519,6 +536,7 @@ namespace Results.Entities
#if IPERL #if IPERL
OrigCalibFactor = src.OrigCalibFactor; OrigCalibFactor = src.OrigCalibFactor;
CalibFactor = src.CalibFactor; CalibFactor = src.CalibFactor;
CalibFactorNominal = src.CalibFactorNominal;
OrigCalibFactorLNA = src.OrigCalibFactorLNA; OrigCalibFactorLNA = src.OrigCalibFactorLNA;
CalibFactorLNA = src.CalibFactorLNA; CalibFactorLNA = src.CalibFactorLNA;
Q2ErrWOCorrection = src.Q2ErrWOCorrection; Q2ErrWOCorrection = src.Q2ErrWOCorrection;
@@ -556,6 +574,7 @@ namespace Results.Entities
Workflow = src.Workflow; /// Not mapped to DB Workflow = src.Workflow; /// Not mapped to DB
LastRecordIsNok = src.LastRecordIsNok; /// Not mapped to DB LastRecordIsNok = src.LastRecordIsNok; /// Not mapped to DB
PrintLabel = src.PrintLabel; /// Not mapped to DB PrintLabel = src.PrintLabel; /// Not mapped to DB
Q3Channel = src.Q3Channel; /// Mapped to DB
foreach (var mtr in MeterTestRslts) foreach (var mtr in MeterTestRslts)
{ {
@@ -597,9 +616,10 @@ namespace Results.Entities
foreach (var mtr in MeterTestRslts) foreach (var mtr in MeterTestRslts)
{ {
if ((mtr.CompoundMeterId == (byte)CompoundMeterId.Single || mtr.CompoundMeterId == (byte)CompoundMeterId.Compound || mtr.CompoundMeterId == (byte)CompoundMeterId.HeatMeterEnergy) 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.Never)
&& (mtr.Publish() != Publish.Internal)) && (mtr.Publish() != Publish.Internal))))
{ {
testNames.Add(mtr.Name()); testNames.Add(mtr.Name());
} }
+4
View File
@@ -51,6 +51,7 @@ namespace Results.Entities
public virtual bool Compound { get; set; } public virtual bool Compound { get; set; }
public virtual bool HeatMeter { 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 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> /// <summary>
/// Default constructor, safe values /// Default constructor, safe values
@@ -59,6 +60,7 @@ namespace Results.Entities
{ {
PulsesPerLtr = 1; PulsesPerLtr = 1;
PulsesPerLtrAux = 1; PulsesPerLtrAux = 1;
Q3Channel = 0;
} }
/// <summary> /// <summary>
@@ -101,6 +103,7 @@ namespace Results.Entities
Compound = oriWMData.Compound; Compound = oriWMData.Compound;
HeatMeter = oriWMData.HeatMeter; HeatMeter = oriWMData.HeatMeter;
WMTypeId = oriWMData.WMTypeId; WMTypeId = oriWMData.WMTypeId;
Q3Channel = oriWMData.Q3Channel;
} }
@@ -146,6 +149,7 @@ namespace Results.Entities
if (Compound != wmd.Compound) return false; if (Compound != wmd.Compound) return false;
if (HeatMeter != wmd.HeatMeter) return false; if (HeatMeter != wmd.HeatMeter) return false;
if (WMTypeId != wmd.WMTypeId) return false; if (WMTypeId != wmd.WMTypeId) return false;
if (Q3Channel != wmd.Q3Channel) return false;
return true; return true;
} }
@@ -29,14 +29,92 @@ namespace Results.Entities.helpers
using (var conn = new MySqlConnection(connectionString)) using (var conn = new MySqlConnection(connectionString))
{ {
conn.Open(); 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. // Genesis Q3 result columns.
// EnsureColumnMySql(conn, "WaterMeterData", "Q3Channel", "INT NOT NULL DEFAULT 0"); EnsureColumnMySql(conn, "WaterMeterData", "Q3Channel", "INT NOT NULL DEFAULT 0");
// EnsureColumnMySql(conn, "WaterMeter", "Q3Channel", "INT NOT NULL DEFAULT 0"); EnsureColumnMySql(conn, "WaterMeter", "Q3Channel", "INT NOT NULL DEFAULT 0");
// EnsureColumnMySql(conn, "MeterTestRslt", "Q3Channel", "INT NOT NULL DEFAULT 0"); EnsureColumnMySql(conn, "MeterTestRslt", "Q3Channel", "INT NOT NULL DEFAULT 0");
EnsureColumnMySql(conn, "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, "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)) using (var conn = new System.Data.SQLite.SQLiteConnection("Data Source=" + databaseFile))
{ {
conn.Open(); 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. // Genesis Q3 result columns.
// EnsureColumnSQLite(conn, "WaterMeterData", "Q3Channel", "INTEGER NOT NULL DEFAULT 0"); EnsureColumnSQLite(conn, "WaterMeterData", "Q3Channel", "INTEGER NOT NULL DEFAULT 0");
// EnsureColumnSQLite(conn, "WaterMeter", "Q3Channel", "INTEGER NOT NULL DEFAULT 0"); EnsureColumnSQLite(conn, "WaterMeter", "Q3Channel", "INTEGER NOT NULL DEFAULT 0");
// EnsureColumnSQLite(conn, "MeterTestRslt", "Q3Channel", "INTEGER NOT NULL DEFAULT 0"); EnsureColumnSQLite(conn, "MeterTestRslt", "Q3Channel", "INTEGER NOT NULL DEFAULT 0");
EnsureColumnSQLite(conn, "MeterTestRslt", "FlipMode", "INTEGER NULL"); EnsureColumnSQLite(conn, "MeterTestRslt", "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();
}
}
}
+1 -1
View File
@@ -69,7 +69,7 @@ namespace Results.Forms
public void Update(Results.Entities.WaterMeter wMtr) 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 /// Water meter position is disabled
this.disabled = true; this.disabled = true;
+6 -3
View File
@@ -81,7 +81,7 @@ namespace Results.Forms
public void Update(Results.Entities.WaterMeter wMtr) 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 /// Water meter position is disabled
this.disabled = true; this.disabled = true;
@@ -114,8 +114,11 @@ namespace Results.Forms
lView.Items.Clear(); lView.Items.Clear();
foreach (var mtr in wMtr.MeterTestRslts) foreach (var mtr in wMtr.MeterTestRslts)
{ {
if (mtr != null && mtr.IsPilotRslt() && mtr.TestDone && mtr.Publish() != Publish.Never if (mtr != null && mtr.IsPilotRslt() &&
&& mtr.Publish() != Publish.Internal) (mtr.Q3Channel!=0 || (mtr.TestDone &&
mtr.Publish() != Publish.Never &&
mtr.Publish() != Publish.Internal)
) )
{ {
ListViewItem lvi = new ListViewItem(testNames[ix++]); ListViewItem lvi = new ListViewItem(testNames[ix++]);
+150 -86
View File
@@ -1,4 +1,8 @@
using System; ///
/// Copyright (c) 2026 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
@@ -11,9 +15,9 @@ namespace Results.Forms
public partial class ResultsConfigCtrl : UserControl public partial class ResultsConfigCtrl : UserControl
{ {
/// <summary> /// <summary>
/// ListViewEx columns /// ListViewEx columns.
/// </summary> /// </summary>
enum Column private enum Column
{ {
Item, Item,
Caption, Caption,
@@ -27,12 +31,48 @@ namespace Results.Forms
Count, Count,
} }
Control[] editors; /// all editors except of units private Control[] editors;
ComboBox unitsCB; /// units combo box private ComboBox unitsCB;
private bool unlocked;
private string captionColumnText;
public MetersKind MetersKind; public MetersKind MetersKind;
public IList<WMeterRsltItemSpec> SelectedItems; public IList<WMeterRsltItemSpec> SelectedItems;
public bool SupressTestIDColumn; public bool SupressTestIDColumn;
/// <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;
}
/// <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 public bool Unlocked
{ {
set set
@@ -48,13 +88,11 @@ namespace Results.Forms
} }
get { return unlocked; } get { return unlocked; }
} }
bool unlocked;
public ResultsConfigCtrl(bool supressTestIDColumn) public ResultsConfigCtrl(bool supressTestIDColumn)
: this() : this()
{ {
this.SupressTestIDColumn = supressTestIDColumn; SupressTestIDColumn = supressTestIDColumn;
} }
public ResultsConfigCtrl() public ResultsConfigCtrl()
@@ -62,7 +100,6 @@ namespace Results.Forms
InitializeComponent(); InitializeComponent();
} }
void Localize() void Localize()
{ {
Text = Strings.Configuration; Text = Strings.Configuration;
@@ -84,21 +121,26 @@ namespace Results.Forms
{ {
Localize(); Localize();
/// Add columns to ListViewEx
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Item, Width = 120 }); 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.Units });
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Format }); selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Format });
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Precision }); selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Precision });
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Width }); selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Width });
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Alignment }); selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Alignment });
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Merge }); selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Merge });
if (!SupressTestIDColumn) if (!SupressTestIDColumn)
{ {
selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Test_ID }); selectedResultsListViewEx.Columns.Add(new ColumnHeader { Text = Strings.Test_ID });
} }
/// Create controls used by ListViewEx to edit items
unitsCB = new ComboBox(); unitsCB = new ComboBox();
var alignmentCB = new ComboBox(); var alignmentCB = new ComboBox();
@@ -114,15 +156,16 @@ namespace Results.Forms
editors = new Control[] editors = new Control[]
{ {
null, null,
new TextBox(), /// caption new TextBox(),
unitsCB, unitsCB,
new TextBox(), /// format new TextBox(),
new TextBox(), /// precision new TextBox(),
new TextBox(), /// width new TextBox(),
alignmentCB, alignmentCB,
mergeCB, mergeCB,
new TextBox(), /// testID new TextBox(),
}; };
foreach (var edi in editors) foreach (var edi in editors)
{ {
if (edi != null) if (edi != null)
@@ -132,8 +175,11 @@ namespace Results.Forms
} }
} }
selectedResultsListViewEx.SubItemClicked += new SubItemEventHandler(selectedResultsListViewEx_SubItemClicked); selectedResultsListViewEx.SubItemClicked +=
selectedResultsListViewEx.SubItemEndEditing += new SubItemEndEditingEventHandler(selectedResultsListViewEx_SubItemEndEditing); new SubItemEventHandler(selectedResultsListViewEx_SubItemClicked);
selectedResultsListViewEx.SubItemEndEditing +=
new SubItemEndEditingEventHandler(selectedResultsListViewEx_SubItemEndEditing);
availableByQuantityTreeView.ShowNodeToolTips = true; availableByQuantityTreeView.ShowNodeToolTips = true;
availableByCategoryTreeView.ShowNodeToolTips = true; availableByCategoryTreeView.ShowNodeToolTips = true;
@@ -145,18 +191,43 @@ namespace Results.Forms
void selectedResultsListViewEx_SubItemClicked(object sender, SubItemEventArgs e) void selectedResultsListViewEx_SubItemClicked(object sender, SubItemEventArgs e)
{ {
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) if (e.SubItem == (int)Column.Units)
{ {
Quantity quantity = (e.Item.Tag as WMeterRsltItemSpec).Quantity; WMeterRsltItemSpec item = e.Item.Tag as WMeterRsltItemSpec;
if (item == null) return;
Quantity quantity = item.Quantity;
unitsCB.Items.Clear(); unitsCB.Items.Clear();
unitsCB.Items.Add(Unit.None.ToDescription()); /// "---" unitsCB.Items.Add(Unit.None.ToDescription());
for (Unit u = (Unit)1; u < Unit.Count; u++) for (Unit u = (Unit)1; u < Unit.Count; u++)
{ {
if (Units.IsQuantity(u, quantity)) unitsCB.Items.Add(u.ToDescription()); if (Units.IsQuantity(u, quantity))
{
unitsCB.Items.Add(u.ToDescription());
} }
}
selectedResultsListViewEx.StartEditing(unitsCB, e.Item, e.SubItem); selectedResultsListViewEx.StartEditing(unitsCB, e.Item, e.SubItem);
} }
else if ((e.SubItem > 0) && (e.SubItem < (int)(SupressTestIDColumn ? Column.TestID : Column.Count))) else if ((e.SubItem > 0) &&
(e.SubItem < (int)(SupressTestIDColumn ? Column.TestID : Column.Count)))
{ {
selectedResultsListViewEx.StartEditing(editors[e.SubItem], e.Item, e.SubItem); selectedResultsListViewEx.StartEditing(editors[e.SubItem], e.Item, e.SubItem);
} }
@@ -169,29 +240,38 @@ namespace Results.Forms
switch ((Column)e.SubItem) switch ((Column)e.SubItem)
{ {
case Column.Caption: item.Caption = e.DisplayText; return; case Column.Caption:
item.Caption = e.DisplayText;
return;
case Column.Units: case Column.Units:
for (Unit u = 0; u < Unit.Count; u++) for (Unit u = 0; u < Unit.Count; u++)
{ {
if (u.ToDescription().Equals(unitsCB.Text)) if (u.ToDescription().Equals(unitsCB.Text))
{ {
item.Units = u; 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.Format: item.Format = e.DisplayText; return;
case Column.Precision: item.Precision = e.DisplayText; return;
case Column.Width: case Column.Width:
{ {
int width; int width;
if (Int32.TryParse(editors[e.SubItem].Text, out width) && width >= 0) if (Int32.TryParse(editors[e.SubItem].Text, out width) && width >= 0)
{ {
item.Width = width; item.Width = width;
return; /// OK return;
} }
break; /// Error break;
} }
case Column.Alignment: case Column.Alignment:
@@ -203,7 +283,7 @@ namespace Results.Forms
return; return;
} }
} }
break; /// Error break;
case Column.Merge: case Column.Merge:
if (editors[e.SubItem].Text == Strings.Yes) if (editors[e.SubItem].Text == Strings.Yes)
@@ -216,23 +296,20 @@ namespace Results.Forms
item.Merge = false; item.Merge = false;
return; return;
} }
break; /// Error break;
case Column.TestID: item.TestID = e.DisplayText; return; case Column.TestID:
item.TestID = e.DisplayText;
return;
default: default:
return; /// OK return;
} }
e.DisplayText = e.Item.SubItems[e.SubItem].Text; e.DisplayText = e.Item.SubItems[e.SubItem].Text;
e.Cancel = true; e.Cancel = true;
return;
} }
/// <summary>
/// Redraw available items (right hand side)
/// </summary>
void RedrawAvailable() void RedrawAvailable()
{ {
RedrawByQuantity(availableByQuantityTreeView); RedrawByQuantity(availableByQuantityTreeView);
@@ -240,14 +317,14 @@ namespace Results.Forms
RedrawInAlphabeticOrder(availableAlphabeticTreeView); RedrawInAlphabeticOrder(availableAlphabeticTreeView);
} }
void RedrawInAlphabeticOrder(TreeView treeView) void RedrawInAlphabeticOrder(TreeView treeView)
{ {
treeView.Nodes.Clear(); treeView.Nodes.Clear();
IList<WMeterRsltItemSpec> alphabeticlList = WMeterRsltItemSpec.AllItems.OrderBy(x => x.Name).ToList(); IList<WMeterRsltItemSpec> alphabeticList =
/// WMeterRsltItemSpec.AllItems.OrderBy(x => x.Name).ToList();
foreach (var item in alphabeticlList)
foreach (var item in alphabeticList)
{ {
TreeNode node = new TreeNode(item.Name); TreeNode node = new TreeNode(item.Name);
node.Tag = item; node.Tag = item;
@@ -256,7 +333,6 @@ namespace Results.Forms
} }
} }
void RedrawByQuantity(TreeView treeView) void RedrawByQuantity(TreeView treeView)
{ {
treeView.Nodes.Clear(); treeView.Nodes.Clear();
@@ -264,11 +340,13 @@ namespace Results.Forms
IList<Quantity> quantities = new List<Quantity>(); IList<Quantity> quantities = new List<Quantity>();
for (Quantity q = 0; q < Quantity.Count; q++) quantities.Add(q); for (Quantity q = 0; q < Quantity.Count; q++) quantities.Add(q);
IList<Quantity> sortedQuantities = quantities.OrderBy(x => x.ToDescription()).ToList(); IList<Quantity> sortedQuantities =
quantities.OrderBy(x => x.ToDescription()).ToList();
foreach (var q in sortedQuantities) foreach (var q in sortedQuantities)
{ {
int n = 0; int n = 0;
foreach (var ri in WMeterRsltItemSpec.AllItems) foreach (var ri in WMeterRsltItemSpec.AllItems)
{ {
if (ri.Quantity == q) n++; if (ri.Quantity == q) n++;
@@ -278,6 +356,7 @@ namespace Results.Forms
{ {
TreeNode[] array = new TreeNode[n]; TreeNode[] array = new TreeNode[n];
int i = 0; int i = 0;
foreach (var ri in WMeterRsltItemSpec.AllItems) foreach (var ri in WMeterRsltItemSpec.AllItems)
{ {
if (ri.Quantity == q) if (ri.Quantity == q)
@@ -294,7 +373,6 @@ namespace Results.Forms
} }
} }
void RedrawByCategory(TreeView treeView) void RedrawByCategory(TreeView treeView)
{ {
treeView.Nodes.Clear(); treeView.Nodes.Clear();
@@ -302,11 +380,13 @@ namespace Results.Forms
IList<ItemCategory> categories = new List<ItemCategory>(); IList<ItemCategory> categories = new List<ItemCategory>();
for (ItemCategory c = 0; c < ItemCategory.Count; c++) categories.Add(c); 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) foreach (var c in sortedCategories)
{ {
int n = 0; int n = 0;
foreach (var ri in WMeterRsltItemSpec.AllItems) foreach (var ri in WMeterRsltItemSpec.AllItems)
{ {
if (ri.Category == c) n++; if (ri.Category == c) n++;
@@ -316,6 +396,7 @@ namespace Results.Forms
{ {
TreeNode[] array = new TreeNode[n]; TreeNode[] array = new TreeNode[n];
int i = 0; int i = 0;
foreach (var ri in WMeterRsltItemSpec.AllItems) foreach (var ri in WMeterRsltItemSpec.AllItems)
{ {
if (ri.Category == c) if (ri.Category == c)
@@ -332,10 +413,6 @@ namespace Results.Forms
} }
} }
/// <summary>
/// Redraw selected items (right hand side)
/// </summary>
void RedrawSelected() void RedrawSelected()
{ {
selectedResultsListViewEx.Items.Clear(); selectedResultsListViewEx.Items.Clear();
@@ -344,18 +421,19 @@ namespace Results.Forms
foreach (var item in SelectedItems) foreach (var item in SelectedItems)
{ {
ListViewItem lvi = new ListViewItem(item.Name); /// Item ListViewItem lvi = new ListViewItem(item.Name);
lvi.Tag = item; lvi.Tag = item;
lvi.SubItems.Add(item.Caption); /// Header lvi.SubItems.Add(item.Caption);
lvi.SubItems.Add(item.Units.ToDescription()); /// Units lvi.SubItems.Add(item.Units.ToDescription());
lvi.SubItems.Add(item.Format); /// Format lvi.SubItems.Add(item.Format);
lvi.SubItems.Add(item.Precision); /// Precision lvi.SubItems.Add(item.Precision);
lvi.SubItems.Add(item.Width.ToString()); /// Width lvi.SubItems.Add(item.Width.ToString());
lvi.SubItems.Add(item.Alignment.ToDescription()); /// Alignment lvi.SubItems.Add(item.Alignment.ToDescription());
lvi.SubItems.Add(item.Merge ? Strings.Yes : Strings.No); /// Merge lvi.SubItems.Add(item.Merge ? Strings.Yes : Strings.No);
if (!SupressTestIDColumn) if (!SupressTestIDColumn)
{ {
lvi.SubItems.Add(item.TestID); /// TestID lvi.SubItems.Add(item.TestID);
} }
selectedResultsListViewEx.Items.Add(lvi); selectedResultsListViewEx.Items.Add(lvi);
@@ -366,7 +444,6 @@ namespace Results.Forms
{ {
} }
void addButton_Click(object sender, EventArgs e) void addButton_Click(object sender, EventArgs e)
{ {
switch (availableTabControl.SelectedIndex) switch (availableTabControl.SelectedIndex)
@@ -380,8 +457,6 @@ namespace Results.Forms
case 2: case 2:
availableAlphabeticTreeView_DoubleClick(this, null); availableAlphabeticTreeView_DoubleClick(this, null);
break; break;
default:
break;
} }
} }
@@ -414,22 +489,24 @@ namespace Results.Forms
void AddItem(WMeterRsltItemSpec item) void AddItem(WMeterRsltItemSpec item)
{ {
if (SelectedItems == null)
{
SelectedItems = new List<WMeterRsltItemSpec>();
}
WMeterRsltItemSpec newItem = item.Clone(); WMeterRsltItemSpec newItem = item.Clone();
newItem.Caption = newItem.Name; newItem.Caption = newItem.Name;
SelectedItems.Add(newItem); SelectedItems.Add(newItem);
RedrawSelected(); RedrawSelected();
/// Select the last item
selectedResultsListViewEx.Focus(); selectedResultsListViewEx.Focus();
selectedResultsListViewEx.Items[SelectedItems.Count - 1].Selected = true; selectedResultsListViewEx.Items[SelectedItems.Count - 1].Selected = true;
selectedResultsListViewEx.Items[SelectedItems.Count - 1].EnsureVisible(); selectedResultsListViewEx.Items[SelectedItems.Count - 1].EnsureVisible();
} }
private void selectedResultsListViewEx_DoubleClick(object sender, EventArgs e) private void selectedResultsListViewEx_DoubleClick(object sender, EventArgs e)
{ {
/// Double click works when just one item is selected
if (selectedResultsListViewEx.SelectedIndices.Count == 1) if (selectedResultsListViewEx.SelectedIndices.Count == 1)
{ {
SelectedItems.RemoveAt(selectedResultsListViewEx.SelectedIndices[0]); SelectedItems.RemoveAt(selectedResultsListViewEx.SelectedIndices[0]);
@@ -440,31 +517,22 @@ namespace Results.Forms
void removeButton_Click(object sender, EventArgs e) void removeButton_Click(object sender, EventArgs e)
{ {
/// Remove from the list (the last selected item first so that the indexes are not affected)
for (int i = selectedResultsListViewEx.SelectedIndices.Count - 1; i >= 0; i--) for (int i = selectedResultsListViewEx.SelectedIndices.Count - 1; i >= 0; i--)
{ {
SelectedItems.RemoveAt(selectedResultsListViewEx.SelectedIndices[i]); SelectedItems.RemoveAt(selectedResultsListViewEx.SelectedIndices[i]);
} }
RedrawAvailable(); RedrawAvailable();
RedrawSelected(); RedrawSelected();
} }
void removeAllButton_Click(object sender, EventArgs e) void removeAllButton_Click(object sender, EventArgs e)
{ {
/// Remove all items from 'Selected' list
SelectedItems.Clear(); SelectedItems.Clear();
RedrawAvailable(); RedrawAvailable();
RedrawSelected(); RedrawSelected();
} }
//void okButton_Click(object sender, EventArgs e)
//{
// DialogResult = DialogResult.OK;
// Close();
//}
private void ResultsConfigCtrl_KeyPress(object sender, KeyPressEventArgs e) private void ResultsConfigCtrl_KeyPress(object sender, KeyPressEventArgs e)
{ {
e.Handled = true; e.Handled = true;
@@ -475,9 +543,9 @@ namespace Results.Forms
if (selectedResultsListViewEx.SelectedIndices.Count != 1) return; if (selectedResultsListViewEx.SelectedIndices.Count != 1) return;
int selIdx = selectedResultsListViewEx.SelectedIndices[0]; int selIdx = selectedResultsListViewEx.SelectedIndices[0];
if (selIdx == 0) if (selIdx == 0)
{ {
/// Cannot move up
selectedResultsListViewEx.Focus(); selectedResultsListViewEx.Focus();
selectedResultsListViewEx.Items[0].Selected = true; selectedResultsListViewEx.Items[0].Selected = true;
return; return;
@@ -499,9 +567,9 @@ namespace Results.Forms
if (selectedResultsListViewEx.SelectedIndices.Count != 1) return; if (selectedResultsListViewEx.SelectedIndices.Count != 1) return;
int selIdx = selectedResultsListViewEx.SelectedIndices[0]; int selIdx = selectedResultsListViewEx.SelectedIndices[0];
if (selIdx == SelectedItems.Count - 1) if (selIdx == SelectedItems.Count - 1)
{ {
/// Cannot move down
selectedResultsListViewEx.Focus(); selectedResultsListViewEx.Focus();
selectedResultsListViewEx.Items[SelectedItems.Count - 1].Selected = true; selectedResultsListViewEx.Items[SelectedItems.Count - 1].Selected = true;
return; return;
@@ -518,13 +586,9 @@ namespace Results.Forms
selectedResultsListViewEx.Items[selIdx + 1].EnsureVisible(); selectedResultsListViewEx.Items[selIdx + 1].EnsureVisible();
} }
//private void cancelButton_Click(object sender, EventArgs e) private void availableByCategoryTreeView_NodeMouseHover2(
//{ object sender,
// DialogResult = DialogResult.Cancel; TreeNodeMouseHoverEventArgs e)
// Close();
//}
private void availableByCategoryTreeView_NodeMouseHover2(object sender, TreeNodeMouseHoverEventArgs e)
{ {
ToolTip toolTip = new ToolTip(); ToolTip toolTip = new ToolTip();
toolTip.SetToolTip(this, e.Node.ToolTipText); toolTip.SetToolTip(this, e.Node.ToolTipText);
+2
View File
@@ -362,6 +362,8 @@ namespace Results
Pulses_per_unit, /// 306 Pulses_per_unit, /// 306
FlipMode, /// 307 iPerl mode used for this meter test result 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, 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();
}
}
}
+1
View File
@@ -50,6 +50,7 @@ namespace Results.Mappings
#endif #endif
References(x => x.WaterMeter); References(x => x.WaterMeter);
References(x => x.TestRslt); 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();
}
}
}
+2
View File
@@ -58,6 +58,8 @@ namespace Results.Mappings
#if ORACLE_DB #if ORACLE_DB
Map(x => x.WMTypeId); Map(x => x.WMTypeId);
#endif #endif
Map(x => x.Q3Channel);
} }
} }
} }
+2
View File
@@ -32,6 +32,7 @@ namespace Results.Mappings
#if IPERL #if IPERL
Map(x => x.OrigCalibFactor); Map(x => x.OrigCalibFactor);
Map(x => x.CalibFactor); Map(x => x.CalibFactor);
Map(x => x.CalibFactorNominal);
Map(x => x.OrigCalibFactorLNA); Map(x => x.OrigCalibFactorLNA);
Map(x => x.CalibFactorLNA); Map(x => x.CalibFactorLNA);
Map(x => x.Q2ErrWOCorrection); Map(x => x.Q2ErrWOCorrection);
@@ -65,6 +66,7 @@ namespace Results.Mappings
Map(x => x.Pruefindex); Map(x => x.Pruefindex);
Map(x => x.HydrPruefung); Map(x => x.HydrPruefung);
#endif #endif
Map(x => x.Q3Channel);// Genesis meter identification
References(x => x.WaterMeterData); References(x => x.WaterMeterData);
References(x => x.Batch); References(x => x.Batch);
HasMany(x => x.MeterTestRslts) HasMany(x => x.MeterTestRslts)
+21 -3
View File
@@ -13,6 +13,8 @@
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<TargetFrameworkProfile /> <TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@@ -53,12 +55,13 @@
<Reference Include="NHibernate"> <Reference Include="NHibernate">
<HintPath>..\packages\NHibernate.4.0.4.4000\lib\net40\NHibernate.dll</HintPath> <HintPath>..\packages\NHibernate.4.0.4.4000\lib\net40\NHibernate.dll</HintPath>
</Reference> </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" />
<Reference Include="System.Core" /> <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.Drawing" />
<Reference Include="System.Transactions" />
<Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Forms" />
<Reference Include="System.Windows.Forms.DataVisualization" /> <Reference Include="System.Windows.Forms.DataVisualization" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
@@ -255,6 +258,7 @@
<EmbeddedResource Include="Resources\Strings.ru.resx" /> <EmbeddedResource Include="Resources\Strings.ru.resx" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="packages.config" />
<None Include="Resources\Headpic.png" /> <None Include="Resources\Headpic.png" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -270,4 +274,18 @@
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </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> </Project>
+2
View File
@@ -457,6 +457,8 @@ namespace Results
#if IPERL #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.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.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.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.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))); AllItems.Add(new WMeterRsltItemSpec(ItemID.Q2ErrWOCorrection, "iPerl Q2ErrWOCorrection", Quantity.Error, ItemCategory.MeterResult, (w,t,u,f,p) => FormatDbl(u, f, p, "V3", w.Q2ErrWOCorrection)));
+3
View File
@@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="log4net" version="2.0.15" targetFramework="net472" /> <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> </packages>
+2 -2
View File
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// Build Number // Build Number
// Revision // Revision
// //
[assembly: AssemblyVersion("3.9.3143.0")] [assembly: AssemblyVersion("3.9.3145.101")]
[assembly: AssemblyFileVersion("3.9.3143.0")] [assembly: AssemblyFileVersion("3.9.3145.101")]
+6
View File
@@ -1770,4 +1770,10 @@
<data name="Loading" xml:space="preserve"><value>Načítání</value></data> <data name="Loading" xml:space="preserve"><value>Načítání</value></data>
<data name="Simulated" xml:space="preserve"><value>Simulace</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="Not_loaded" xml:space="preserve"><value>Nenačteno</value></data>
<data name="IperlCalibFactorNominal" xml:space="preserve">
<value>Výchozí kalibrační faktor:</value>
</data>
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
<value>Nominální surový kalibrační faktor odpovídající 100 %. Tato hodnota se používá pro výpočet položky „iPerl CalibFactor (%)“ v konfiguraci výsledků.</value>
</data>
</root> </root>
+6
View File
@@ -2232,4 +2232,10 @@
<data name="Loading" xml:space="preserve"><value>Laden</value></data> <data name="Loading" xml:space="preserve"><value>Laden</value></data>
<data name="Simulated" xml:space="preserve"><value>Simuliert</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="Not_loaded" xml:space="preserve"><value>Nicht geladen</value></data>
<data name="IperlCalibFactorNominal" xml:space="preserve">
<value>Standard-Kalibrierfaktor:</value>
</data>
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
<value>Roher Kalibrierfaktor, der 100 % entspricht. Dieser Wert wird zur Berechnung von „iPerl CalibFactor (%)“ in der Ergebnis-Konfiguration verwendet.</value>
</data>
</root> </root>
+6
View File
@@ -141,4 +141,10 @@
<data name="Loading" xml:space="preserve"><value>Cargando</value></data> <data name="Loading" xml:space="preserve"><value>Cargando</value></data>
<data name="Simulated" xml:space="preserve"><value>Simulado</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="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> </root>
+6
View File
@@ -2031,4 +2031,10 @@
<data name="Loading" xml:space="preserve"><value>Chargement</value></data> <data name="Loading" xml:space="preserve"><value>Chargement</value></data>
<data name="Simulated" xml:space="preserve"><value>Simulé</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="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> </root>
+6
View File
@@ -1803,4 +1803,10 @@
<data name="Loading" xml:space="preserve"><value>Caricamento</value></data> <data name="Loading" xml:space="preserve"><value>Caricamento</value></data>
<data name="Simulated" xml:space="preserve"><value>Simulato</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="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> </root>
+6
View File
@@ -1707,4 +1707,10 @@
<data name="Loading" xml:space="preserve"><value>Ładowanie</value></data> <data name="Loading" xml:space="preserve"><value>Ładowanie</value></data>
<data name="Simulated" xml:space="preserve"><value>Symulacja</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="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> </root>
+6
View File
@@ -2503,4 +2503,10 @@
<data name="Not_loaded" xml:space="preserve"> <data name="Not_loaded" xml:space="preserve">
<value>Not loaded</value> <value>Not loaded</value>
</data> </data>
<data name="IperlCalibFactorNominal" xml:space="preserve">
<value>Default calibration factor:</value>
</data>
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
<value>Raw calibration factor representing 100 %. This value is used to calculate 'iPerl CalibFactor (%)' in the results configuration.</value>
</data>
</root> </root>
+6
View File
@@ -855,4 +855,10 @@
<data name="Loading" xml:space="preserve"><value>Se încarcă</value></data> <data name="Loading" xml:space="preserve"><value>Se încarcă</value></data>
<data name="Simulated" xml:space="preserve"><value>Simulat</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="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> </root>
+6
View File
@@ -1620,4 +1620,10 @@
<data name="Loading" xml:space="preserve"><value>Загрузка</value></data> <data name="Loading" xml:space="preserve"><value>Загрузка</value></data>
<data name="Simulated" 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="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> </root>
+6
View File
@@ -268,4 +268,10 @@
<data name="Loading" xml:space="preserve"><value>Načítavanie</value></data> <data name="Loading" xml:space="preserve"><value>Načítavanie</value></data>
<data name="Simulated" xml:space="preserve"><value>Simulácia</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="Not_loaded" xml:space="preserve"><value>Nenačítané</value></data>
<data name="IperlCalibFactorNominal" xml:space="preserve">
<value>Predvolený kalibračný faktor:</value>
</data>
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
<value>Nominálny surový kalibračný faktor zodpovedajúci 100 %. Táto hodnota sa používa na výpočet položky „iPerl CalibFactor (%)“ v konfigurácii výsledkov.</value>
</data>
</root> </root>
+6
View File
@@ -1209,4 +1209,10 @@
<data name="Loading" xml:space="preserve"><value>正在加载</value></data> <data name="Loading" xml:space="preserve"><value>正在加载</value></data>
<data name="Simulated" 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="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> </root>
File diff suppressed because it is too large Load Diff
@@ -9,19 +9,27 @@ using TBF.Rig.Generic;
namespace TBF.Rig.Output.DB.ResultsWriter 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 = public static XmlSerializer Serializer =
XmlSerializer.FromTypes(new[] { typeof(ResultsWriterCfg) })[0]; XmlSerializer.FromTypes(
new[] { typeof(ResultsWriterCfg) })[0];
public override XmlSerializer GetSerializer() public override XmlSerializer GetSerializer()
{ {
return Serializer; 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> /// <summary>
@@ -34,25 +42,34 @@ namespace TBF.Rig.Output.DB.ResultsWriter
/// </summary> /// </summary>
public string StorageName; public string StorageName;
/// Runtime model used ResultsConfigCtrl /// <summary>
/// Runtime result model used by ResultsConfigCtrl.
/// </summary>
[XmlIgnore] [XmlIgnore]
public List<WMeterRsltItemSpec> SelectedItems; public List<WMeterRsltItemSpec> SelectedItems;
/// <summary>
/// Serialized representation of selected result items.
/// </summary>
public string[] Items; public string[] Items;
/// Serializable model /// <summary>
/// Serializable model retained for compatibility.
/// </summary>
public List<ResultsWriterItemCfg> SelectedItemsCfg; public List<ResultsWriterItemCfg> SelectedItemsCfg;
ResultsWriterCfg() ResultsWriterCfg()
{ {
ParentName = string.Empty; // here should be UniDataStorageWriter component name ParentName = string.Empty;
SelectedItems = new List<WMeterRsltItemSpec>(); SelectedItems = new List<WMeterRsltItemSpec>();
SelectedItemsCfg = new List<ResultsWriterItemCfg>(); SelectedItemsCfg = new List<ResultsWriterItemCfg>();
Enabled = true; Enabled = true;
StorageName = "Results"; StorageName = "Results";
} }
public ResultsWriterCfg(string name, IComponentFactory factory) public ResultsWriterCfg(
string name,
IComponentFactory factory)
: this() : this()
{ {
Name = name; Name = name;
@@ -67,22 +84,35 @@ namespace TBF.Rig.Output.DB.ResultsWriter
ParentName, ParentName,
Enabled, Enabled,
StorageName, StorageName,
SelectedItems != null ? SelectedItems.Count : 0); SelectedItems != null
? SelectedItems.Count
: 0);
} }
/// <summary>
/// Copies runtime result items into the serialized model.
/// </summary>
public void UpdateSerializableModel() public void UpdateSerializableModel()
{ {
Items = WMeterRsltItemSpec.ToStrArray(SelectedItems); Items =
WMeterRsltItemSpec.ToStrArray(
SelectedItems);
} }
/// <summary>
/// Recreates runtime result items after deserialization.
/// </summary>
public void UpdateRuntimeModel() public void UpdateRuntimeModel()
{ {
SelectedItems = new List<WMeterRsltItemSpec>(); SelectedItems =
new List<WMeterRsltItemSpec>();
if (Items == null) if (Items == null)
return; return;
SelectedItems.AddRange(WMeterRsltItemSpec.FromStrArray(Items)); SelectedItems.AddRange(
WMeterRsltItemSpec.FromStrArray(
Items));
} }
} }
} }
@@ -8,37 +8,53 @@ using System.Windows.Forms;
using Common; using Common;
using Config.Entities; using Config.Entities;
using TBF.Rig.Generic; 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 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; private ResultsWriterCfg config;
IList<Component> cmpntEntities; private IList<Component> cmpntEntities;
private bool resultsConfigChanged;
bool resultsConfigChanged;
public IComponentCfg Config public IComponentCfg Config
{ {
get { return config as IComponentCfg; } get { return config as IComponentCfg; }
set set
{ {
config = value as ResultsWriterCfg; config =
value as ResultsWriterCfg;
Redraw(); Redraw();
} }
} }
public ResultsWriterCfgCtrl(IList<Component> cmpntEntities) public ResultsWriterCfgCtrl(
IList<Component> cmpntEntities)
{ {
InitializeComponent(); 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(); 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(); config.UpdateRuntimeModel();
classNameLabel.Text = config.Factory.ClassName; classNameLabel.Text =
nameTextBox.Text = config.Name; config.Factory.ClassName;
enabledCheckBox.Checked = config.Enabled;
storageNameTextBox.Text = config.StorageName; nameTextBox.Text =
config.Name;
enabledCheckBox.Checked =
config.Enabled;
storageNameTextBox.Text =
config.StorageName;
parentComboBox.Items.Clear(); parentComboBox.Items.Clear();
parentComboBox.Items.Add(string.Empty); parentComboBox.Items.Add(
string.Empty);
if (cmpntEntities != null) 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 && 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( selectedItemsLabel.Text =
string.Format(
"{0} selected item(s)", "{0} selected item(s)",
config.SelectedItems != null ? config.SelectedItems.Count : 0); config.SelectedItems != null
? config.SelectedItems.Count
: 0);
resultsConfigChanged = false; resultsConfigChanged =
false;
} }
public void Unlock() public void Unlock()
@@ -94,23 +127,35 @@ namespace TBF.Rig.Output.DB.ResultsWriter
previewRequestButton.Enabled = true; 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; 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; 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; return CfgUpdateFlags.Error;
} }
@@ -119,89 +164,140 @@ namespace TBF.Rig.Output.DB.ResultsWriter
public CfgUpdateFlags UpdateCfg() 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; config.Name =
flags |= CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd; nameTextBox.Text;
flags |=
CfgUpdateFlags.AnyChange |
CfgUpdateFlags.RestartRqrd;
} }
if (config.ParentName != parentComboBox.Text) if (config.ParentName !=
parentComboBox.Text)
{ {
config.ParentName = parentComboBox.Text; config.ParentName =
flags |= CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd; parentComboBox.Text;
flags |=
CfgUpdateFlags.AnyChange |
CfgUpdateFlags.RestartRqrd;
} }
flags |= UpdateDifferent( flags |=
UpdateDifferent(
ref config.Enabled, ref config.Enabled,
enabledCheckBox.Checked, enabledCheckBox.Checked,
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd); CfgUpdateFlags.AnyChange |
CfgUpdateFlags.RestartRqrd);
flags |= UpdateDifferent( flags |=
UpdateDifferent(
ref config.StorageName, ref config.StorageName,
storageNameTextBox.Text, storageNameTextBox.Text,
CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd); CfgUpdateFlags.AnyChange |
CfgUpdateFlags.RestartRqrd);
if (resultsConfigChanged) if (resultsConfigChanged)
{ {
flags |= CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd; config.UpdateSerializableModel();
resultsConfigChanged = false;
flags |=
CfgUpdateFlags.AnyChange |
CfgUpdateFlags.RestartRqrd;
resultsConfigChanged =
false;
} }
return flags; 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)
using (ResultsWriterResultsDlg dlg = new ResultsWriterResultsDlg())
{
dlg.SelectedItems = config.SelectedItems;
if (dlg.ShowDialog(this) == DialogResult.OK)
{
config.SelectedItems = new List<Results.WMeterRsltItemSpec>(dlg.SelectedItems);
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)
{
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);
return; return;
string payloadTemplatePath =
ResolvePayloadTemplatePath();
using (ResultsWriterResultsDlg dlg =
new ResultsWriterResultsDlg())
{
dlg.SelectedItems =
CloneSelectedItems(
config.SelectedItems);
dlg.PayloadTemplatePath =
payloadTemplatePath;
if (dlg.ShowDialog(this) ==
DialogResult.OK)
{
config.SelectedItems =
new List<Results.WMeterRsltItemSpec>(
dlg.SelectedItems);
config.UpdateSerializableModel();
resultsConfigChanged =
true;
selectedItemsLabel.Text =
string.Format(
"{0} selected item(s)",
config.SelectedItems != null
? config.SelectedItems.Count
: 0);
} }
}
}
/// <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();
try try
{ {
config.UpdateSerializableModel();
config.UpdateRuntimeModel(); config.UpdateRuntimeModel();
ResultsWriter writer = new ResultsWriter(config); ResultsWriter writer =
new ResultsWriter(
config);
writer.InitializeParent(); writer.InitializeParent();
writer.WriteBatchResults(batch);
XmlPayloadBuildResult preview =
writer.GeneratePreviewPayload(
batch,
5);
MessageBox.Show( MessageBox.Show(
"Current batch was written by ResultsWriter.", string.Format(
"ResultsWriter", "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, MessageBoxButtons.OK,
MessageBoxIcon.Information); MessageBoxIcon.Information);
} }
@@ -209,33 +305,93 @@ namespace TBF.Rig.Output.DB.ResultsWriter
{ {
MessageBox.Show( MessageBox.Show(
ex.Message, ex.Message,
"ResultsWriter write failed", "ResultsWriter preview failed",
MessageBoxButtons.OK, MessageBoxButtons.OK,
MessageBoxIcon.Error); 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() 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.BatchNr = 999999;
batch.ProcedureName = "ResultsWriter simulation"; batch.ProcedureName = "ResultsWriter XML preview";
batch.StartTime = DateTime.Now; batch.StartTime = DateTime.Now;
batch.EndTime = DateTime.Now; batch.EndTime = DateTime.Now;
batch.TestBenchName = "Mexico"; batch.TestBenchName = "SIMULATION-BENCH";
Results.Entities.WaterMeter wm1 = new Results.Entities.WaterMeter(); Results.Entities.WaterMeter wm =
wm1.Batch = batch; new Results.Entities.WaterMeter();
wm1.WMPosition = 1;
wm1.SerialNr = "SN000001";
batch.WaterMeters.Add(wm1);
Results.Entities.WaterMeter wm2 = new Results.Entities.WaterMeter(); wm.Batch = batch;
wm2.Batch = batch; wm.WMPosition = 1;
wm2.WMPosition = 2; wm.SerialNr = "SIM000001";
wm2.SerialNr = "SN000002";
batch.WaterMeters.Add(wm2); batch.WaterMeters.Add(
wm);
return batch; return batch;
} }
@@ -1,4 +1,5 @@
namespace TBF.Rig.Output.DB.ResultsWriter
namespace TBF.Rig.Output.DB.ResultsWriter
{ {
partial class ResultsWriterResultsDlg partial class ResultsWriterResultsDlg
{ {
@@ -6,7 +7,9 @@
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
if (disposing && (components != null)) components.Dispose(); if (disposing && (components != null))
components.Dispose();
base.Dispose(disposing); base.Dispose(disposing);
} }
@@ -34,10 +37,11 @@
this.okButton.Location = new System.Drawing.Point(714, 512); this.okButton.Location = new System.Drawing.Point(714, 512);
this.okButton.Name = "okButton"; this.okButton.Name = "okButton";
this.okButton.Size = new System.Drawing.Size(104, 30); this.okButton.Size = new System.Drawing.Size(104, 30);
this.okButton.TabIndex = 1; this.okButton.TabIndex = 2;
this.okButton.Text = "OK"; this.okButton.Text = "OK";
this.okButton.UseVisualStyleBackColor = true; 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 = this.cancelButton.Anchor =
((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom |
@@ -46,7 +50,7 @@
this.cancelButton.Location = new System.Drawing.Point(824, 512); this.cancelButton.Location = new System.Drawing.Point(824, 512);
this.cancelButton.Name = "cancelButton"; this.cancelButton.Name = "cancelButton";
this.cancelButton.Size = new System.Drawing.Size(104, 30); this.cancelButton.Size = new System.Drawing.Size(104, 30);
this.cancelButton.TabIndex = 2; this.cancelButton.TabIndex = 3;
this.cancelButton.Text = "Cancel"; this.cancelButton.Text = "Cancel";
this.cancelButton.UseVisualStyleBackColor = true; this.cancelButton.UseVisualStyleBackColor = true;
@@ -61,7 +65,8 @@
this.Name = "ResultsWriterResultsDlg"; this.Name = "ResultsWriterResultsDlg";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "ResultsWriter configuration"; this.Text = "ResultsWriter configuration";
this.Load += new System.EventHandler(this.ResultsWriterResultsDlg_Load); this.Load +=
new System.EventHandler(this.ResultsWriterResultsDlg_Load);
this.ResumeLayout(false); this.ResumeLayout(false);
} }
@@ -11,34 +11,180 @@ using TBF.Resources;
namespace TBF.Rig.Output.DB.ResultsWriter namespace TBF.Rig.Output.DB.ResultsWriter
{ {
/// <summary>
/// Configures ResultsWriter result items and optional XML destinations.
/// </summary>
public partial class ResultsWriterResultsDlg : Form public partial class ResultsWriterResultsDlg : Form
{ {
private string payloadTemplatePath;
public IList<WMeterRsltItemSpec> SelectedItems public IList<WMeterRsltItemSpec> SelectedItems
{ {
set { resultsConfigCtrl.SelectedItems = value; } set { resultsConfigCtrl.SelectedItems = value; }
get { return resultsConfigCtrl.SelectedItems; } 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() public ResultsWriterResultsDlg()
{ {
InitializeComponent(); InitializeComponent();
this.Icon = Properties.Resources.TBF_icon; Icon =
resultsConfigCtrl.SupressTestIDColumn = true; 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"; Text =
okButton.Text = Strings.OkBtnText; "ResultsWriter configuration";
cancelButton.Text = Strings.CancelBtnText;
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(); 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 namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
{ {
/// <summary> /// <summary>
/// Factory component 'UniDataStorageWriter' implements more storing modules /// Provides factory services for the
/// Modules: /// <see cref="UniDataStorageWriter"/> component.
/// </summary> /// </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 class Factory : IComponentFactory
{ {
public string ClassName { get { return GetType().Namespace.Substring(8); } } /// For backward compatibility /// <summary>
public override string ToString() { return ClassName; } /// Gets the component class name used by the TBF component framework.
/// </summary>
public IComponent DummyComponent() { return new Writer(new WriterCfg("UniDataStorageWriter", this)); } /// <remarks>
/// The namespace prefix is removed for backward compatibility with
public IComponent GetComponent(IComponentCfg cfg, IList<IComponent> components) /// existing component configurations.
/// </remarks>
public string ClassName
{ {
WriterCfg WriterCfg = cfg as WriterCfg; get { return GetType().Namespace.Substring(8); }
if (WriterCfg == null)
throw new ArgumentException("Invalid config for UniDataStorageWriter");
return new Writer(WriterCfg);
} }
public IComponentCfg DefaultConfig() { return new WriterCfg("UniDataStorageWriter", this); } /// <summary>
/// Returns the component class name.
public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component) /// </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; }
}
}
@@ -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;
}
}
}
@@ -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));
}
}
}
@@ -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.");
}
}
}
}
@@ -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>(); InsertItems = new List<InsertWriteItem>();
UpdateItems = new List<UpdateWriteItem>(); UpdateItems = new List<UpdateWriteItem>();
StoredProcedureParameters = new List<StoredProcedureWriteParameter>();
Payload = string.Empty;
OutputFileName = string.Empty;
Mode = WriteMode.Insert; Mode = WriteMode.Insert;
} }
@@ -20,6 +23,32 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces
public List<InsertWriteItem> InsertItems { get; private set; } public List<InsertWriteItem> InsertItems { get; private set; }
public List<UpdateWriteItem> UpdateItems { 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 public enum WriteMode
@@ -27,7 +56,22 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces
Insert, Insert,
Update, Update,
Upsert, 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 public class InsertWriteItem
@@ -54,4 +98,25 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces
return $"WHERE {WhereParameterName} = {WhereValue} -> SET {SetParameterName} = {SetValue}"; 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.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 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; } 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); 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); WriterDiagnosticResult WriteData(DataWriteRequest request);
} }
} }
@@ -33,6 +33,7 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
public const string Xls = ".xls"; public const string Xls = ".xls";
public const string Xlsx = ".xlsx"; public const string Xlsx = ".xlsx";
public const string Json = ".json"; public const string Json = ".json";
public const string Xml = ".xml";
} }
} }
} }
File diff suppressed because it is too large Load Diff
@@ -72,11 +72,22 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
this.examples1Button = new System.Windows.Forms.Button(); this.examples1Button = new System.Windows.Forms.Button();
this.technologyTypeLabel = new System.Windows.Forms.Label(); this.technologyTypeLabel = new System.Windows.Forms.Label();
this.technologyTypeComboBox = new System.Windows.Forms.ComboBox(); 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.groupBox1.SuspendLayout();
this.groupBox2.SuspendLayout(); this.groupBox2.SuspendLayout();
this.groupBox3.SuspendLayout(); this.groupBox3.SuspendLayout();
this.groupBox4.SuspendLayout(); this.groupBox4.SuspendLayout();
this.groupBox5.SuspendLayout(); this.groupBox5.SuspendLayout();
this.groupBox6.SuspendLayout();
this.SuspendLayout(); this.SuspendLayout();
// //
// nameTextBox // nameTextBox
@@ -128,9 +139,9 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
this.groupBox1.Controls.Add(this.info2Button); this.groupBox1.Controls.Add(this.info2Button);
this.groupBox1.Controls.Add(this.sourceTestResultTextBox); this.groupBox1.Controls.Add(this.sourceTestResultTextBox);
this.groupBox1.Controls.Add(this.connectToDataSourceButton); 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.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.TabIndex = 15;
this.groupBox1.TabStop = false; this.groupBox1.TabStop = false;
this.groupBox1.Text = "Data storage source testing"; this.groupBox1.Text = "Data storage source testing";
@@ -151,7 +162,7 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
this.sourceTestResultTextBox.Multiline = true; this.sourceTestResultTextBox.Multiline = true;
this.sourceTestResultTextBox.Name = "sourceTestResultTextBox"; this.sourceTestResultTextBox.Name = "sourceTestResultTextBox";
this.sourceTestResultTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both; 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; this.sourceTestResultTextBox.TabIndex = 15;
// //
// connectToDataSourceButton // connectToDataSourceButton
@@ -168,9 +179,9 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
this.groupBox2.Controls.Add(this.info4Button); this.groupBox2.Controls.Add(this.info4Button);
this.groupBox2.Controls.Add(this.writeTestResultTextBox); this.groupBox2.Controls.Add(this.writeTestResultTextBox);
this.groupBox2.Controls.Add(this.writeDataByParamAndTemplateButton); 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.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.TabIndex = 16;
this.groupBox2.TabStop = false; this.groupBox2.TabStop = false;
this.groupBox2.Text = "Complete write testing"; this.groupBox2.Text = "Complete write testing";
@@ -191,7 +202,7 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
this.writeTestResultTextBox.Multiline = true; this.writeTestResultTextBox.Multiline = true;
this.writeTestResultTextBox.Name = "writeTestResultTextBox"; this.writeTestResultTextBox.Name = "writeTestResultTextBox";
this.writeTestResultTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both; 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; this.writeTestResultTextBox.TabIndex = 18;
// //
// writeDataByParamAndTemplateButton // writeDataByParamAndTemplateButton
@@ -213,9 +224,9 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
this.groupBox3.Controls.Add(this.listBoxWriteParams); this.groupBox3.Controls.Add(this.listBoxWriteParams);
this.groupBox3.Controls.Add(this.writeParamValueTextBox); this.groupBox3.Controls.Add(this.writeParamValueTextBox);
this.groupBox3.Controls.Add(this.info5Button); 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.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.TabIndex = 17;
this.groupBox3.TabStop = false; this.groupBox3.TabStop = false;
this.groupBox3.Text = "Component interface testing"; this.groupBox3.Text = "Component interface testing";
@@ -240,7 +251,7 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
// //
// buttonRemoveParam // 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.Name = "buttonRemoveParam";
this.buttonRemoveParam.Size = new System.Drawing.Size(75, 23); this.buttonRemoveParam.Size = new System.Drawing.Size(75, 23);
this.buttonRemoveParam.TabIndex = 25; this.buttonRemoveParam.TabIndex = 25;
@@ -249,7 +260,7 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
// //
// buttonAddParam // 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.Name = "buttonAddParam";
this.buttonAddParam.Size = new System.Drawing.Size(75, 23); this.buttonAddParam.Size = new System.Drawing.Size(75, 23);
this.buttonAddParam.TabIndex = 24; this.buttonAddParam.TabIndex = 24;
@@ -268,17 +279,22 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
// listBoxWriteParams // listBoxWriteParams
// //
this.listBoxWriteParams.FormattingEnabled = true; 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.Name = "listBoxWriteParams";
this.listBoxWriteParams.Size = new System.Drawing.Size(297, 329); this.listBoxWriteParams.Size = new System.Drawing.Size(297, 121);
this.listBoxWriteParams.TabIndex = 22; this.listBoxWriteParams.TabIndex = 22;
// //
// writeParamValueTextBox // writeParamValueTextBox
// //
this.writeParamValueTextBox.AcceptsReturn = true;
this.writeParamValueTextBox.AcceptsTab = true;
this.writeParamValueTextBox.Location = new System.Drawing.Point(9, 68); this.writeParamValueTextBox.Location = new System.Drawing.Point(9, 68);
this.writeParamValueTextBox.Multiline = true;
this.writeParamValueTextBox.Name = "writeParamValueTextBox"; 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.TabIndex = 21;
this.writeParamValueTextBox.WordWrap = false;
// //
// info5Button // info5Button
// //
@@ -390,7 +406,6 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
this.buttonUpdateTemplate.TabIndex = 35; this.buttonUpdateTemplate.TabIndex = 35;
this.buttonUpdateTemplate.Text = "Update"; this.buttonUpdateTemplate.Text = "Update";
this.buttonUpdateTemplate.UseVisualStyleBackColor = true; this.buttonUpdateTemplate.UseVisualStyleBackColor = true;
this.buttonUpdateTemplate.Click += new System.EventHandler(this.buttonUpdateTemplate_Click);
// //
// buttonAddTemplate // buttonAddTemplate
// //
@@ -401,7 +416,6 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
this.buttonAddTemplate.TabIndex = 34; this.buttonAddTemplate.TabIndex = 34;
this.buttonAddTemplate.Text = "Add"; this.buttonAddTemplate.Text = "Add";
this.buttonAddTemplate.UseVisualStyleBackColor = true; this.buttonAddTemplate.UseVisualStyleBackColor = true;
this.buttonAddTemplate.Click += new System.EventHandler(this.buttonAddTemplate_Click);
// //
// templateEditTextBox // templateEditTextBox
// //
@@ -468,10 +482,111 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
this.technologyTypeComboBox.Size = new System.Drawing.Size(326, 21); this.technologyTypeComboBox.Size = new System.Drawing.Size(326, 21);
this.technologyTypeComboBox.TabIndex = 25; 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 // WriterCfgCtrl
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.groupBox6);
this.Controls.Add(this.technologyTypeComboBox); this.Controls.Add(this.technologyTypeComboBox);
this.Controls.Add(this.technologyTypeLabel); this.Controls.Add(this.technologyTypeLabel);
this.Controls.Add(this.groupBox5); this.Controls.Add(this.groupBox5);
@@ -497,6 +612,8 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
this.groupBox4.PerformLayout(); this.groupBox4.PerformLayout();
this.groupBox5.ResumeLayout(false); this.groupBox5.ResumeLayout(false);
this.groupBox5.PerformLayout(); this.groupBox5.PerformLayout();
this.groupBox6.ResumeLayout(false);
this.groupBox6.PerformLayout();
this.ResumeLayout(false); this.ResumeLayout(false);
this.PerformLayout(); this.PerformLayout();
@@ -547,5 +664,15 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI
private System.Windows.Forms.Button buttonUpdateTemplate; private System.Windows.Forms.Button buttonUpdateTemplate;
private System.Windows.Forms.Button buttonRemoveTemplate; private System.Windows.Forms.Button buttonRemoveTemplate;
private System.Windows.Forms.Label label1; 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();
}
}
}
@@ -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 Common;
using Config.Entities; using Config.Entities;
using FluentNHibernate.MappingModel.Output;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using TBF.Rig.Generic; using TBF.Rig.Generic;
@@ -114,6 +115,9 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
case TechnologyTypes.Json: case TechnologyTypes.Json:
return new JsonWriter(cfg); return new JsonWriter(cfg);
case TechnologyTypes.Xml:
return new XmlFileWriter(cfg);
default: default:
throw new NotSupportedException( throw new NotSupportedException(
string.Format("Unsupported file technology type: '{0}'", cfg.TechnologyType)); string.Format("Unsupported file technology type: '{0}'", cfg.TechnologyType));
@@ -5,6 +5,7 @@ using Config.Entities;
/// ///
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization; using System.Xml.Serialization;
using TBF.Resources; using TBF.Resources;
using TBF.Rig.Generic; using TBF.Rig.Generic;
@@ -14,86 +15,199 @@ using static TBF.Rig.Output.DataStorage.UniDataStorageWriter.Types;
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
{ {
/// /// <summary>
/// Class and file name is preserved for backward compatibility /// 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 class WriterCfg : ComponentCfgBase, Generic.IComponentCfg, IParamsProvider
{ {
public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(WriterCfg) })[0]; /// <summary>
public override XmlSerializer GetSerializer() { return Serializer; } /// Serializer used by the TBF configuration framework.
/// </summary>
public IComponentCfgCtrl GetControl(IList<Config.Entities.Component> cmpntEntities) { return new WriterCfgCtrl(); } public static XmlSerializer Serializer =
XmlSerializer.FromTypes(new[] { typeof(WriterCfg) })[0];
/// <summary> /// <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> /// </summary>
public string DataStorageType; public string DataStorageType;
/// <summary> /// <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> /// </summary>
public string DataSource; public string DataSource;
/// <summary> /// <summary>
/// Original field name preserved for backward compatibility. /// Legacy single-template field preserved for backward compatibility.
/// For writer semantics this represents the write template.
/// </summary> /// </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; public string QueryTemplate;
/// <summary>
/// Technology used by the configured storage target.
/// </summary>
public string TechnologyType; public string TechnologyType;
/// <summary>
/// Default write operation used by the component.
/// </summary>
public WriteMode WriteMode; 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; public List<string> WriteTemplates;
/// <summary> /// <summary>
/// Private parameterless constructor invoked by all other constructors. /// Path to an optional external XML payload template.
/// </summary> /// </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(); 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()
{ {
this.Name = name; Name = name;
this.Factory = factory; Factory = factory;
} }
/// <summary>
/// Gets the configured component name.
/// </summary>
public string ComponentName public string ComponentName
{ {
get { return Name; } get { return Name; }
} }
/// <summary>
/// Initializes all UniDataStorageWriter-specific configuration fields.
/// </summary>
public void InitializeAll() public void InitializeAll()
{ {
DataStorageType = string.Empty; DataStorageType = string.Empty;
DataSource = string.Empty; DataSource = string.Empty;
QueryTemplate = string.Empty; QueryTemplate = string.Empty;
TechnologyType = string.Empty; TechnologyType = string.Empty;
WriteMode = WriteMode.Insert; WriteMode = WriteMode.Insert;
WriteTemplates = new List<string>(); 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", "Data Storage type", // 0
"Technology type", "Technology type", // 1
"Data source", "Data source", // 2
"Query template", "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) public ICollection<string> ParamValues(int i)
{ {
switch (i) switch (i)
{ {
case 0: case 0:
return new string[] return new[]
{ {
StorageTypes.RestApi, StorageTypes.RestApi,
StorageTypes.LocalDatabase, StorageTypes.LocalDatabase,
@@ -107,7 +221,7 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
{ {
case StorageTypes.LocalDatabase: case StorageTypes.LocalDatabase:
case StorageTypes.RemoteDatabase: case StorageTypes.RemoteDatabase:
return new string[] return new[]
{ {
TechnologyTypes.MicrosoftSql, TechnologyTypes.MicrosoftSql,
TechnologyTypes.MySqlMariaDb, TechnologyTypes.MySqlMariaDb,
@@ -116,46 +230,173 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
case StorageTypes.LocalFile: case StorageTypes.LocalFile:
case StorageTypes.RemoteFile: case StorageTypes.RemoteFile:
return new string[] return new[]
{ {
TechnologyTypes.Csv, TechnologyTypes.Csv,
TechnologyTypes.Xls, TechnologyTypes.Xls,
TechnologyTypes.Xlsx,
TechnologyTypes.Json, TechnologyTypes.Json,
TechnologyTypes.Xml,
}; };
default: default:
return null; return null;
} }
case 2: case 4:
case 3: return new[]
{
WriteMode.Insert.ToString(),
WriteMode.Update.ToString(),
WriteMode.StoredProcedure.ToString(),
};
case 8:
return new[]
{
bool.FalseString,
bool.TrueString,
};
default: default:
return null; 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) 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) switch (i)
{ {
case 0: DataStorageType = strValue; return CfgUpdateFlags.RestartRqrd; case 0:
case 1: TechnologyType = strValue; return CfgUpdateFlags.RestartRqrd; return DataStorageType ?? string.Empty;
case 2: DataSource = strValue; return CfgUpdateFlags.RestartRqrd;
case 3: QueryTemplate = strValue; return CfgUpdateFlags.RestartRqrd; case 1:
default: return CfgUpdateFlags.None; 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; message = string.Empty;
strValue = strValue ?? string.Empty; strValue = strValue ?? string.Empty;
@@ -171,8 +412,10 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
return true; return true;
case 1: case 1:
if ((DataStorageType == StorageTypes.LocalDatabase || DataStorageType == StorageTypes.RemoteDatabase || if ((DataStorageType == StorageTypes.LocalDatabase ||
DataStorageType == StorageTypes.LocalFile || DataStorageType == StorageTypes.RemoteFile) && DataStorageType == StorageTypes.RemoteDatabase ||
DataStorageType == StorageTypes.LocalFile ||
DataStorageType == StorageTypes.RemoteFile) &&
string.IsNullOrWhiteSpace(strValue)) string.IsNullOrWhiteSpace(strValue))
{ {
message = "Technology type must be selected."; message = "Technology type must be selected.";
@@ -189,11 +432,80 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
return true; return true;
case 3: 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 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; return true;
default: 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; string storageType =
prms.TechnologyType = this.TechnologyType; (DataStorageType ?? string.Empty).Trim();
prms.DataSource = this.DataSource;
prms.QueryTemplate = this.QueryTemplate;
prms.WriteMode = this.WriteMode;
prms.WriteTemplates = new List<string>(); string technologyType =
(TechnologyType ?? string.Empty).Trim();
if (this.WriteTemplates != null) return
{ (storageType == StorageTypes.LocalFile ||
foreach (string item in this.WriteTemplates) storageType == StorageTypes.RemoteFile) &&
{ technologyType == TechnologyTypes.Xml;
prms.WriteTemplates.Add(item);
}
}
}
public IParamsProvider Clone()
{
WriterCfg pars = new WriterCfg();
CopyContentTo(pars);
return pars;
} }
/// <summary> /// <summary>
/// Strongly typed helper for internal use. /// Returns whether the configured target is a Microsoft SQL stored
/// procedure receiving a generated XML payload.
/// </summary> /// </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); CopyContentTo(copy);
return 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; mode = WriteMode.Insert;
template = string.Empty; template = string.Empty;
@@ -252,35 +656,72 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter
return false; return false;
int separatorIndex = item.IndexOf('|'); int separatorIndex = item.IndexOf('|');
if (separatorIndex <= 0) if (separatorIndex <= 0)
return false; return false;
string modeText = item.Substring(0, separatorIndex).Trim(); string modeText =
template = item.Substring(separatorIndex + 1).Trim(); item.Substring(0, separatorIndex).Trim();
if (!Enum.TryParse(modeText, true, out mode)) template =
return false; 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 ||
WriteTemplates.Count == 0)
{ {
if (WriteTemplates == null)
return string.Empty; return string.Empty;
}
foreach (string item in WriteTemplates) 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)
{ {
WriteMode m; WriteTemplates = new List<string>();
string t;
if (TryParseTemplateItem(item, out m, out t) && m == mode) 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)
{ {
return t; string trimmed = item.Trim();
}
}
return QueryTemplate ?? string.Empty; // fallback if (!string.IsNullOrWhiteSpace(trimmed))
WriteTemplates.Add(trimmed);
}
} }
} }
} }
@@ -1,27 +1,68 @@
using System; using System;
using System.Data;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Diagnostic; using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Diagnostic;
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces; using TBF.Rig.Output.DataStorage.UniDataStorageWriter.Interfaces;
using TBF.Rig.Output.DataStorage.UniDataStorageWriter.UI;
using static TBF.Rig.Output.DataStorage.UniDataStorageWriter.Types; using static TBF.Rig.Output.DataStorage.UniDataStorageWriter.Types;
namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers
{ {
/// <summary> /// <summary>
/// Database writer implementation for Microsoft SQL. /// Provides data writing support for Microsoft SQL databases.
/// Uses full SQL template defined in cfg.QueryTemplate.
/// </summary> /// </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 public class DatabaseWriter : IDataStorageWriter
{ {
private readonly WriterCfg cfg; 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) public DatabaseWriter(WriterCfg cfg)
{ {
this.cfg = cfg ?? throw new ArgumentNullException(nameof(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 public WriterCapabilities Capabilities
{ {
get get
@@ -35,11 +76,23 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers
caps.SupportedWriteModes.Add(WriteMode.Insert); caps.SupportedWriteModes.Add(WriteMode.Insert);
caps.SupportedWriteModes.Add(WriteMode.Update); caps.SupportedWriteModes.Add(WriteMode.Update);
caps.SupportedWriteModes.Add(WriteMode.StoredProcedure);
return caps; 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) public WriterDiagnosticResult TestSource(bool validateOnly)
{ {
if (string.IsNullOrWhiteSpace(cfg.DataSource)) 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) public WriterDiagnosticResult WriteData(DataWriteRequest request)
{ {
if (request == null) if (request == null)
@@ -81,72 +148,78 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers
case WriteMode.Update: case WriteMode.Update:
return ExecuteUpdate(request); return ExecuteUpdate(request);
case WriteMode.StoredProcedure:
return ExecuteStoredProcedure(request);
default: default:
return Fail("Mode not supported: " + request.Mode); 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) private WriterDiagnosticResult ExecuteInsert(DataWriteRequest request)
{ {
// Validate input
if (request.InsertItems == null || request.InsertItems.Count == 0) if (request.InsertItems == null || request.InsertItems.Count == 0)
return Fail("No insert items provided."); return Fail("No insert items provided.");
// Resolve template for current write mode
string template = cfg.GetTemplate(request.Mode); string template = cfg.GetTemplate(request.Mode);
if (string.IsNullOrWhiteSpace(template)) if (string.IsNullOrWhiteSpace(template))
return Fail("Insert template is empty."); return Fail("Insert template is empty.");
// Build comma-separated list of column names
string columns = string.Join(", ", string columns = string.Join(", ",
request.InsertItems.Select(i => i.ColumnName)); request.InsertItems.Select(i => i.ColumnName));
// Build comma-separated list of SQL-formatted values
string values = string.Join(", ", string values = string.Join(", ",
request.InsertItems.Select(i => ToSqlLiteral(i.Value))); request.InsertItems.Select(i => ToSqlLiteral(i.Value)));
// Replace template placeholders:
// {0} -> column list
// {1} -> value list
string sql = template string sql = template
.Replace("{0}", columns) .Replace("{0}", columns)
.Replace("{1}", values); .Replace("{1}", values);
// Execute final SQL command
return ExecuteSql(sql, "Insert OK."); 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) private WriterDiagnosticResult ExecuteUpdate(DataWriteRequest request)
{ {
// Validate input: at least one update item must be provided
if (request.UpdateItems == null || request.UpdateItems.Count == 0) if (request.UpdateItems == null || request.UpdateItems.Count == 0)
return Fail("No update items provided."); return Fail("No update items provided.");
// Resolve template for current write mode
string template = cfg.GetTemplate(request.Mode); string template = cfg.GetTemplate(request.Mode);
if (string.IsNullOrWhiteSpace(template)) if (string.IsNullOrWhiteSpace(template))
return Fail("Update template is empty."); return Fail("Update template is empty.");
int totalRows = 0; int totalRows = 0;
// Collect all executed SQL statements for diagnostics
StringBuilder executedSql = new StringBuilder(); StringBuilder executedSql = new StringBuilder();
// Open database connection try
{
using (SqlConnection connection = new SqlConnection(cfg.DataSource)) using (SqlConnection connection = new SqlConnection(cfg.DataSource))
{ {
connection.Open(); connection.Open();
// Process each update item separately
foreach (UpdateWriteItem item in request.UpdateItems) foreach (UpdateWriteItem item in request.UpdateItems)
{ {
string sql = template; string sql = template;
// 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("{0}", item.WhereParameterName);
sql = sql.Replace("{1}", ToSqlLiteral(item.WhereValue)); sql = sql.Replace("{1}", ToSqlLiteral(item.WhereValue));
sql = sql.Replace("{2}", item.SetParameterName); sql = sql.Replace("{2}", item.SetParameterName);
@@ -168,8 +241,173 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers
ExecutedTemplate = executedSql.ToString().TrimEnd() ExecutedTemplate = executedSql.ToString().TrimEnd()
}; };
} }
catch (Exception ex)
{
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 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) private string ToSqlLiteral(string value)
{ {
if (value == null) if (value == null)
@@ -204,6 +460,15 @@ namespace TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writers
return "'" + value.Replace("'", "''") + "'"; 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) private WriterDiagnosticResult Ok(string message)
{ {
return new WriterDiagnosticResult 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) private WriterDiagnosticResult Fail(string message)
{ {
return new WriterDiagnosticResult 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 HeadCommunicationComPortNr;
public int OptoComPortNr; public int OptoComPortNr;
public int RfidComPortNr; /// 0 = use MuxBoardNr 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 int Group; /// Number written to QuidoRS to connct the watermeter to RfidComPort, 1 .. 10
//public CommunicationInterface CommunicationInterface; /// Communication Interface: RFID or NFC //public CommunicationInterface CommunicationInterface; /// Communication Interface: RFID or NFC
public string CommunicationInterfaceBridge; /// 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"; 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; flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + string.Format(Strings.Invalid_0, muxBoardNrLabel.Text); message += Environment.NewLine + string.Format(Strings.Invalid_0, muxBoardNrLabel.Text);
@@ -76,11 +76,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
// //
this.tabControl1.Controls.Add(this.tabPage1); this.tabControl1.Controls.Add(this.tabPage1);
this.tabControl1.Controls.Add(this.tabPage2); this.tabControl1.Controls.Add(this.tabPage2);
this.tabControl1.Location = new System.Drawing.Point(3, 4); this.tabControl1.Location = new System.Drawing.Point(2, 3);
this.tabControl1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.tabControl1.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
this.tabControl1.Name = "tabControl1"; this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = 0; 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; this.tabControl1.TabIndex = 0;
// //
// tabPage1 // tabPage1
@@ -99,40 +99,42 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
this.tabPage1.Controls.Add(this.nameTextBox); this.tabPage1.Controls.Add(this.nameTextBox);
this.tabPage1.Controls.Add(this.nameLabel); this.tabPage1.Controls.Add(this.nameLabel);
this.tabPage1.Controls.Add(this.classNameLabel); this.tabPage1.Controls.Add(this.classNameLabel);
this.tabPage1.Location = new System.Drawing.Point(4, 29); this.tabPage1.Location = new System.Drawing.Point(4, 22);
this.tabPage1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.tabPage1.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
this.tabPage1.Name = "tabPage1"; this.tabPage1.Name = "tabPage1";
this.tabPage1.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4); this.tabPage1.Padding = new System.Windows.Forms.Padding(2, 3, 2, 3);
this.tabPage1.Size = new System.Drawing.Size(679, 507); this.tabPage1.Size = new System.Drawing.Size(450, 325);
this.tabPage1.TabIndex = 0; this.tabPage1.TabIndex = 0;
this.tabPage1.Text = "Config"; this.tabPage1.Text = "Config";
this.tabPage1.UseVisualStyleBackColor = true; this.tabPage1.UseVisualStyleBackColor = true;
// //
// label5 // 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.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.TabIndex = 28;
this.label5.Text = "Slot Nr:"; this.label5.Text = "Slot Nr:";
// //
// textBoxSlotNr // textBoxSlotNr
// //
this.textBoxSlotNr.Enabled = false; 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.Name = "textBoxSlotNr";
this.textBoxSlotNr.Size = new System.Drawing.Size(74, 26); this.textBoxSlotNr.Size = new System.Drawing.Size(51, 20);
this.textBoxSlotNr.TabIndex = 27; this.textBoxSlotNr.TabIndex = 27;
// //
// groupBox2 // groupBox2
// //
this.groupBox2.Controls.Add(this.headPortNrTextBox); this.groupBox2.Controls.Add(this.headPortNrTextBox);
this.groupBox2.Controls.Add(this.label2); this.groupBox2.Controls.Add(this.label2);
this.groupBox2.Location = new System.Drawing.Point(11, 450); this.groupBox2.Location = new System.Drawing.Point(7, 292);
this.groupBox2.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.groupBox2.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
this.groupBox2.Name = "groupBox2"; this.groupBox2.Name = "groupBox2";
this.groupBox2.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4); this.groupBox2.Padding = new System.Windows.Forms.Padding(2, 3, 2, 3);
this.groupBox2.Size = new System.Drawing.Size(621, 52); this.groupBox2.Size = new System.Drawing.Size(414, 34);
this.groupBox2.TabIndex = 26; this.groupBox2.TabIndex = 26;
this.groupBox2.TabStop = false; this.groupBox2.TabStop = false;
this.groupBox2.Text = "Head Communication"; this.groupBox2.Text = "Head Communication";
@@ -140,41 +142,37 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
// headPortNrTextBox // headPortNrTextBox
// //
this.headPortNrTextBox.Enabled = false; this.headPortNrTextBox.Enabled = false;
this.headPortNrTextBox.Location = new System.Drawing.Point(494, 19); this.headPortNrTextBox.Location = new System.Drawing.Point(329, 12);
this.headPortNrTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.headPortNrTextBox.Name = "headPortNrTextBox"; 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; this.headPortNrTextBox.TabIndex = 8;
// //
// label2 // label2
// //
this.label2.AutoSize = true; this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(361, 22); this.label2.Location = new System.Drawing.Point(241, 14);
this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label2.Name = "label2"; 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.TabIndex = 7;
this.label2.Text = "Serial port nr.:"; this.label2.Text = "Serial port nr.:";
// //
// label4 // label4
// //
this.label4.AutoSize = true; this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(234, 126); this.label4.Location = new System.Drawing.Point(156, 82);
this.label4.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label4.Name = "label4"; 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.TabIndex = 25;
this.label4.Text = "1 .. 10"; this.label4.Text = "1 .. 10";
// //
// label3 // label3
// //
this.label3.AutoSize = true; this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(234, 90); this.label3.Location = new System.Drawing.Point(156, 58);
this.label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label3.Name = "label3"; 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.TabIndex = 24;
this.label3.Text = "1 .. 4"; this.label3.Text = "1 .. 10";
// //
// groupBox1 // groupBox1
// //
@@ -182,11 +180,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
this.groupBox1.Controls.Add(this.label1); this.groupBox1.Controls.Add(this.label1);
this.groupBox1.Controls.Add(this.rfidPortNrTextBox); this.groupBox1.Controls.Add(this.rfidPortNrTextBox);
this.groupBox1.Controls.Add(this.rfidSerialPortNrLabel); this.groupBox1.Controls.Add(this.rfidSerialPortNrLabel);
this.groupBox1.Location = new System.Drawing.Point(11, 363); this.groupBox1.Location = new System.Drawing.Point(7, 236);
this.groupBox1.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.groupBox1.Name = "groupBox1"; this.groupBox1.Name = "groupBox1";
this.groupBox1.Padding = new System.Windows.Forms.Padding(4, 5, 4, 5); this.groupBox1.Size = new System.Drawing.Size(414, 55);
this.groupBox1.Size = new System.Drawing.Size(621, 85);
this.groupBox1.TabIndex = 23; this.groupBox1.TabIndex = 23;
this.groupBox1.TabStop = false; this.groupBox1.TabStop = false;
this.groupBox1.Text = "RFID / NFC communication (in case mux. board is not used)"; 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.Enabled = false;
this.comboBoxCommunicationInterface.FormattingEnabled = true; this.comboBoxCommunicationInterface.FormattingEnabled = true;
this.comboBoxCommunicationInterface.Location = new System.Drawing.Point(202, 34); this.comboBoxCommunicationInterface.Location = new System.Drawing.Point(135, 22);
this.comboBoxCommunicationInterface.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.comboBoxCommunicationInterface.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
this.comboBoxCommunicationInterface.Name = "comboBoxCommunicationInterface"; 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; this.comboBoxCommunicationInterface.TabIndex = 9;
// //
// label1 // label1
// //
this.label1.AutoSize = true; this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(8, 38); this.label1.Location = new System.Drawing.Point(5, 25);
this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label1.Name = "label1"; 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.TabIndex = 8;
this.label1.Text = "Communication Interface"; this.label1.Text = "Communication Interface";
// //
// rfidPortNrTextBox // rfidPortNrTextBox
// //
this.rfidPortNrTextBox.Enabled = false; this.rfidPortNrTextBox.Enabled = false;
this.rfidPortNrTextBox.Location = new System.Drawing.Point(494, 32); this.rfidPortNrTextBox.Location = new System.Drawing.Point(329, 21);
this.rfidPortNrTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.rfidPortNrTextBox.Name = "rfidPortNrTextBox"; 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; this.rfidPortNrTextBox.TabIndex = 7;
// //
// rfidSerialPortNrLabel // rfidSerialPortNrLabel
// //
this.rfidSerialPortNrLabel.AutoSize = true; this.rfidSerialPortNrLabel.AutoSize = true;
this.rfidSerialPortNrLabel.Location = new System.Drawing.Point(361, 38); this.rfidSerialPortNrLabel.Location = new System.Drawing.Point(241, 25);
this.rfidSerialPortNrLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.rfidSerialPortNrLabel.Name = "rfidSerialPortNrLabel"; 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.TabIndex = 6;
this.rfidSerialPortNrLabel.Text = "Serial port nr.:"; 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.radioButton2);
this.optoDataGroupBox.Controls.Add(this.optoSerialPortLabel); this.optoDataGroupBox.Controls.Add(this.optoSerialPortLabel);
this.optoDataGroupBox.Controls.Add(this.optoSerialPortTextBox); this.optoDataGroupBox.Controls.Add(this.optoSerialPortTextBox);
this.optoDataGroupBox.Location = new System.Drawing.Point(11, 164); this.optoDataGroupBox.Location = new System.Drawing.Point(7, 107);
this.optoDataGroupBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.optoDataGroupBox.Name = "optoDataGroupBox"; this.optoDataGroupBox.Name = "optoDataGroupBox";
this.optoDataGroupBox.Padding = new System.Windows.Forms.Padding(4, 5, 4, 5); this.optoDataGroupBox.Size = new System.Drawing.Size(414, 123);
this.optoDataGroupBox.Size = new System.Drawing.Size(621, 189);
this.optoDataGroupBox.TabIndex = 18; this.optoDataGroupBox.TabIndex = 18;
this.optoDataGroupBox.TabStop = false; this.optoDataGroupBox.TabStop = false;
this.optoDataGroupBox.Text = "Opto-data"; this.optoDataGroupBox.Text = "Opto-data";
@@ -257,9 +248,10 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
this.checkBox_EnableShowChanels.Checked = true; this.checkBox_EnableShowChanels.Checked = true;
this.checkBox_EnableShowChanels.CheckState = System.Windows.Forms.CheckState.Checked; this.checkBox_EnableShowChanels.CheckState = System.Windows.Forms.CheckState.Checked;
this.checkBox_EnableShowChanels.Enabled = false; 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.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.TabIndex = 10;
this.checkBox_EnableShowChanels.Text = "Enable Show Channels"; this.checkBox_EnableShowChanels.Text = "Enable Show Channels";
this.checkBox_EnableShowChanels.UseVisualStyleBackColor = true; this.checkBox_EnableShowChanels.UseVisualStyleBackColor = true;
@@ -267,58 +259,56 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
// tBBeginDataFlush // tBBeginDataFlush
// //
this.tBBeginDataFlush.Enabled = false; 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.MaxLength = 8;
this.tBBeginDataFlush.Name = "tBBeginDataFlush"; 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.TabIndex = 9;
this.tBBeginDataFlush.Text = "2000"; this.tBBeginDataFlush.Text = "2000";
this.tBBeginDataFlush.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; this.tBBeginDataFlush.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
// //
// labelFlush // 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.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.TabIndex = 8;
this.labelFlush.Text = "Begin Data Flush:"; this.labelFlush.Text = "Begin Data Flush:";
// //
// tcpipPortLabel // tcpipPortLabel
// //
this.tcpipPortLabel.AutoSize = true; this.tcpipPortLabel.AutoSize = true;
this.tcpipPortLabel.Location = new System.Drawing.Point(46, 109); this.tcpipPortLabel.Location = new System.Drawing.Point(31, 71);
this.tcpipPortLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.tcpipPortLabel.Name = "tcpipPortLabel"; 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.TabIndex = 4;
this.tcpipPortLabel.Text = "Port nr..:"; this.tcpipPortLabel.Text = "Port nr..:";
// //
// tcpipPortTextBox // tcpipPortTextBox
// //
this.tcpipPortTextBox.Enabled = false; this.tcpipPortTextBox.Enabled = false;
this.tcpipPortTextBox.Location = new System.Drawing.Point(161, 105); this.tcpipPortTextBox.Location = new System.Drawing.Point(107, 68);
this.tcpipPortTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.tcpipPortTextBox.Name = "tcpipPortTextBox"; 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; this.tcpipPortTextBox.TabIndex = 5;
// //
// ipAddressLabel // ipAddressLabel
// //
this.ipAddressLabel.AutoSize = true; this.ipAddressLabel.AutoSize = true;
this.ipAddressLabel.Location = new System.Drawing.Point(46, 74); this.ipAddressLabel.Location = new System.Drawing.Point(31, 48);
this.ipAddressLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.ipAddressLabel.Name = "ipAddressLabel"; 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.TabIndex = 2;
this.ipAddressLabel.Text = "IP address.:"; this.ipAddressLabel.Text = "IP address.:";
// //
// ipAddressTextBox // ipAddressTextBox
// //
this.ipAddressTextBox.Enabled = false; this.ipAddressTextBox.Enabled = false;
this.ipAddressTextBox.Location = new System.Drawing.Point(161, 69); this.ipAddressTextBox.Location = new System.Drawing.Point(107, 45);
this.ipAddressTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.ipAddressTextBox.Name = "ipAddressTextBox"; 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; this.ipAddressTextBox.TabIndex = 3;
// //
// radioButton1 // radioButton1
@@ -326,10 +316,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
this.radioButton1.AutoSize = true; this.radioButton1.AutoSize = true;
this.radioButton1.Checked = true; this.radioButton1.Checked = true;
this.radioButton1.Enabled = false; this.radioButton1.Enabled = false;
this.radioButton1.Location = new System.Drawing.Point(33, 29); this.radioButton1.Location = new System.Drawing.Point(22, 19);
this.radioButton1.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.radioButton1.Name = "radioButton1"; 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.TabIndex = 0;
this.radioButton1.TabStop = true; this.radioButton1.TabStop = true;
this.radioButton1.Text = "Use TCP/IP"; this.radioButton1.Text = "Use TCP/IP";
@@ -339,10 +328,9 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
// //
this.radioButton2.AutoSize = true; this.radioButton2.AutoSize = true;
this.radioButton2.Enabled = false; this.radioButton2.Enabled = false;
this.radioButton2.Location = new System.Drawing.Point(351, 29); this.radioButton2.Location = new System.Drawing.Point(234, 19);
this.radioButton2.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.radioButton2.Name = "radioButton2"; 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.TabIndex = 1;
this.radioButton2.Text = "Use serial port"; this.radioButton2.Text = "Use serial port";
this.radioButton2.UseVisualStyleBackColor = true; this.radioButton2.UseVisualStyleBackColor = true;
@@ -350,108 +338,98 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader
// optoSerialPortLabel // optoSerialPortLabel
// //
this.optoSerialPortLabel.AutoSize = true; this.optoSerialPortLabel.AutoSize = true;
this.optoSerialPortLabel.Location = new System.Drawing.Point(361, 69); this.optoSerialPortLabel.Location = new System.Drawing.Point(241, 45);
this.optoSerialPortLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.optoSerialPortLabel.Name = "optoSerialPortLabel"; 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.TabIndex = 6;
this.optoSerialPortLabel.Text = "Serial port nr.:"; this.optoSerialPortLabel.Text = "Serial port nr.:";
// //
// optoSerialPortTextBox // optoSerialPortTextBox
// //
this.optoSerialPortTextBox.Enabled = false; this.optoSerialPortTextBox.Enabled = false;
this.optoSerialPortTextBox.Location = new System.Drawing.Point(494, 65); this.optoSerialPortTextBox.Location = new System.Drawing.Point(329, 42);
this.optoSerialPortTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.optoSerialPortTextBox.Name = "optoSerialPortTextBox"; 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; this.optoSerialPortTextBox.TabIndex = 7;
// //
// groupTextBox // groupTextBox
// //
this.groupTextBox.Enabled = false; this.groupTextBox.Enabled = false;
this.groupTextBox.Location = new System.Drawing.Point(172, 121); this.groupTextBox.Location = new System.Drawing.Point(115, 79);
this.groupTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.groupTextBox.Name = "groupTextBox"; 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; this.groupTextBox.TabIndex = 22;
// //
// groupLabel // groupLabel
// //
this.groupLabel.AutoSize = true; this.groupLabel.AutoSize = true;
this.groupLabel.Location = new System.Drawing.Point(7, 126); this.groupLabel.Location = new System.Drawing.Point(5, 82);
this.groupLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.groupLabel.Name = "groupLabel"; 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.TabIndex = 21;
this.groupLabel.Text = "Group 2"; this.groupLabel.Text = "Group 2";
// //
// muxBoardNrTextBox // muxBoardNrTextBox
// //
this.muxBoardNrTextBox.Enabled = false; this.muxBoardNrTextBox.Enabled = false;
this.muxBoardNrTextBox.Location = new System.Drawing.Point(172, 86); this.muxBoardNrTextBox.Location = new System.Drawing.Point(115, 56);
this.muxBoardNrTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.muxBoardNrTextBox.Name = "muxBoardNrTextBox"; 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; this.muxBoardNrTextBox.TabIndex = 20;
// //
// muxBoardNrLabel // muxBoardNrLabel
// //
this.muxBoardNrLabel.AutoSize = true; this.muxBoardNrLabel.AutoSize = true;
this.muxBoardNrLabel.Location = new System.Drawing.Point(7, 90); this.muxBoardNrLabel.Location = new System.Drawing.Point(5, 58);
this.muxBoardNrLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.muxBoardNrLabel.Name = "muxBoardNrLabel"; 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.TabIndex = 19;
this.muxBoardNrLabel.Text = "Group 1 (mux. board)"; this.muxBoardNrLabel.Text = "Group 1 (mux. board)";
// //
// nameTextBox // nameTextBox
// //
this.nameTextBox.Enabled = false; this.nameTextBox.Enabled = false;
this.nameTextBox.Location = new System.Drawing.Point(172, 50); this.nameTextBox.Location = new System.Drawing.Point(115, 32);
this.nameTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.nameTextBox.Name = "nameTextBox"; 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; this.nameTextBox.TabIndex = 17;
// //
// nameLabel // nameLabel
// //
this.nameLabel.AutoSize = true; this.nameLabel.AutoSize = true;
this.nameLabel.Location = new System.Drawing.Point(7, 55); this.nameLabel.Location = new System.Drawing.Point(5, 36);
this.nameLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.nameLabel.Name = "nameLabel"; 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.TabIndex = 16;
this.nameLabel.Text = "Name"; this.nameLabel.Text = "Name";
// //
// classNameLabel // classNameLabel
// //
this.classNameLabel.AutoSize = true; this.classNameLabel.AutoSize = true;
this.classNameLabel.Location = new System.Drawing.Point(168, 14); this.classNameLabel.Location = new System.Drawing.Point(112, 9);
this.classNameLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.classNameLabel.Name = "classNameLabel"; 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.TabIndex = 15;
this.classNameLabel.Text = "ClassName"; this.classNameLabel.Text = "ClassName";
// //
// tabPage2 // tabPage2
// //
this.tabPage2.Location = new System.Drawing.Point(4, 29); this.tabPage2.Location = new System.Drawing.Point(4, 22);
this.tabPage2.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.tabPage2.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
this.tabPage2.Name = "tabPage2"; this.tabPage2.Name = "tabPage2";
this.tabPage2.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4); this.tabPage2.Padding = new System.Windows.Forms.Padding(2, 3, 2, 3);
this.tabPage2.Size = new System.Drawing.Size(679, 507); this.tabPage2.Size = new System.Drawing.Size(450, 325);
this.tabPage2.TabIndex = 1; this.tabPage2.TabIndex = 1;
this.tabPage2.Text = "Test"; this.tabPage2.Text = "Test";
this.tabPage2.UseVisualStyleBackColor = true; this.tabPage2.UseVisualStyleBackColor = true;
// //
// GenesisCfgCtrl // 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.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.tabControl1); this.Controls.Add(this.tabControl1);
this.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.Name = "GenesisCfgCtrl"; 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.Load += new System.EventHandler(this.WaterMeterCfgCtrl_Load);
this.tabControl1.ResumeLayout(false); this.tabControl1.ResumeLayout(false);
this.tabPage1.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;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using GenesisCordonelInterface.API;
using log4net; using log4net;
using TBF.Rig.BridgeComponents.GciBridge; 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.RegisterReaders.GenesisRegReader.implementations;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed; using PublicModels = TBF.Rig.BridgeComponents.GciBridge.Interfaces.PublicModels;
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.protocolCommons;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication 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 public class RadioService
{ {
private static readonly ILog log = LogManager.GetLogger(typeof(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 okResponse = "Command complete, no errors";
static string errorResponse = "Unable to execute"; static string errorResponse = "Unable to execute";
private bool bConnected = false;
private GciBridge _bridge; private GciBridge _bridge;
public RadioService(GciBridge genesisHeadCommInterfaceBridgeComponent) public RadioService(GciBridge genesisHeadCommInterfaceBridgeComponent)
@@ -26,260 +41,492 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
log.Debug("RadioService created with GciBridge= " + genesisHeadCommInterfaceBridgeComponent + ""); 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) log.Debug("EnsureConnectedAsync called for iHead: " + head);
return null; if (head?.CommInterfaceBridge == 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)
{ {
// Timed out return new ReadPcbResult
return null; {
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; var result = await connectTask;
if (result == null || !result.Success || !result.IsConnected) if (result == null || !result.Success)
return null;
string pcbId = result.PcbId;
if (!string.IsNullOrEmpty(pcbId))
{ {
if (iHead.ConfigStruct != null) return new ReadPcbResult
iHead.ConfigStruct.PCBNumberString = pcbId; {
IsConnected = false,
return pcbId; 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) if (string.IsNullOrWhiteSpace(pcbId))
return null; 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("GetPasswordAsync PCB=" + pcbId + " Result: " + result);
{
log.Debug("ReadRequest_PCB() - calling ConnectAsync");
result = await iHead.CommInterfaceBridge return result;
.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) public async Task<GenesisCordonelInterface.API.PublicModels.GciLoginResult> LoginByPasswordAsync(
int slotId, string txtPassword,
CancellationToken token = default)
{ {
log.Debug("ReadRequest_PCB() - invalid result: " + result); if (string.IsNullOrWhiteSpace(txtPassword))
return null; 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;
} }
string pcbId = result.PcbId; public async Task<ReadPcbResult> ReadRequest_PCBAsync( GenesisSmartReader head, bool bReload = false)
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;
}
public string ReadRequest_PCB(ref GenesisSmartReader iHead)
{
var head = iHead; // <-- copy to local (no longer ref)
log.Debug("ReadRequest_PCB called for iHead: " + head); log.Debug("ReadRequest_PCB called for iHead: " + head);
if (head?.CommInterfaceBridge == null) if (head?.CommInterfaceBridge == null)
return null; {
return new ReadPcbResult
{
IsConnected = false,
IsValidPcb = false,
Message = "CommInterfaceBridge is null."
};
}
try try
{ {
var connectTask = Task.Run(async () => // CONNECT ONLY IF NEEDED
await head.CommInterfaceBridge.ConnectAsync(head.GetSlotNr) var connectResult = await EnsureConnectedAsync(head);
);
var completedTask = Task.WhenAny( if (!connectResult.IsConnected)
connectTask,
Task.Delay(TimeSpan.FromMinutes(1))
).GetAwaiter().GetResult();
if (completedTask != connectTask)
{ {
log.Debug("ReadRequest_PCB() - Timeout happened"); return connectResult;
return null;
} }
var result = connectTask.GetAwaiter().GetResult(); log.Debug($"ReadRequest_PCB() connect - {connectResult.Message}");
log.Debug("ReadRequest_PCB() - Result: " + result);
if (result == null || !result.Success || !result.IsConnected) //Check if exist PCB
return null; if (!bReload)
var pcbId = result.PcbId;
log.Debug("Result pcbId: " + pcbId);
if (!string.IsNullOrEmpty(pcbId) && head.ConfigStruct != null)
{ {
head.ConfigStruct.PCBNumberString = pcbId; var gciSlotInfo = await head.CommInterfaceBridge.GetSlotAsync(head.GetSlotNr);
log.Debug("Result set to ConfigStruct.PCBNumberString = " + head.ConfigStruct.PCBNumberString);
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) catch (Exception ex)
{ {
log.Error("ReadRequest_PCB() failed", ex); log.Error("ReadRequest_PCBAsync() failed", ex);
return null;
return new ReadPcbResult
{
IsConnected = false,
IsValidPcb = false,
PcbId = null,
Message = ex.Message
};
} }
} }
public string ReadRequest_PCB2(ref GenesisSmartReader iHead) public async Task<bool> PrepareLoginAdnConnect_Async(
GenesisSmartReader iHead,
bool isConnected,
CancellationToken token = default)
{ {
return ReadRequest_PCBAsync(iHead).GetAwaiter().GetResult(); log.Debug("PrepareLoginAdnConnect_Async called for iHead: " + iHead + " isConnected: " + isConnected);
}
public string ReadRequest_PCB1(ref GenesisSmartReader iHead)
{
log.Debug("ReadRequest_PCB called for iHead: " + iHead.ToString());
if (iHead?.CommInterfaceBridge == null) if (iHead?.CommInterfaceBridge == null)
return null; return false;
// -- connection -- try
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"); token.ThrowIfCancellationRequested();
return null;
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
var result = connectTask.GetAwaiter().GetResult(); if (string.IsNullOrWhiteSpace(pcb))
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))
{ {
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) if (iHead.ConfigStruct != null)
{ {
iHead.ConfigStruct.PCBNumberString = pcbId; iHead.ConfigStruct.PCBNumberString = pcb;
log.Debug("ReadRequest_PCB() - iHead.ConfigStruct.PCBNumberString: " + }
iHead.ConfigStruct.PCBNumberString); }
}
} }
return pcbId; if (string.IsNullOrWhiteSpace(pcb))
}
log.Debug("ReadRequest_PCB() - pcbId is empty");
return null;
}
public ProtocolStatuses GetActivityStatusMode(GenesisSmartReader iHead)
{ {
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;
}
catch (OperationCanceledException)
{
log.Debug("PrepareLoginAdnConnect_Async() canceled.");
return false;
}
}
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) if (iHead?.CommInterfaceBridge == null)
return ProtocolStatuses.Unknown; return LedState.Unknown;
var connectResult = iHead.CommInterfaceBridge try
.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)
{ {
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;
} }
return ProtocolStatuses.Active; 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;
}
} }
public DiagnosticLedState SetOptoStatusMode(GenesisSmartReader iHead, DiagnosticLedState opthoStatusMode) 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) if (iHead?.CommInterfaceBridge == null)
return DiagnosticLedState.StatusUnknown; return false;
var connectResult = iHead.CommInterfaceBridge try
.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)
{ {
iHead.ConfigStruct.PCBNumberString = pcbResult.PcbId; 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;
} }
return DiagnosticLedState.StatusUnknown; 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) private static ushort SafeIntToUShort(int value)
{ {
@@ -316,19 +563,27 @@ 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) if (iHead?.CommInterfaceBridge == null)
return false; return false;
var connectResult = iHead.CommInterfaceBridge try
.ConnectAsync(iHead.GetSlotNr) {
.GetAwaiter() token.ThrowIfCancellationRequested();
.GetResult();
if (connectResult == null || !connectResult.Success || !connectResult.IsConnected) var connectResult = await EnsureConnectedAsync(iHead, token);
if (!connectResult.IsConnected)
return false; return false;
isConnected = connectResult.IsConnected;
log.Debug($"SetActivityMode_Active() connect - {connectResult.Message}");
//Set LED to state 4 //Set LED to state 4
// string version = _bridge?.GciExternalInterface?.GetPcbId(iHead.GetSlotNr); // string version = _bridge?.GciExternalInterface?.GetPcbId(iHead.GetSlotNr);
// if (!string.IsNullOrEmpty(version)) // if (!string.IsNullOrEmpty(version))
@@ -339,6 +594,10 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
// } // }
// return true; // return true;
// } // }
}catch(OperationCanceledException)
{
return false;
}
return false; 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() public void Destroy()
{ {
if(Head != null && Head.OptoHeadTest != null)
{
Head.OptoHeadTest.CloseConnection();
}
stopWorkerThread = true; stopWorkerThread = true;
if (optoThread != null) if (optoThread != null)
{ {
@@ -216,6 +216,8 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
} }
public int ChannelsCount { get => iChanelsCount; }
private static int iChanelsCount = 3; private static int iChanelsCount = 3;
private int firstChanel; private int firstChanel;
@@ -1339,16 +1341,6 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
void DataStreamPostProcessing() void DataStreamPostProcessing()
{ {
PrepareCalculatedChannelData(); 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> /// <summary>
@@ -1882,7 +1874,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
_readLoopTask = Task.Run(() => _readLoopTask = Task.Run(() =>
{ {
log.Debug($"OPTHO {OptoComPortNr} background read loop started."); logStream.Debug($"OPTHO {OptoComPortNr} background read loop started.");
while (!token.IsCancellationRequested) while (!token.IsCancellationRequested)
{ {
@@ -1938,12 +1930,12 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
} }
catch (Exception ex) catch (Exception ex)
{ {
log.Error($"OPTHO {OptoComPortNr} background read error: {ex.Message}"); logStream.Error($"OPTHO {OptoComPortNr} background read error: {ex.Message}");
Thread.Sleep(100); Thread.Sleep(100);
} }
} }
log.Debug($"OPTHO {OptoComPortNr} background read loop stopped."); logStream.Debug($"OPTHO {OptoComPortNr} background read loop stopped.");
}, token); }, token);
} }
@@ -2024,7 +2016,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{ {
try try
{ {
log.Debug($"OPTHO {OptoComPortNr} processing loop started."); logStream.Debug($"OPTHO {OptoComPortNr} processing loop started.");
while (!token.IsCancellationRequested) while (!token.IsCancellationRequested)
{ {
@@ -2054,14 +2046,14 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
if (blockCompleted) if (blockCompleted)
{ {
log.Debug("Processing loop completed flow block detected."); logStream.Debug("Processing loop completed flow block detected.");
if (resetSerialBuffersOnCompletedFlowBlock) if (resetSerialBuffersOnCompletedFlowBlock)
ResetDataBuffer(); ResetDataBuffer();
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
log.Error($"Processing loop failed: {ex}"); logStream.Error($"Processing loop failed: {ex}");
} }
continue; continue;
@@ -2075,16 +2067,16 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
} }
catch (Exception ex) catch (Exception ex)
{ {
log.Error($"OPTHO {OptoComPortNr} processing loop error: {ex}"); logStream.Error($"OPTHO {OptoComPortNr} processing loop error: {ex}");
Thread.Sleep(50); Thread.Sleep(50);
} }
} }
log.Debug($"OPTHO {OptoComPortNr} processing loop stopped."); logStream.Debug($"OPTHO {OptoComPortNr} processing loop stopped.");
} }
catch (Exception ex) catch (Exception ex)
{ {
log.Error($"StartProcessingLoop fatal error: {ex}"); logStream.Error($"StartProcessingLoop fatal error: {ex}");
} }
}, token); }, token);
} }
@@ -2113,13 +2105,13 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
// 🔴 STEP 1: Check if we should start processing // 🔴 STEP 1: Check if we should start processing
if (startDataProcessing && optoState == DataStreamState.ProcessAndSave) 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; bool blockCompleted;
ProcessOptoLine(line, optoState, out blockCompleted); ProcessOptoLine(line, optoState, out blockCompleted);
if (blockCompleted) if (blockCompleted)
{ {
log.Debug("ReadOptoData() completed flow block detected."); logStream.Debug("ReadOptoData() completed flow block detected.");
if (resetSerialBuffersOnCompletedFlowBlock) // DO NOT call ResetDataBuffer() here if (resetSerialBuffersOnCompletedFlowBlock) // DO NOT call ResetDataBuffer() here
ResetDataBuffer(); ResetDataBuffer();
} }
@@ -2127,7 +2119,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
} }
catch (Exception ex) catch (Exception ex)
{ {
log.Error($"OPTHO {OptoComPortNr} processing queued line failed: {ex.Message}"); logStream.Error($"OPTHO {OptoComPortNr} processing queued line failed: {ex.Message}");
} }
} }
} }
@@ -2154,13 +2146,13 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
if (streamingDecode.DataFlowTest != null && streamingDecode.DataFlowTest.IsValid) if (streamingDecode.DataFlowTest != null && streamingDecode.DataFlowTest.IsValid)
{ {
blockCompleted = HandleFlowMarker(); blockCompleted = HandleFlowMarker();
log.Debug("ComPort: " + OptoComPortNr + " Decoded Flow data: " + streamingDecode.DataFlowTest + logStream.Debug("ComPort: " + OptoComPortNr + " Decoded Flow data: " + streamingDecode.DataFlowTest +
" OPTHO RX ← " + HexFormatter.ToSerialHex(bytes)); " OPTHO RX ← " + HexFormatter.ToSerialHex(bytes));
} }
if (calibData != null && calibData.IsValid) if (calibData != null && calibData.IsValid)
{ {
log.Debug("ComPort: " + OptoComPortNr + " Decoded Calib: " + calibData + " OPTHO RX ← " + logStream.Debug("ComPort: " + OptoComPortNr + " Decoded Calib: " + calibData + " OPTHO RX ← " +
HexFormatter.ToSerialHex(bytes)); HexFormatter.ToSerialHex(bytes));
MarkCalibrationChannelSeen(calibData.Channel); MarkCalibrationChannelSeen(calibData.Channel);
@@ -2181,7 +2173,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
if (optoData[bufferIx] == null) 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(); optoData[bufferIx] = new OptoTelegramRaw();
} }
@@ -2194,7 +2186,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
int iChanel = calibData.Channel - 1; int iChanel = calibData.Channel - 1;
if (iChanel >= 0 && iChanel < iChanelsCount) if (iChanel >= 0 && iChanel < iChanelsCount)
{ {
log.Debug( logStream.Debug(
$"Before UpdateFromSmart ch={iChanel + 1}: " + $"Before UpdateFromSmart ch={iChanel + 1}: " +
$"volumeRawExtLast={volumeRawExtLast[iChanel]}, " + $"volumeRawExtLast={volumeRawExtLast[iChanel]}, " +
$"timestampExtLast={timestampExtLast[iChanel]}, " + $"timestampExtLast={timestampExtLast[iChanel]}, " +
@@ -2262,7 +2254,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
var encoding = optoSerialPort?.Encoding ?? Encoding.ASCII; var encoding = optoSerialPort?.Encoding ?? Encoding.ASCII;
byte[] bytes = encoding.GetBytes(line); byte[] bytes = encoding.GetBytes(line);
received = HexFormatter.ToSerialHex(bytes); received = HexFormatter.ToSerialHex(bytes);
log.Debug("RX ← " + received); logStream.Debug("RX ← " + received);
try try
{ {
@@ -2271,7 +2263,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
CalibrationRecord data = _streamingDecode.DataCalib; CalibrationRecord data = _streamingDecode.DataCalib;
if (data != null && data.IsValid) if (data != null && data.IsValid)
{ {
log.Info($"OPTHO {OptoComPortNr} DataCalib Parsed opto data: " + data + " RX ← " + logStream.Info($"OPTHO {OptoComPortNr} DataCalib Parsed opto data: " + data + " RX ← " +
received); received);
MarkCalibrationChannelSeen(data.Channel); MarkCalibrationChannelSeen(data.Channel);
} }
@@ -2279,11 +2271,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
FlowTestRecord dataFlow = _streamingDecode.DataFlowTest; FlowTestRecord dataFlow = _streamingDecode.DataFlowTest;
if (dataFlow != null && dataFlow.IsValid) 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()) if (HandleFlowMarker())
{ {
log.Debug("ReadOptoData() completed flow block detected."); logStream.Debug("ReadOptoData() completed flow block detected.");
if (resetSerialBuffersOnCompletedFlowBlock) if (resetSerialBuffersOnCompletedFlowBlock)
ResetDataBuffer(); // no ResetDataBuffer() here ResetDataBuffer(); // no ResetDataBuffer() here
} }
@@ -2292,7 +2284,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
} }
catch (Exception ex) catch (Exception ex)
{ {
log.Error($"OPTHO {OptoComPortNr} Read error: {ex.Message}"); logStream.Error($"OPTHO {OptoComPortNr} Read error: {ex.Message}");
} }
// string line = optoSerialPort.ReadExisting(); // string line = optoSerialPort.ReadExisting();
@@ -2337,11 +2329,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
} }
catch (TimeoutException) catch (TimeoutException)
{ {
log.Debug($"ReadOptoData() OPTHO {OptoComPortNr} timeout - continuing."); logStream.Debug($"ReadOptoData() OPTHO {OptoComPortNr} timeout - continuing.");
} }
catch (Exception ex) 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.DiscardInBuffer();
optoSerialPort.DiscardOutBuffer(); optoSerialPort.DiscardOutBuffer();
log.Debug("-- Reaset Data Buffer --"); logStream.Debug("-- Reaset Data Buffer --");
return; return;
} }
} }
log.Debug("-- Reaset Data Buffer - no serial port --"); logStream.Debug("-- Reaset Data Buffer - no serial port --");
} }
void ISmartReader.SetNfcInterface() void ISmartReader.SetNfcInterface()
@@ -2387,17 +2379,17 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
byte[] bytes = optoSerialPort.Encoding.GetBytes(line); byte[] bytes = optoSerialPort.Encoding.GetBytes(line);
string received = HexFormatter.ToSerialHex(bytes); string received = HexFormatter.ToSerialHex(bytes);
log.Debug("RX ← " + received); logStream.Debug("RX ← " + received);
return line; return line;
} }
} }
catch (TimeoutException) catch (TimeoutException)
{ {
log.Debug($"ReadOptoData() OPTHO {OptoComPortNr} ReadLine timeout (3s) - continuing."); logStream.Debug($"ReadOptoData() OPTHO {OptoComPortNr} ReadLine timeout (3s) - continuing.");
} }
catch (Exception ex) catch (Exception ex)
{ {
log.Error($"ReadOptoData() OPTHO {OptoComPortNr} Read error: {ex.Message}"); logStream.Error($"ReadOptoData() OPTHO {OptoComPortNr} Read error: {ex.Message}");
} }
return string.Empty; return string.Empty;
@@ -2408,7 +2400,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
if (completedTask == readTask) if (completedTask == readTask)
return await readTask; return await readTask;
log.Debug("ReadOptoData timeout after " + timeoutMs + " ms"); logStream.Debug("ReadOptoData timeout after " + timeoutMs + " ms");
return string.Empty; return string.Empty;
} }
@@ -2960,7 +2952,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
if (data == null || !data.IsValid) if (data == null || !data.IsValid)
continue; continue;
log.Info($"OPTHO {OptoComPortNr} Parsed optho data:" + data); logStream.Info($"OPTHO {OptoComPortNr} Parsed optho data:" + data);
int dch = data.Channel - 1; int dch = data.Channel - 1;
if (dch >= 0 && dch < iChanelsCount) if (dch >= 0 && dch < iChanelsCount)
@@ -2973,7 +2965,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
} }
catch (Exception ex) 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 (optoSerialPort != null && optoSerialPort.IsOpen) CloseOptoSerialPort();
if (!Double.IsNaN(volumeLtr[ch])) if (!Double.IsNaN(volumeLtr[ch]))
@@ -2995,12 +2987,12 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
//Solve roll over //Solve roll over
if (endWMState < beginWMState) 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 const double VOL_RANGE_LITERS = 16777216.0 * 0.00025; // 4,194.304 l
endWMState += VOL_RANGE_LITERS; endWMState += VOL_RANGE_LITERS;
volumeLtr[ch] = endWMState; volumeLtr[ch] = endWMState;
ReadPulses(); ReadPulses();
log.Debug( logStream.Debug(
$"Solve roll over! Upgraded endWMState: {endWMState}, beginWMState: {beginWMState}"); $"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; return Double.NaN;
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
@@ -3019,14 +3011,14 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
{ {
if (ConfigStruct == null) if (ConfigStruct == null)
{ {
log.Debug("ConfigStruct is null - created new in ReadSerialNr()"); logStream.Debug("ConfigStruct is null - created new in ReadSerialNr()");
ConfigStruct = new ConfigStruct(); ConfigStruct = new ConfigStruct();
} }
return await Task.Run(() => return await Task.Run(() =>
{ {
log.Debug($"Try get Start Volume! COM: {this.OptoComPortNr}"); logStream.Debug($"Try get Start Volume! COM: {this.OptoComPortNr}");
Start(); Start();
@@ -3050,7 +3042,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
StreamingDecoder _streamingDecode = new StreamingDecoder(true); StreamingDecoder _streamingDecode = new StreamingDecoder(true);
_streamingDecode.DecodeMsg(readOptoDataWithTimeout); _streamingDecode.DecodeMsg(readOptoDataWithTimeout);
CalibrationRecord data = _streamingDecode.DataCalib; 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) if (data == null || !data.IsValid)
continue; continue;
@@ -3066,7 +3058,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
} }
catch (Exception ex) 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 (optoSerialPort != null && optoSerialPort.IsOpen) CloseOptoSerialPort();
if (!Double.IsNaN(volumeLtr0[ch])) 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; return Double.NaN;
}).ConfigureAwait(false); }).ConfigureAwait(false);
} }
@@ -3099,7 +3091,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
if (ConfigStruct == null) if (ConfigStruct == null)
{ {
log.Debug("ConfigStruct is null - created new in ReadSerialNr()"); logStream.Debug("ConfigStruct is null - created new in ReadSerialNr()");
ConfigStruct = new ConfigStruct(); ConfigStruct = new ConfigStruct();
} }
@@ -3109,11 +3101,11 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
return await Task.Run(() => return await Task.Run(() =>
{ {
log.Debug($"Try get ReadSerialNr! COM: {this.RfidComPortNr}"); logStream.Debug($"Try get ReadSerialNr! COM: {this.RfidComPortNr}");
SerialNr = OptoHeadTest.ReadRequest_PCB(); SerialNr = OptoHeadTest.ReadRequest_PCB();
if (string.IsNullOrEmpty(SerialNr)) if (string.IsNullOrEmpty(SerialNr))
{ {
log.Debug("ReadSerialNr successful"); logStream.Debug("ReadSerialNr successful");
} }
//optoHeadTest.CloseConnection(); //optoHeadTest.CloseConnection();
@@ -3898,7 +3890,8 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
_startupFlushActive = false; _startupFlushActive = false;
log.WarnFormat( log.WarnFormat(
"Startup flush finished. Ignored {0} incoming opto lines.", "Startup flush finished. ({0}) Ignored {1} incoming opto lines.",
Name,
_startupFlushIgnoredLines); _startupFlushIgnoredLines);
} }
} }
@@ -3915,7 +3908,8 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
_startupFlushActive = false; _startupFlushActive = false;
log.WarnFormat( 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, _startupFlushIgnoredLines,
_startupFlushFirstIgnoredUtc, _startupFlushFirstIgnoredUtc,
_startupFlushLastIgnoredUtc); _startupFlushLastIgnoredUtc);
@@ -3934,9 +3928,13 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
} }
#endregion #endregion
/// <summary>Compatibility limit used when the Write Q3 Calibration activity has no error limits configured.</summary>
public const double DefaultQ3CalibrationFactorErrorLimit = 5.0;
private double[] q3CalibInitial = {Double.NaN,Double.NaN,Double.NaN}; private double[] q3CalibInitial = {Double.NaN,Double.NaN,Double.NaN};
private bool[] isChQ3CalibValid = { false,false,false}; private bool[] isChQ3CalibValid = { false,false,false};
private double[] q3CalibCh = {Double.NaN,Double.NaN,Double.NaN}; private double[] q3CalibCh = {Double.NaN,Double.NaN,Double.NaN};
private double[] q3DiffPercentageCalibCh = {Double.NaN,Double.NaN,Double.NaN};
public bool Q3CalibValid public bool Q3CalibValid
@@ -3954,6 +3952,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
} }
public double[] Q3CalibValue { get => q3CalibInitial; } public double[] Q3CalibValue { get => q3CalibInitial; }
public double[] Q3CalibDiffPercentageValue { get => q3DiffPercentageCalibCh; }
public bool Q3Calib_Ch1Valid { get => isChQ3CalibValid[0]; } public bool Q3Calib_Ch1Valid { get => isChQ3CalibValid[0]; }
public bool Q3Calib_Ch2Valid { get => isChQ3CalibValid[1]; } public bool Q3Calib_Ch2Valid { get => isChQ3CalibValid[1]; }
@@ -3964,14 +3963,100 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
public int GetSlotNr { get => genesisHeadCfg?.SlotNr ?? -1; } public int GetSlotNr { get => genesisHeadCfg?.SlotNr ?? -1; }
void SetQ3Calibration(double[] q3CalibInitial) { this.q3CalibInitial = q3CalibInitial; } //TODO BUMI implement variable values for Q3Channel!
public void SetQ3Calibration(double[] q3CalibInitial)
{
if (q3CalibInitial == null || q3CalibInitial.Length != 3 ||
Array.Exists(q3CalibInitial, x => double.IsNaN(x) || double.IsInfinity(x) || x < 1 || x > ushort.MaxValue))
throw new ArgumentException("Three valid Genesis calibration factors are required.", nameof(q3CalibInitial));
this.q3CalibInitial = (double[])q3CalibInitial.Clone();
refVolume = double.NaN;
refTime = double.NaN;
Array.Clear(isChQ3CalibValid, 0, isChQ3CalibValid.Length);
}
private double refVolume = double.NaN;
private double refTime = double.NaN;
public double RefVolume { get => refVolume; set => refVolume = value; }
public double RefTime { get => refTime; set => refTime = value; }
public bool CalculateQ3Calibration()
{
return CalculateQ3CalibrationWithErrorLimits(-DefaultQ3CalibrationFactorErrorLimit,
DefaultQ3CalibrationFactorErrorLimit);
}
/// <summary>
/// Calculates Q3 factors using the limits configured on the procedure activity that writes them.
/// A pair for which the high limit is not greater than the low limit (the normal unset 0 / 0
/// value) falls back to the historical +/- 5 % validation.
/// </summary>
public bool CalculateQ3CalibrationWithErrorLimits(double errorLimitLo, double errorLimitHi)
{
if (Double.IsNaN(refVolume) || Double.IsInfinity(refVolume) || refVolume <= 0 || Double.IsNaN(refTime) || Double.IsInfinity(refTime) || refTime <= 0)
{
log.Debug("RefVolume or RefTime is NaN");
return false;
}
CalculateQ3Calibration(refVolume, refTime, errorLimitLo, errorLimitHi);
return true;
}
public void CalculateQ3Calibration(double refVolume, double refTime) public void CalculateQ3Calibration(double refVolume, double refTime)
{ {
GetQ3Calibration(refVolume, refTime, q3CalibInitial, ref isChQ3CalibValid, ref q3CalibCh); CalculateQ3Calibration(refVolume, refTime, -DefaultQ3CalibrationFactorErrorLimit,
DefaultQ3CalibrationFactorErrorLimit);
}
public void CalculateQ3Calibration(double refVolume, double refTime, double errorLimitLo, double errorLimitHi)
{
double effectiveErrorLimitLo;
double effectiveErrorLimitHi;
TryGetEffectiveQ3CalibrationErrorLimits(errorLimitLo, errorLimitHi,
out effectiveErrorLimitLo, out effectiveErrorLimitHi);
GetQ3Calibration(refVolume, refTime, q3CalibInitial, effectiveErrorLimitLo,
effectiveErrorLimitHi, ref isChQ3CalibValid, ref q3DiffPercentageCalibCh, ref q3CalibCh);
} }
public void GetQ3Calibration(double refVolume, double refTime, double[] initCalibFactor, ref bool[] isChQ3CalibValid, ref double[] q3CalibCh) public void GetQ3Calibration(double refVolume, double refTime, double[] initCalibFactor, ref bool[] isChQ3CalibValid, ref double[] q3CalibCh)
{
var differences = new double[3];
GetQ3Calibration(refVolume, refTime, initCalibFactor, ref isChQ3CalibValid, ref differences, ref q3CalibCh);
}
public void GetQ3Calibration(double refVolume, double refTime, double[] initCalibFactor, ref bool[] isChQ3CalibValid, ref double[] calibDiffPercent, ref double[] q3CalibCh)
{
GetQ3Calibration(refVolume, refTime, initCalibFactor,
-DefaultQ3CalibrationFactorErrorLimit, DefaultQ3CalibrationFactorErrorLimit,
ref isChQ3CalibValid, ref calibDiffPercent, ref q3CalibCh);
}
/// <summary>
/// Resolves the error limits used to validate a calculated Q3 factor. TBF stores an unset
/// limit pair as equal values (normally 0 / 0), so only an ordered finite pair is considered set.
/// </summary>
public static bool TryGetEffectiveQ3CalibrationErrorLimits(double errorLimitLo, double errorLimitHi,
out double effectiveErrorLimitLo, out double effectiveErrorLimitHi)
{
if (!Double.IsNaN(errorLimitLo) && !Double.IsInfinity(errorLimitLo) &&
!Double.IsNaN(errorLimitHi) && !Double.IsInfinity(errorLimitHi) &&
errorLimitHi > errorLimitLo)
{
effectiveErrorLimitLo = errorLimitLo;
effectiveErrorLimitHi = errorLimitHi;
return true;
}
effectiveErrorLimitLo = -DefaultQ3CalibrationFactorErrorLimit;
effectiveErrorLimitHi = DefaultQ3CalibrationFactorErrorLimit;
return false;
}
public void GetQ3Calibration(double refVolume, double refTime, double[] initCalibFactor,
double errorLimitLo, double errorLimitHi, ref bool[] isChQ3CalibValid,
ref double[] calibDiffPercent, ref double[] q3CalibCh)
{ {
log.Debug("=== Q3 CALIBRATION START ==="); log.Debug("=== Q3 CALIBRATION START ===");
@@ -3991,7 +4076,7 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
} }
} }
log.Debug($"Inputs: refVolume={refVolume}, refTime={refTime}, initCalibFactor={initCalibFactor}"); log.Debug($"Inputs: refVolume={refVolume}, refTime={refTime}, initCalibFactors={string.Join(",", initCalibFactor)}, errorLimits={errorLimitLo}..{errorLimitHi}%");
if (_rawStartEndByChannel == null) if (_rawStartEndByChannel == null)
{ {
@@ -4080,26 +4165,20 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
} }
q3CalibCh[iChannel] = (refVolume / recalculatedDeltaVolume) * initCalibFactor[iChannel]; q3CalibCh[iChannel] = (refVolume / recalculatedDeltaVolume) * initCalibFactor[iChannel];
double diffPercent = Math.Abs((initCalibFactor[iChannel] - q3CalibCh[iChannel] ) / initCalibFactor[iChannel]) * 100.0; // Procedure error limits are an ordered range, e.g. -2 .. +2, so validation
isChQ3CalibValid[iChannel] = diffPercent <= 5.0; // must retain the direction of the factor change. Keep the persisted difference
log.Debug($"Calculated Q3Calib Ch[{iChannel}] ={q3CalibCh[iChannel]} DiffPercent={diffPercent}% isValid[{isChQ3CalibValid[iChannel]}] IninitCalibFactor={initCalibFactor}"); // absolute for compatibility with the existing calibration-result fields.
double signedDiffPercent = ((q3CalibCh[iChannel] - initCalibFactor[iChannel]) / initCalibFactor[iChannel]) * 100.0;
double diffPercent = Math.Abs(signedDiffPercent);
isChQ3CalibValid[iChannel] = signedDiffPercent >= errorLimitLo && signedDiffPercent <= errorLimitHi &&
!double.IsNaN(q3CalibCh[iChannel]) && !double.IsInfinity(q3CalibCh[iChannel]) &&
q3CalibCh[iChannel] >= 1 && q3CalibCh[iChannel] <= ushort.MaxValue;
calibDiffPercent[iChannel] = diffPercent;
log.Debug($"Calculated Q3Calib Ch[{iChannel}] ={q3CalibCh[iChannel]} SignedDiffPercent={signedDiffPercent}% DiffPercent={diffPercent}% isValid[{isChQ3CalibValid[iChannel]}] IninitCalibFactor={initCalibFactor}");
} }
log.Debug("=== Q3 CALIBRATION END ==="); log.Debug("=== Q3 CALIBRATION END ===");
} }
void newPokus()
{
//TODO BUMI implement genesis communication
//volat z GCI Bridge
//vybere sa component - GCI bridge
// - rozhranie
// - database
}
} }
} }
+19 -2
View File
@@ -67,7 +67,7 @@ namespace TBF.Rig.Sequences
// //
// /// 3th argument // /// 3th argument
// IList<ITestParams> iPerlCommParams = new List<ITestParams>(); // 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 = new TestMethods.iPerlCommunication.iPerlCommunicationForm(iPerlCfg, tests, iPerlCommParams);
// myRef.modelessDlg.Show();*/ // myRef.modelessDlg.Show();*/
@@ -75,6 +75,14 @@ namespace TBF.Rig.Sequences
// myRef.modelessDlg = new SmartCommunicationForm( testMethod , tests, iPerlCommParams); // myRef.modelessDlg = new SmartCommunicationForm( testMethod , tests, iPerlCommParams);
// myRef.modelessDlg.Show(); // 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 /// 1nd argument
TestMethods.iPerlCommunication.TestMethodCfg iPerlCfg = cfg as TestMethods.iPerlCommunication.TestMethodCfg; TestMethods.iPerlCommunication.TestMethodCfg iPerlCfg = cfg as TestMethods.iPerlCommunication.TestMethodCfg;
@@ -88,7 +96,7 @@ namespace TBF.Rig.Sequences
myRef.modelessDlg.Show();*/ myRef.modelessDlg.Show();*/
myRef.modelessDlg = new TestMethods.iPerlCommunication.iPerlCommunicationForm( 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(); myRef.modelessDlg.Show();
} }
catch (Exception e) catch (Exception e)
@@ -1477,6 +1485,7 @@ namespace TBF.Rig.Sequences
for (int wmNr0 = BatchRslts.Batch.WaterMeters.Count - 1; wmNr0 >= 0; wmNr0--) for (int wmNr0 = BatchRslts.Batch.WaterMeters.Count - 1; wmNr0 >= 0; wmNr0--)
{ {
var wm = BatchRslts.Batch.WaterMeters[wmNr0]; var wm = BatchRslts.Batch.WaterMeters[wmNr0];
if (wm.Q3Channel == 0){
if (wm.Disabled) if (wm.Disabled)
{ {
/// Do not save disabled watermeters to DB, remove them from the list /// Do not save disabled watermeters to DB, remove them from the list
@@ -1488,6 +1497,14 @@ namespace TBF.Rig.Sequences
wm.Passed = wm.PassedFromTests(); wm.Passed = wm.PassedFromTests();
} }
} }
else
{
if (!wm.Disabled)
{
wm.Passed = wm.PassedFromTests();
}
}
}
//------------------------------------------------------ //------------------------------------------------------
+4 -1
View File
@@ -1305,7 +1305,10 @@ namespace TBF.Rig.Sequences
var smryItems = DEItem.GetSummaryColumns(); 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 && if (BatchRslts.Batch.WaterMeters != null &&
BatchRslts.Batch.WaterMeters.Count > i && BatchRslts.Batch.WaterMeters.Count > i &&
+1 -1
View File
@@ -198,7 +198,7 @@ namespace TBF.Rig
new TestMethods.FlyingStartFirstRepetWithMassColl.HeatMeters.Factory(), new TestMethods.FlyingStartFirstRepetWithMassColl.HeatMeters.Factory(),
new TestMethods.FlyingStartTankCollection.Single.Factory(), new TestMethods.FlyingStartTankCollection.Single.Factory(),
new TestMethods.FlyingStartTankCollection.Compound.Factory(), new TestMethods.FlyingStartTankCollection.Compound.Factory(),
//new TestMethods.GenesisCommunication.GenesisHead.Factory(), new TestMethods.GenesisCommunication.Factory(),
new TestMethods.GrabImage.Factory(), new TestMethods.GrabImage.Factory(),
new TestMethods.iPerlCommunication.TestMethodFactory(), /// iPerlCommunication new TestMethods.iPerlCommunication.TestMethodFactory(), /// iPerlCommunication
new TestMethods.LeakTest.Factory(), new TestMethods.LeakTest.Factory(),
@@ -12,6 +12,8 @@ using TBF.Rig;
using TBF.Rig.GenericDevices; using TBF.Rig.GenericDevices;
using TBF.Boxes; using TBF.Boxes;
using TBF.Resources; using TBF.Resources;
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
using TBF.Rig.Sequences;
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common; using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
using TBF.UiBridge; 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 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 /// Measurement loop end
StopRecordingStatistics(); StopRecordingStatistics();
@@ -532,7 +539,7 @@ namespace TBF.Rig.TestMethods.FlyingStart
tstRslt.TimeBtwnMassMsrmnts = 0; tstRslt.TimeBtwnMassMsrmnts = 0;
tstRslt.ConstMasterRaw = outPath.FlowMeter.LtrPerPulse; /// Uncorrected master flowmeter coefficient tstRslt.ConstMasterRaw = outPath.FlowMeter.LtrPerPulse; /// Uncorrected master flowmeter coefficient
tstRslt.VolumeMaster = tstRslt.ConstMasterRaw * tstRslt.PulsesMaster; /// [l] volume from the master flow meter 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 /// Corrected data
tstRslt.MassStart = 0; tstRslt.MassStart = 0;
@@ -542,7 +549,7 @@ namespace TBF.Rig.TestMethods.FlyingStart
/// Main result calculation /// Main result calculation
tstRslt.VolumeCTV = tstRslt.ConstMasterCorr * tstRslt.PulsesMaster; /// [l] 1000.0f is because density is in [kg/m3] 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.ErrorMaster = 0.0; /// Not available without a mass measurement
tstRslt.ConstMaster = tstRslt.ConstMasterCorr; tstRslt.ConstMaster = tstRslt.ConstMasterCorr;
@@ -660,6 +667,7 @@ namespace TBF.Rig.TestMethods.FlyingStart
TestMethods.iPerlCommunication.iPerlHead.IperlHead iPerl = regReader as TestMethods.iPerlCommunication.iPerlHead.IperlHead; TestMethods.iPerlCommunication.iPerlHead.IperlHead iPerl = regReader as TestMethods.iPerlCommunication.iPerlHead.IperlHead;
GenericDevices.IRegReaderLiveCamera cameraRoi = regReader as GenericDevices.IRegReaderLiveCamera; GenericDevices.IRegReaderLiveCamera cameraRoi = regReader as GenericDevices.IRegReaderLiveCamera;
//GenesisHead Genesis = regReader as GenesisHead; //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) if (meterRslt != null && regReader != null)
{ {
@@ -711,6 +719,24 @@ namespace TBF.Rig.TestMethods.FlyingStart
Genesis.Log(" MeterError = " + calError.ToString() + " %"); Genesis.Log(" MeterError = " + calError.ToString() + " %");
} }
else*/ 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) 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, IList<Event> Simulate(Config.Entities.Test test, int repetitionNr, bool isLastRepetition,
Compound.TestParams compoundTestParams, 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 if (test.Name.ToLower().Contains("q1")) MakeSimulated(test, 1, 0, -5.1f);
else MakeSimulated(test, 1, 0, 0.9f); 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.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))); 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 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 }; 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.GenesisRegReader.implementations;
using TBF.Rig.RegisterReaders.PoseidonReader; using TBF.Rig.RegisterReaders.PoseidonReader;
using TBF.Rig.RegisterReaders.PoseidonReader.implementations; using TBF.Rig.RegisterReaders.PoseidonReader.implementations;
using TBF.Rig.Sequences;
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common; using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
using TBF.UiBridge; using TBF.UiBridge;
@@ -1106,12 +1107,13 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
bUpgradeCountOfMeters = true; bUpgradeCountOfMeters = true;
int CH1=0, CH2=1, CH3=2; 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); 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, CH1,testName,meterRslt,GenesisSmart,tstRslt);
WaterMeterParentCopy(i, CH2,testName,meterRslt,GenesisSmart,tstRslt); WaterMeterParentCopy(i, CH2,testName,meterRslt,GenesisSmart,tstRslt);
WaterMeterParentCopy(i, CH3,testName,meterRslt,GenesisSmart,tstRslt); WaterMeterParentCopy(i, CH3,testName,meterRslt,GenesisSmart,tstRslt);
@@ -1179,6 +1181,7 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
} }
#if IPERL #if IPERL
meterRslt.WaterMeter.CalibFactor = iPerl.CalibFactor; meterRslt.WaterMeter.CalibFactor = iPerl.CalibFactor;
meterRslt.WaterMeter.CalibFactorNominal = iPerl.CalibFactorNominal;
meterRslt.FlipMode = iPerl.ConfigStruct != null && iPerl.ConfigStruct.FlipMode.HasValue meterRslt.FlipMode = iPerl.ConfigStruct != null && iPerl.ConfigStruct.FlipMode.HasValue
? (int?)iPerl.ConfigStruct.FlipMode.Value ? (int?)iPerl.ConfigStruct.FlipMode.Value
: null; : null;
@@ -1374,10 +1377,14 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
private static void WaterMeterParentCopy(int i, int iCH, string testName, MeterTestRslt meterRslt, private static void WaterMeterParentCopy(int i, int iCH, string testName, MeterTestRslt meterRslt,
GenesisSmartReader genesisSmart, GenesisSmartReader genesisSmart,
TestRslt tstRslt) TestRslt tstRslt)
{
try
{ {
WaterMeter waterMeterParent = BatchRslts.Batch.WaterMeters[i]; WaterMeter waterMeterParent = BatchRslts.Batch.WaterMeters[i];
int wmNrChX = (i * CountCh) + BatchRslts.WMPositionsCount + iCH + 1; int wmNrChX = (i * CountCh) + BatchRslts.WMPositionsCount + iCH + 1;
String sSerialNr = waterMeterParent.SerialNr + "_CH" + (iCH + 1); String sSerialNr =
(string.IsNullOrEmpty(waterMeterParent.SerialNr) ? (i+1).ToString() : waterMeterParent.SerialNr) +
"_CH" + (iCH + 1);
WaterMeter chXWaterMeter = null; WaterMeter chXWaterMeter = null;
//------ add new water meter to batch ------ //------ add new water meter to batch ------
if (BatchRslts.Batch.WaterMeters.Count < wmNrChX || BatchRslts.Batch.WaterMeters[wmNrChX - 1] == null) if (BatchRslts.Batch.WaterMeters.Count < wmNrChX || BatchRslts.Batch.WaterMeters[wmNrChX - 1] == null)
@@ -1385,68 +1392,177 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
log.Debug("add new water meter to batch, CH = " + (iCH + 1) + " CH = " + wmNrChX); log.Debug("add new water meter to batch, CH = " + (iCH + 1) + " CH = " + wmNrChX);
chXWaterMeter = new WaterMeter() chXWaterMeter = new WaterMeter()
{ {
Batch = BatchRslts.Batch,
WaterMeterData = waterMeterParent.WaterMeterData,
MeterTestRslts = new List<MeterTestRslt>(), MeterTestRslts = new List<MeterTestRslt>(),
SerialNr = sSerialNr,
WMPosition = wmNrChX,
YearOfProduction = 0,
Disabled = false,
}; };
//This will delete each setting before
chXWaterMeter.CopyContentFrom(waterMeterParent); chXWaterMeter.CopyContentFrom(waterMeterParent);
chXWaterMeter.Batch = BatchRslts.Batch;
chXWaterMeter.WaterMeterData = waterMeterParent.WaterMeterData;
chXWaterMeter.SerialNr = sSerialNr; chXWaterMeter.SerialNr = sSerialNr;
chXWaterMeter.WMPosition = wmNrChX; chXWaterMeter.WMPosition = wmNrChX;
chXWaterMeter.Q3Channel = iCH+1;
chXWaterMeter.YearOfProduction = waterMeterParent.YearOfProduction; chXWaterMeter.YearOfProduction = waterMeterParent.YearOfProduction;
chXWaterMeter.Disabled = false; chXWaterMeter.Disabled = false;
BatchRslts.Batch.WaterMeters.Add(chXWaterMeter); BatchRslts.Batch.WaterMeters.Add(chXWaterMeter);
} }
else else
{ {
chXWaterMeter = BatchRslts.Batch.WaterMeters[wmNrChX - 1]; 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 ------~ //~------ add new water meter to batch ------~
//create copy of meterRslt and add to additionalResultsByChannel //create copy of meterRslt and add to additionalResultsByChannel
MeterTestRslt meterTestRsltChX = new MeterTestRslt(chXWaterMeter, meterRslt.TestRslt, (CompoundMeterId)meterRslt.CompoundMeterId); MeterTestRslt meterTestRsltChX = new MeterTestRslt(chXWaterMeter, meterRslt.TestRslt,
(CompoundMeterId)meterRslt.CompoundMeterId);
meterTestRsltChX.Q3Channel = iCH + 1;
chXWaterMeter.MeterTestRslts.Add(meterTestRsltChX); chXWaterMeter.MeterTestRslts.Add(meterTestRsltChX);
MeterTestRslt chanelXMeterRslt = BatchRslts.GetMeterTestRslt(testName, wmNrChX-1, Common.CompoundMeterId.Single); MeterTestRslt chanelXMeterRslt =
BatchRslts.GetMeterTestRslt(testName, wmNrChX - 1, Common.CompoundMeterId.Single);
if (chanelXMeterRslt != null) if (chanelXMeterRslt != null)
{ {
chanelXMeterRslt?.CopyContentFrom(meterRslt); chanelXMeterRslt?.CopyContentFrom(meterRslt);
if (iCH == 0) if (iCH == 0)
{ {
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt, genesisSmart.TimestampSecStartRawCh1, chanelXMeterRslt.Q3Channel = 1;
genesisSmart.TimestampSecEndRawCh1, genesisSmart.VolumeLtrStartRawCh1, genesisSmart.VolumeLtrEndRawCh1); 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) else if (iCH == 1)
{ {
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt, genesisSmart.TimestampSecStartRawCh2, chanelXMeterRslt.Q3Channel = 2;
genesisSmart.TimestampSecEndRawCh2, genesisSmart.VolumeLtrStartRawCh2, genesisSmart.VolumeLtrEndRawCh2); 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) else if (iCH == 2)
{ {
CalculateMeterResults(chanelXMeterRslt, genesisSmart, tstRslt, genesisSmart.TimestampSecStartRawCh3, chanelXMeterRslt.Q3Channel = 3;
genesisSmart.TimestampSecEndRawCh3, genesisSmart.VolumeLtrStartRawCh3, genesisSmart.VolumeLtrEndRawCh3); 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) if (!genesisSmart.EnableShowChanels)
{ {
chXWaterMeter.Disabled = !genesisSmart.EnableShowChanels; chXWaterMeter.Disabled = !genesisSmart.EnableShowChanels;
meterTestRsltChX.TestDone = false; //meterTestRsltChX.TestDone = false;
} }
else else
{ {
chXWaterMeter.Disabled = false;
meterTestRsltChX.TestDone = true; meterTestRsltChX.TestDone = true;
} }
meterTestRsltChX.Passed = meterRslt.Passed; 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) private static void CalculateMeterResults(MeterTestRslt meterRslt, IRegReaderDatastream dstrReader, TestRslt tstRslt, double dstrReaderTimestampSecStart, double dstrReaderTimestampSecEnd, double dstrReaderVolumeLtrStart, double dstrReaderVolumeLtrEnd)
{
try
{ {
meterRslt.TimestampStart = dstrReaderTimestampSecStart; meterRslt.TimestampStart = dstrReaderTimestampSecStart;
meterRslt.TimestampEnd = !dstrReader.NoSamples meterRslt.TimestampEnd = !dstrReader.NoSamples
@@ -1455,11 +1571,19 @@ namespace TBF.Rig.TestMethods.FlyingStartMassCollection
meterRslt.TestTime = meterRslt.TimestampEnd - meterRslt.TimestampStart; meterRslt.TestTime = meterRslt.TimestampEnd - meterRslt.TimestampStart;
meterRslt.VolumeStart = dstrReaderVolumeLtrStart; /// liter meterRslt.VolumeStart = dstrReaderVolumeLtrStart; /// liter
meterRslt.VolumeEnd = dstrReaderVolumeLtrEnd; /// liter meterRslt.VolumeEnd = dstrReaderVolumeLtrEnd; /// liter
meterRslt.VolumeMeter = meterRslt.VolumeMeter = Math.Abs(dstrReaderVolumeLtrEnd - dstrReaderVolumeLtrStart);
Math.Abs(dstrReaderVolumeLtrEnd - dstrReaderVolumeLtrStart); meterRslt.VolumeRef = tstRslt.TestTime == 0
meterRslt.VolumeRef = tstRslt.TestTime==0? tstRslt.VolumeCTV : tstRslt.VolumeCTV * meterRslt.TestTime / tstRslt.TestTime; ? tstRslt.VolumeCTV
meterRslt.PulsesMaster = tstRslt.TestTime == 0? tstRslt.PulsesMaster : : tstRslt.VolumeCTV * meterRslt.TestTime / tstRslt.TestTime;
tstRslt.PulsesMaster * 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); 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.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))); 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 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 }; 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, IList<Event> Simulate2(Config.Entities.Test test, int repetitionNr, bool isLastRepetition,
Compound.CombinedTestParams compoundTestParams, 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
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; 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 //TODO solve this wia SmartCommunicationForm
throw new NotImplementedException();
//myRef.modelessDlg = new iPerlCommunicationForm(method, test, testParams); myRef.modelessDlg = new GenesisCommunicationForm(method, test, testParams);
//myRef.modelessDlg.Show(); myRef.modelessDlg.Show();
} }
@@ -72,7 +72,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
processDataLoggingOp = new TBF.Rig.Operations.ProcessDataLoggingOp(processDataLogger, this, false); processDataLoggingOp = new TBF.Rig.Operations.ProcessDataLoggingOp(processDataLogger, this, false);
string cmd; 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 }); TestProgressEventArgs.SetEstimatedTimes(new int[] { 0, 0, 0, 30, 0, 30, 0, 0 });
Bridge.OnTestProgress(this, new TestProgressEventArgs(test, Progress.JustStarted)); Bridge.OnTestProgress(this, new TestProgressEventArgs(test, Progress.JustStarted));
@@ -33,13 +33,13 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
/// Private parameterless constructor invoked by all other (public) constructors /// Private parameterless constructor invoked by all other (public) constructors
TestMethodCfg() TestMethodCfg()
{ {
Name = "SmartCommunication"; Name = "SmartCommunicationGenesis";
ParentName = string.Empty; ParentName = string.Empty;
CommTimeout = 1800; /// ms CommTimeout = 1800; /// ms
MaxCommRetries = 4; MaxCommRetries = 4;
WaitTimeAfterFailure = 2200; WaitTimeAfterFailure = 2200;
PassThroughWaitTime = 1500; PassThroughWaitTime = 1500;
NrThreads = 2; /// 1, 2 or 4 threads NrThreads = 10; /// 1, 2 or 4 threads
IperlCheckErrorsToStop = 10; IperlCheckErrorsToStop = 10;
MciTimeoutMs = 4000; // ms, NFC interface MciTimeoutMs = 4000; // ms, NFC interface
BaudRate = 57600; // NFC Interface BaudRate = 57600; // NFC Interface
@@ -28,6 +28,14 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
public TestMethodCfgCtrl() public TestMethodCfgCtrl()
{ {
InitializeComponent(); InitializeComponent();
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) private void EntryFormCfgCtrl_Load(object sender, EventArgs e)
@@ -116,10 +124,10 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
flags |= CfgUpdateFlags.Error; flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'Comm. timeout' should be in range 0 .. 5000"; message += Environment.NewLine + "'Comm. timeout' 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; 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)))
{ {
@@ -10,7 +10,6 @@ using Config.Entities;
using TBF.Resources; using TBF.Resources;
using TBF.Rig.Generic; using TBF.Rig.Generic;
using TBF.Rig.TestMethods.iPerlCommunication; using TBF.Rig.TestMethods.iPerlCommunication;
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
namespace TBF.Rig.TestMethods.GenesisCommunication namespace TBF.Rig.TestMethods.GenesisCommunication
{ {
@@ -31,7 +30,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
public override void InitializeAll() public override void InitializeAll()
{ {
Activity = iPerlCommunicationForm.ReadConfigurationStr; Activity = GenesisCommunicationForm.SlotInitializeStr;
SimultWithPrevious = false; SimultWithPrevious = false;
SimultWithNext = false; SimultWithNext = false;
} }
@@ -51,62 +50,79 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
if (i == 0) if (i == 0)
{ {
var retVal = new List<string>(); var retVal = new List<string>();
retVal.Add(iPerlCommunicationForm.ReadConfigurationStr);
retVal.Add(iPerlCommunicationConstants.ReadAdditionalCommonParametersStr); retVal.Add(GenesisCommunicationForm.SlotInitializeStr);
retVal.Add(iPerlCommunicationForm.ReadSerialNrStr); retVal.Add(GenesisCommunicationForm.SlotUpdateStr);
retVal.Add(string.Format("{0} A0", iPerlCommunicationForm.SetTestModeStr)); retVal.Add(GenesisCommunicationForm.SlotConnectStr);
retVal.Add(string.Format("{0} A4", iPerlCommunicationForm.SetTestModeStr)); retVal.Add(GenesisCommunicationForm.SlotPCBSlotStr);
retVal.Add(iPerlCommunicationForm.ReadCalibrationStr); retVal.Add(GenesisCommunicationForm.SlotSetPasswordStr);
retVal.Add(iPerlCommunicationForm.ReadCalibrationV4Str); retVal.Add(GenesisCommunicationForm.SlotLoginStr);
retVal.Add(iPerlCommunicationForm.NormalizeCalibrationFactorStr); retVal.Add(GenesisCommunicationForm.SlotGroupedLoginStr);
retVal.Add(iPerlCommunicationForm.NormalizeCalibrationV4FactorsStr); retVal.Add(GenesisCommunicationForm.SlotSetTestModeStr);
retVal.Add(iPerlCommunicationForm.GetDefaultQ2CorrectionsStr); retVal.Add(GenesisCommunicationForm.SlotSetActiveModeStr);
retVal.Add(iPerlCommunicationForm.ReadQ2CorrectionStr); retVal.Add(GenesisCommunicationForm.SlotDisconnectStr);
retVal.Add(iPerlCommunicationForm.ResetQ2CorrectionStr); retVal.Add(GenesisCommunicationForm.PrepareSlotQ3CalibrationStr);
retVal.Add(iPerlCommunicationForm.WriteDefaultQ2CorrectionsStr); retVal.Add(GenesisCommunicationForm.WriteSlotQ3CalibrationStr);
retVal.Add(iPerlCommunicationForm.InitOrReadQ2CorrectionsStr); retVal.Add(GenesisCommunicationForm.CheckMeterPrepareStr);
retVal.Add(iPerlCommunicationForm.WriteCalibrationFactorStr); retVal.Add(GenesisCommunicationForm.HoldSlotStr);
retVal.Add(iPerlCommunicationForm.WriteCalibrationV4FactorsStr);
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionStr);
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionAltStr);
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionGreeceStr); // retVal.Add(GenesisCommunicationForm.ReadConfigurationStr);
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionRLStr); // retVal.Add(GenesisCommunicationForm.ReadSerialNrStr);
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionLRStr); // retVal.Add(string.Format("{0} A0", GenesisCommunicationForm.SetTestModeStr));
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionIncl05Str); // retVal.Add(string.Format("{0} A4", GenesisCommunicationForm.SetTestModeStr));
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionAltIncl05Str); // retVal.Add(GenesisCommunicationForm.ReadCalibrationStr);
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionPlusIncl05Str); // retVal.Add(GenesisCommunicationForm.ReadCalibrationV4Str);
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionPlusAltIncl05Str); // retVal.Add(GenesisCommunicationForm.NormalizeCalibrationFactorStr);
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionGreeceIncl05Str); // retVal.Add(GenesisCommunicationForm.NormalizeCalibrationV4FactorsStr);
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionRLIncl05Str); // retVal.Add(GenesisCommunicationForm.GetDefaultQ2CorrectionsStr);
retVal.Add(iPerlCommunicationForm.WriteQ2CorrectionLRIncl05Str); // retVal.Add(GenesisCommunicationForm.ReadQ2CorrectionStr);
retVal.Add(iPerlCommunicationSeq.Q2correctedFromCmd + "Qx"); // retVal.Add(GenesisCommunicationForm.ResetQ2CorrectionStr);
retVal.Add(iPerlCommunicationSeq.StrictQ2ErrorCheckStr + "Qx"); // retVal.Add(GenesisCommunicationForm.WriteDefaultQ2CorrectionsStr);
retVal.Add(iPerlCommunicationSeq.Q2correctionCheckCmd); // retVal.Add(GenesisCommunicationForm.InitOrReadQ2CorrectionsStr);
retVal.Add(iPerlCommunicationSeq.IperlCheckCmd); // retVal.Add(GenesisCommunicationForm.WriteCalibrationFactorStr);
retVal.Add(iPerlCommunicationForm.UpdateBothQ2FactorsTestRLOnlyStr); // retVal.Add(GenesisCommunicationForm.WriteCalibrationV4FactorsStr);
retVal.Add(iPerlCommunicationForm.UpdateBothQ2FactorsTestLROnlyStr); // retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionStr);
retVal.Add(iPerlCommunicationForm.UpdateQ2CorrectionsStr); // retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionAltStr);
retVal.Add(iPerlCommunicationForm.ConditnlUpdateQ2CorrectionsStr); // retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionGreeceStr);
retVal.Add(iPerlCommunicationForm.ConditnlUpdateQ2CorrRLStr); // retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionRLStr);
retVal.Add(iPerlCommunicationForm.ConditnlUpdateQ2CorrLRStr); // retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionLRStr);
retVal.Add("Q2 corrected from Q2adj"); // retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionIncl05Str);
retVal.Add("Q2 correction check Q2bc Q2ac"); // retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionAltIncl05Str);
retVal.Add(iPerlCommunicationForm.SetActiveModeStr); // retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionPlusIncl05Str);
retVal.Add(iPerlCommunicationForm.SetIdleModeStr); // retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionPlusAltIncl05Str);
retVal.Add("---"); // retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionGreeceIncl05Str);
retVal.Add(iPerlCommunicationForm.Reset2HzCorrectionStr); // retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionRLIncl05Str);
retVal.Add(iPerlCommunicationForm.Write2HzCorrectionStr); // retVal.Add(GenesisCommunicationForm.WriteQ2CorrectionLRIncl05Str);
retVal.Add(iPerlCommunicationForm.DewaReworkRLStr); // retVal.Add(iPerlCommunicationSeq.Q2correctedFromCmd + "Qx");
retVal.Add(iPerlCommunicationForm.DewaReworkLRStr); // retVal.Add(iPerlCommunicationSeq.StrictQ2ErrorCheckStr + "Qx");
retVal.Add(iPerlCommunicationForm.StartTestingSealedMetersStr); // retVal.Add(iPerlCommunicationSeq.Q2correctionCheckCmd);
retVal.Add(iPerlCommunicationForm.EndTestingSealedMetersStr); // retVal.Add(iPerlCommunicationSeq.IperlCheckCmd);
retVal.Add(string.Format("{0} if enabled", iPerlCommunicationForm.ReadConfigurationStr)); // retVal.Add(GenesisCommunicationForm.UpdateBothQ2FactorsTestRLOnlyStr);
retVal.Add(string.Format("{0} 80", iPerlCommunicationForm.SetTestModeStr)); // retVal.Add(GenesisCommunicationForm.UpdateBothQ2FactorsTestLROnlyStr);
retVal.Add("iPerl_check prevWorkStep direction q2factors"); // retVal.Add(GenesisCommunicationForm.UpdateQ2CorrectionsStr);
for (iPerlCommunication.ConditionID id = iPerlCommunication.ConditionID.A; id < iPerlCommunication.ConditionID.Count; id++) // retVal.Add(GenesisCommunicationForm.ConditnlUpdateQ2CorrectionsStr);
{ // retVal.Add(GenesisCommunicationForm.ConditnlUpdateQ2CorrRLStr);
retVal.Add(string.Format(iPerlCommunication.SequenceConditionOp.ConditionNameFmt, id)); // 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; 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)) State.Create(string.Format("{0}({1}) : Delay for meters stabilization <T flow stab. - start = {2}s> ", test.Method, test.Name, delay))
.AddOperation(checkUiOp) .AddOperation(checkUiOp)
.AddOperations(readTempPressOps) .AddOperations(readTempPressOps)
.AddOperation(testInProgress) //.AddOperation(testInProgress)
.AddOperation(new Operations.TimerOp(delay)) //...develop: step 5 .AddOperation(new Operations.TimerOp(delay)) //...develop: step 5
.EnterState(); .EnterState();
do do
@@ -48,6 +48,8 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
public const int EndOptoDataCount = OptoDataBufferSize - StartOptoDataCount; public const int EndOptoDataCount = OptoDataBufferSize - StartOptoDataCount;
public const int StartEndFilterSamplesCount2 = 1; //20 /// StartEndFilterSamplesCount = 2 * StartEndFilterSamplesCount2 + 1 public const int StartEndFilterSamplesCount2 = 1; //20 /// StartEndFilterSamplesCount = 2 * StartEndFilterSamplesCount2 + 1
public const int FeatureVectorSize = 9; 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 StartSampleDelaySec = 5;
private const int EndSampleDelayCount = 2; private const int EndSampleDelayCount = 2;
@@ -94,6 +96,15 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
public string QuantityUnits { get; set; } public string QuantityUnits { get; set; }
public double CalibTarget { get { return iperlHeadCfg.ProcParams.CalibTarget; } } 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 FactorLimitLo { get { return (ushort)iperlHeadCfg.ProcParams.FactorLimitLo; } }
public ushort FactorLimitHi { get { return (ushort)iperlHeadCfg.ProcParams.FactorLimitHi; } } public ushort FactorLimitHi { get { return (ushort)iperlHeadCfg.ProcParams.FactorLimitHi; } }
public Counting InitFlowDir { get { return (iperlHeadCfg != null && iperlHeadCfg.ProcParams != null) ? iperlHeadCfg.ProcParams.Counting : Counting.Arbitrary; } } 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 OrigCalibFactorLNA;
public ushort CalibFactorLNA { get { return (CalibrationStructV4 != null) ? CalibrationStructV4.CalibrationLNA : (ushort)0; } } 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 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 int Group; /// Number written to QuidoRS to connct the watermeter to RfidComPort, 1 .. 10
public CommunicationInterface CommunicationInterface; /// Communication Interface: RFID or NFC public CommunicationInterface CommunicationInterface; /// Communication Interface: RFID or NFC
public double CalibFactorNominal; /// Raw calibration factor representing 100 % in the results calculation
/// <summary> Procedure parameters </summary> /// <summary> Procedure parameters </summary>
[XmlIgnore] [XmlIgnore]
@@ -49,6 +50,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
ProcParams = CreateProcParamsProvider() as ProcParams; ProcParams = CreateProcParamsProvider() as ProcParams;
CommunicationInterface = CommunicationInterface.RFID; CommunicationInterface = CommunicationInterface.RFID;
HeadCommunicationComPortNr = 0; HeadCommunicationComPortNr = 0;
CalibFactorNominal = IperlHead.DefaultCalibFactorNominal;
} }
public IperlHeadCfg(IComponentFactory factory) public IperlHeadCfg(IComponentFactory factory)
@@ -2,6 +2,7 @@
/// Copyright (c) 2015-2017 Sensus Metering Systems /// Copyright (c) 2015-2017 Sensus Metering Systems
/// ///
using System; using System;
using System.Globalization;
using System.Net; using System.Net;
using System.Windows.Forms; using System.Windows.Forms;
using Common; using Common;
@@ -33,6 +34,10 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
private void WaterMeterCfgCtrl_Load(object sender, EventArgs e) private void WaterMeterCfgCtrl_Load(object sender, EventArgs e)
{ {
nameLabel.Text = Strings.Name; 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; classNameLabel.Text = config.Factory.ClassName;
Redraw(); Redraw();
} }
@@ -55,6 +60,10 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
muxBoardNrTextBox.Text = config.MuxBoardNr.ToString(); muxBoardNrTextBox.Text = config.MuxBoardNr.ToString();
groupTextBox.Text = config.Group.ToString(); groupTextBox.Text = config.Group.ToString();
comboBoxCommunicationInterface.SelectedItem = config.CommunicationInterface.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)); tabPage2.Controls.Add(new IperlHeadTestCtrl(config));
} }
@@ -71,6 +80,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
muxBoardNrTextBox.Enabled = true; muxBoardNrTextBox.Enabled = true;
groupTextBox.Enabled = true; groupTextBox.Enabled = true;
comboBoxCommunicationInterface.Enabled = true; comboBoxCommunicationInterface.Enabled = true;
calibFactorNominalTextBox.Enabled = true;
} }
public CfgUpdateFlags VerifyCfg(ref string message) 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); 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; return flags;
} }
@@ -155,8 +172,22 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
config.Group = int.Parse(groupTextBox.Text); config.Group = int.Parse(groupTextBox.Text);
config.CommunicationInterface = (CommunicationInterface)comboBoxCommunicationInterface.SelectedIndex; config.CommunicationInterface = (CommunicationInterface)comboBoxCommunicationInterface.SelectedIndex;
config.HeadCommunicationComPortNr = int.Parse(headPortNrTextBox.Text); config.HeadCommunicationComPortNr = int.Parse(headPortNrTextBox.Text);
double nominalCalibFactor;
if (TryParseDouble(calibFactorNominalTextBox.Text, out nominalCalibFactor))
config.CalibFactorNominal = nominalCalibFactor;
return flags; 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;
}
} }
} }
@@ -31,6 +31,7 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
/// </summary> /// </summary>
private void InitializeComponent() private void InitializeComponent()
{ {
this.components = new System.ComponentModel.Container();
this.tabControl1 = new System.Windows.Forms.TabControl(); this.tabControl1 = new System.Windows.Forms.TabControl();
this.tabPage1 = new System.Windows.Forms.TabPage(); this.tabPage1 = new System.Windows.Forms.TabPage();
this.label4 = new System.Windows.Forms.Label(); 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.groupBox2 = new System.Windows.Forms.GroupBox();
this.label2 = new System.Windows.Forms.Label(); this.label2 = new System.Windows.Forms.Label();
this.headPortNrTextBox = new System.Windows.Forms.TextBox(); 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.tabControl1.SuspendLayout();
this.tabPage1.SuspendLayout(); this.tabPage1.SuspendLayout();
this.groupBox1.SuspendLayout(); this.groupBox1.SuspendLayout();
@@ -80,6 +84,8 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
// tabPage1 // tabPage1
// //
this.tabPage1.Controls.Add(this.groupBox2); 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.label4);
this.tabPage1.Controls.Add(this.label3); this.tabPage1.Controls.Add(this.label3);
this.tabPage1.Controls.Add(this.groupBox1); this.tabPage1.Controls.Add(this.groupBox1);
@@ -365,6 +371,23 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
this.groupBox2.TabStop = false; this.groupBox2.TabStop = false;
this.groupBox2.Text = "Head Communication"; 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 // label2
// //
this.label2.AutoSize = true; this.label2.AutoSize = true;
@@ -437,5 +460,8 @@ namespace TBF.Rig.TestMethods.iPerlCommunication.iPerlHead
private System.Windows.Forms.GroupBox groupBox2; private System.Windows.Forms.GroupBox groupBox2;
private System.Windows.Forms.TextBox headPortNrTextBox; private System.Windows.Forms.TextBox headPortNrTextBox;
private System.Windows.Forms.Label label2; private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label calibFactorNominalLabel;
private System.Windows.Forms.TextBox calibFactorNominalTextBox;
private System.Windows.Forms.ToolTip calibFactorNominalToolTip;
} }
} }
+34
View File
@@ -1237,11 +1237,25 @@
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Diagnostic\WriterDiagnosticResult.cs" /> <Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Diagnostic\WriterDiagnosticResult.cs" />
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Enums.cs" /> <Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Enums.cs" />
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Factory.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\DataWriteRequest.cs" />
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Interfaces\IDataStorageWriter.cs" /> <Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Interfaces\IDataStorageWriter.cs" />
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Types.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\Writer.cs" />
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\WriterCfg.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\CsvWriter .cs" />
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Writers\DatabaseWriter .cs" /> <Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Writers\DatabaseWriter .cs" />
<Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Writers\JsonWriter.cs" /> <Compile Include="Rig\Output\DataStorage\UniDataStorageWriter\Writers\JsonWriter.cs" />
@@ -1296,6 +1310,12 @@
<Compile Include="Rig\Output\DB\ResultsWriter\ResultsWriterResultsDlg.Designer.cs"> <Compile Include="Rig\Output\DB\ResultsWriter\ResultsWriterResultsDlg.Designer.cs">
<DependentUpon>ResultsWriterResultsDlg.cs</DependentUpon> <DependentUpon>ResultsWriterResultsDlg.cs</DependentUpon>
</Compile> </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\Factory.cs" />
<Compile Include="Rig\Output\DB\SaveDiverterCorrections\SaveDiverterCorr.cs" /> <Compile Include="Rig\Output\DB\SaveDiverterCorrections\SaveDiverterCorr.cs" />
<Compile Include="Rig\Output\DB\SaveDiverterCorrections\SaveDiverterCorrCfg.cs" /> <Compile Include="Rig\Output\DB\SaveDiverterCorrections\SaveDiverterCorrCfg.cs" />
@@ -4633,4 +4653,18 @@
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
<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> </Project>
+213
View File
@@ -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.");
}
}
}
}
+179
View File
@@ -0,0 +1,179 @@
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
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,263 @@
using System;
using System.Globalization;
using System.Text;
using System.Linq;
using System.Reflection;
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication;
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.StreamingProtocol;
using TBF.Rig.RegisterReaders.GenesisRegReader.common;
namespace TBFTests.Rig.RegisterReaders.GenesisRegReader
{
// Golden frames have CRCs generated independently with Python binascii.crc_hqx.
// No ports, databases, GCI engine or production-code changes are required.
[TestClass]
[TestCategory("GenesisReadRegression")]
public class GenesisReadDataRegressionTests
{
[DataTestMethod]
[DataRow("@h 1 0 10000000 FFFFFF00 00000400 00100000 00000000 00008000 00400000 00800000 FFFFF000 0C 00018000 8D2C", 1, 1024, 0.001024)]
[DataRow("@h 1 0 10000000 FFFFFF00 00000400 00100000 00000200 00008000 00400000 00800000 FFFFF000 0C 00018000 7BAC", 1, 512, 0.002048)]
[DataRow("@h 1 0 10000000 FFFFFF00 00000400 00100000 00000400 00008000 00400000 00800000 FFFFF000 0C 00018000 700D", 1, 1024, 0.001024)]
[DataRow("@h 1 0 10000000 FFFFFF00 00000400 00100000 00000800 00008000 00400000 00800000 FFFFF000 0C 00018000 674F", 1, 2048, 0.000512)]
[DataRow("@h 2 0 10000000 FFFFFF00 00000400 00100000 00000000 00008000 00400000 00800000 FFFFF000 0C 00018000 3F43", 2, 1024, 0.001024)]
[DataRow("@h 2 0 10000000 FFFFFF00 00000400 00100000 00000200 00008000 00400000 00800000 FFFFF000 0C 00018000 C9C3", 2, 512, 0.002048)]
[DataRow("@h 2 0 10000000 FFFFFF00 00000400 00100000 00000400 00008000 00400000 00800000 FFFFF000 0C 00018000 C262", 2, 1024, 0.001024)]
[DataRow("@h 2 0 10000000 FFFFFF00 00000400 00100000 00000800 00008000 00400000 00800000 FFFFF000 0C 00018000 D520", 2, 2048, 0.000512)]
[DataRow("@h 3 0 10000000 FFFFFF00 00000400 00100000 00000000 00008000 00400000 00800000 FFFFF000 0C 00018000 A179", 3, 1024, 0.001024)]
[DataRow("@h 3 0 10000000 FFFFFF00 00000400 00100000 00000200 00008000 00400000 00800000 FFFFF000 0C 00018000 57F9", 3, 512, 0.002048)]
[DataRow("@h 3 0 10000000 FFFFFF00 00000400 00100000 00000400 00008000 00400000 00800000 FFFFF000 0C 00018000 5C58", 3, 1024, 0.001024)]
[DataRow("@h 3 0 10000000 FFFFFF00 00000400 00100000 00000800 00008000 00400000 00800000 FFFFF000 0C 00018000 4B1A", 3, 2048, 0.000512)]
public void ProtocolH_GoldenFramesPreserveChannelUnitsAndScale(string frame, int channel, int scale, double volume)
{
var decoder = new StreamingDecoder();
Assert.IsTrue(decoder.DecodeMsg(frame));
var data = decoder.DataCalib;
Assert.IsNotNull(data);
Assert.IsTrue(data.IsValid);
Assert.AreEqual(channel, data.Channel);
Assert.AreEqual(scale, data.VolumeScaleRawPerMl);
Assert.AreEqual(volume, data.VolumeCm, 1e-12);
Assert.AreEqual(1048576d, data.AccuVolumeRaw);
Assert.AreEqual(1024d, data.DeltaVolumeRaw);
Assert.AreEqual(volume / 1024, data.DeltaVolumeQm, 1e-15);
Assert.AreEqual(1.5, data.TimeS, 1e-12);
Assert.AreEqual(.5, data.SampleIntervalS, 1e-12);
Assert.AreEqual(.001, data.AmplitudeUpV, 1e-12);
Assert.AreEqual(.002, data.AmplitudeDownV, 1e-12);
Assert.AreEqual(-1d, data.TemperatureDegC, 1e-12);
Assert.AreEqual(65536d, data.OverflowTimeS);
Assert.IsNull(decoder.DataFlowTest);
double previousVolume = double.NaN, previousTime = double.NaN;
var telegram = new OptoTelegramRaw();
telegram.UpdateFromSmart(data, 17, 2.5f, ref previousVolume, ref previousTime);
Assert.AreEqual(channel - 1, telegram.iChannel);
Assert.AreEqual(volume * 1000, telegram.VolumeRawExt, 1e-9);
Assert.AreEqual(1.5, telegram.TimestampExt, 1e-12);
Assert.AreEqual(17, telegram.Counter);
Assert.AreEqual(2.5f, telegram.RefFlow);
}
[DataTestMethod]
[DataRow("@h 1 0 00000000 00000000 00000400 00100000 00000400 00008000 00400000 00800000 00000000 0C 00018000 0A39", 0, 0, 0)]
[DataRow("@h 1 0 7FFFFFFF 7FFFFFFF 00000400 00100000 00000400 00008000 00400000 00800000 7FFFFFFF 0C 00018000 89B0", 2147483647, 2147483647, 2147483647)]
[DataRow("@h 1 0 80000000 80000000 00000400 00100000 00000400 00008000 00400000 00800000 80000000 0C 00018000 3E06", -2147483648, -2147483648, -2147483648)]
[DataRow("@h 1 0 FFFFFFFF FFFFFFFF 00000400 00100000 00000400 00008000 00400000 00800000 FFFFFFFF 0C 00018000 6870", -1, -1, -1)]
public void ProtocolH_SignedFieldsPreserveTwosComplement(string frame, int total, int delta, int temperature)
{
var decoder = new StreamingDecoder();
Assert.IsTrue(decoder.DecodeMsg(frame));
var data = decoder.DataCalib;
Assert.AreEqual(total, data.RawTotalTimeOfFlight);
Assert.AreEqual(delta, data.RawDeltaTimeOfFlight);
Assert.AreEqual(total / 274877906944d, data.TotalTimeOfFlightS, 1e-15);
Assert.AreEqual(delta / 274877906944d, data.DeltaTimeOfFlightS, 1e-15);
Assert.AreEqual(temperature / 4096d, data.TemperatureDegC, 1e-10);
}
[DataTestMethod]
[DataRow("@f 00000000 00000000 F603", 0.0, 0.0)]
[DataRow("@f 7FFFFFFF FFFFFFFF 3996", 2147.483647, 65535.99998474121)]
[DataRow("@f FFFFFFFF 00010000 21AD", -1e-06, 1.0)]
[DataRow("@f 80000000 00008000 0541", -2147.483648, 0.5)]
public void ProtocolF_PreservesSignedVolumeAndUnsignedTime(string frame, double volume, double time)
{
var decoder = new StreamingDecoder();
Assert.IsTrue(decoder.DecodeMsg(frame));
Assert.IsNotNull(decoder.DataFlowTest);
Assert.IsTrue(decoder.DataFlowTest.IsValid);
Assert.AreEqual(volume, decoder.DataFlowTest.VolumeCm, 1e-9);
Assert.AreEqual(time, decoder.DataFlowTest.TimeS, 1e-12);
Assert.IsNull(decoder.DataCalib);
}
[DataTestMethod]
[DataRow(null)]
[DataRow("")]
[DataRow("garbage")]
[DataRow("@h")]
[DataRow("@h 1")]
[DataRow("@h 1 invalid")]
[DataRow("@f 00000001 00010000 ZZZZ")]
[DataRow("@h\t1\t0\t10000000\tFFFFFF00\t00000400\t00100000\t00000400\t00008000\t00400000\t00800000\tFFFFF000\t0C\t00018000\t700D")]
[DataRow("@h 1 0 10000000 FFFFFF00 00000400 00100000 00000400 00008000 00400000 00800000 FFFFF000 0C 00018000 700D ")]
[DataRow("@he 1 0 10000000 FFFFFF00 00000400 00100000 00000400 00008000 00400000 00800000 FFFFF000 0C 00018000 700D")]
public void InvalidOrUnsupportedInputProducesNoMeasurement(string frame)
{
var decoder = new StreamingDecoder();
decoder.DecodeMsg(frame);
Assert.IsNull(decoder.DataCalib);
Assert.IsNull(decoder.DataFlowTest);
}
[DataTestMethod]
[DataRow(1)]
[DataRow(2)]
[DataRow(3)]
[DataRow(4)]
[DataRow(5)]
[DataRow(6)]
[DataRow(7)]
[DataRow(8)]
[DataRow(9)]
[DataRow(10)]
[DataRow(11)]
[DataRow(12)]
[DataRow(13)]
public void AlteringAnyCalibrationFieldWithoutUpdatingCrcRejectsRecord(int field)
{
var words = Golden.Split(' ');
words[field] = words[field] == "0" ? "1" : "0";
var decoder = new StreamingDecoder();
Assert.IsFalse(decoder.DecodeMsg(string.Join(" ", words)));
Assert.IsNull(decoder.DataCalib);
}
[DataTestMethod]
[DataRow("en-US")]
[DataRow("sk-SK")]
[DataRow("de-DE")]
public void HexDecodingIsIndependentOfCulture(string culture)
{
var previous = System.Threading.Thread.CurrentThread.CurrentCulture;
try
{
System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(culture);
var decoder = new StreamingDecoder();
Assert.IsTrue(decoder.DecodeMsg(Golden));
Assert.AreEqual(.001024, decoder.DataCalib.VolumeCm, 1e-12);
Assert.AreEqual(-1d, decoder.DataCalib.TemperatureDegC);
}
finally { System.Threading.Thread.CurrentThread.CurrentCulture = previous; }
}
[TestMethod]
public void DiagnosticModeRetainsBadCrcButMarksRecordInvalid()
{
var corrupted = Golden.Substring(0, Golden.Length - 4) + "0000";
var decoder = new StreamingDecoder(false);
Assert.IsFalse(decoder.DecodeMsg(corrupted));
Assert.IsNotNull(decoder.DataCalib);
Assert.IsFalse(decoder.DataCalib.IsValid);
Assert.AreEqual(.001024, decoder.DataCalib.VolumeCm, 1e-12);
}
[TestMethod]
public void CrcMatchesIndependentCcittFalseCheckVector()
{
Assert.AreEqual((ushort)0x29B1, Crc16Ccitt.CalculateMsb1021(Encoding.ASCII.GetBytes("123456789")));
Assert.AreEqual((ushort)0xFFFF, Crc16Ccitt.CalculateMsb1021(new byte[0]));
}
private static void InitializeReaderForTest(GenesisSmartReader reader)
{
const int channelCount = 3;
SetPrivateField(reader, "volumeRawExtLast", new double[channelCount]);
SetPrivateField(reader, "timestampExtLast", new double[channelCount]);
SetPrivateField(reader, "lastTimestamp", new double[channelCount]);
SetPrivateField(reader, "timestampSec", Enumerable.Repeat(double.NaN, channelCount).ToArray());
SetPrivateField(reader, "timestampSec0", Enumerable.Repeat(double.NaN, channelCount).ToArray());
SetPrivateField(reader, "lastVolumeRaw", new double[channelCount]);
SetPrivateField(reader, "volumeLtr", Enumerable.Repeat(double.NaN, channelCount).ToArray());
SetPrivateField(reader, "volumeLtr0", Enumerable.Repeat(double.NaN, channelCount).ToArray());
var optoData = new OptoTelegramRaw[GenesisSmartReader.OptoDataBufferSize];
for (int i = 0; i < optoData.Length; i++)
optoData[i] = new OptoTelegramRaw();
SetPrivateField(reader, "optoData", optoData);
SetPrivateField(reader, "optoDataCount", 0);
SetPrivateField(reader, "toBeFlushed", new OptoTelegramRaw());
SetPrivateField(reader, "flowDirectionDetection", new FlowDirectionDetection());
SetPrivateField(reader, "dataStreamState", DataStreamState.ProcessAndSave);
SetPrivateField(reader, "synchronized", false);
SetPrivateField(reader, "synchronized2", false);
SetPrivateField(reader, "partOfTelegram", string.Empty);
SetPrivateField(reader, "startDataProcessing", true);
reader.StopQueueData = false;
reader.TestStartTelegramIx = 0;
reader.TestEndTelegramIx = 0;
}
private static void SetPrivateField(object target, string name, object value)
{
var field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
Assert.IsNotNull(field, name);
field.SetValue(target, value);
}
private static T ReadField<T>(object target, string name)
{
return (T)target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(target);
}
[TestMethod]
public void ProcessOptoLinePreservesPayloadAndCounterThroughReaderPipeline()
{
var reader = new GenesisSmartReader(new TBF.Rig.RegisterReaders.GenesisRegReader.GenesisCfg(new TBF.Rig.RegisterReaders.GenesisRegReader.Factory()));
InitializeReaderForTest(reader);
bool complete;
reader.ProcessOptoLine(Golden, DataStreamState.ProcessAndSave, out complete);
Assert.IsFalse(complete);
Assert.AreEqual(1, ReadField<int>(reader, "optoDataCount"));
var rows = ReadField<OptoTelegramRaw[]>(reader, "optoData");
Assert.AreEqual(0, rows[0].iChannel);
Assert.AreEqual(1.024, rows[0].VolumeRawExt, 1e-9);
Assert.AreEqual(1.5, rows[0].TimestampExt, 1e-12);
Assert.AreEqual(0, rows[0].Counter);
reader.ProcessOptoLine("invalid telegram", DataStreamState.ProcessAndSave, out complete);
Assert.AreEqual(1, ReadField<int>(reader, "optoDataCount"));
reader.ProcessOptoLine(Golden, DataStreamState.ProcessAndSave, out complete);
Assert.AreEqual(2, ReadField<int>(reader, "optoDataCount"));
Assert.AreEqual(1, rows[1].Counter);
Assert.AreEqual(rows[0].VolumeRawExt, rows[1].VolumeRawExt, 1e-9);
}
[DataTestMethod]
[DataRow("stop")]
[DataRow("queue")]
[DataRow("processing")]
public void ReaderStopGatesPreventMeasurementsFromBeingAppended(string gate)
{
var reader = new GenesisSmartReader(new TBF.Rig.RegisterReaders.GenesisRegReader.GenesisCfg(new TBF.Rig.RegisterReaders.GenesisRegReader.Factory()));
InitializeReaderForTest(reader);
if (gate == "stop") SetPrivateField(reader, "_isStopping", true);
if (gate == "queue") reader.StopQueueData = true;
if (gate == "processing") SetPrivateField(reader, "startDataProcessing", false);
bool complete;
reader.ProcessOptoLine(Golden, DataStreamState.ProcessAndSave, out complete);
Assert.AreEqual(0, ReadField<int>(reader, "optoDataCount"));
Assert.IsFalse(complete);
}
private const string Golden = "@h 1 0 10000000 FFFFFF00 00000400 00100000 00000400 00008000 00400000 00800000 FFFFF000 0C 00018000 700D";
}
}
@@ -237,6 +237,40 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
Assert.AreEqual(15625.0, calib[2], 0.001); Assert.AreEqual(15625.0, calib[2], 0.001);
} }
[TestMethod]
public void GetQ3Calibration_ShouldUseWriteActivityLimits_AndFallbackWhenTheyAreUnset()
{
double limitLo;
double limitHi;
Assert.IsFalse(GenesisSmartReader.TryGetEffectiveQ3CalibrationErrorLimits(0.0, 0.0, out limitLo, out limitHi));
Assert.AreEqual(-5.0, limitLo);
Assert.AreEqual(5.0, limitHi);
Assert.IsTrue(GenesisSmartReader.TryGetEffectiveQ3CalibrationErrorLimits(-2.0, 2.0, out limitLo, out limitHi));
Assert.AreEqual(-2.0, limitLo);
Assert.AreEqual(2.0, limitHi);
var sut = new GenesisSmartReader();
// At refTime 120 s, this gives a calculated factor 3 % above the initial factor.
var raw = CreateKnownStartEndData(
(100.0, 100.0 + (200.0 / 1.03 / 12.0), 10.0, 20.0),
(200.0, 200.0 + (200.0 / 1.03 / 12.0), 10.0, 20.0),
(300.0, 300.0 + (200.0 / 1.03 / 12.0), 10.0, 20.0));
SetPrivateField(sut, "_rawStartEndByChannel", raw);
SetPrivateField(sut, "_recalculatedStartEndByChannel", raw);
SetPrivateField(sut, "optoDataCount", 2);
sut.TestStartTelegramIx = 0;
sut.TestEndTelegramIx = 1;
var valid = new bool[3];
var differences = new double[3];
var factors = new double[3];
sut.GetQ3Calibration(200.0, 120.0, ValidInitFactors, -2.0, 2.0,
ref valid, ref differences, ref factors);
CollectionAssert.AreEqual(new[] { false, false, false }, valid);
Assert.IsTrue(differences.All(x => x > 2.9 && x < 3.1));
}
[TestMethod] [TestMethod]
public void CalculateQ3Calibration_ShouldComputeExpectedChannelValues_FromSimulationData() public void CalculateQ3Calibration_ShouldComputeExpectedChannelValues_FromSimulationData()
{ {
@@ -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);
}
}
}
+18 -1
View File
@@ -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"> <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')" /> <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> <PropertyGroup>
@@ -63,6 +63,13 @@
<Reference Include="Moq, Version=4.20.70.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL"> <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> <HintPath>..\packages\Moq.4.20.70\lib\net462\Moq.dll</HintPath>
</Reference> </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" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> <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> <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\RegisterReaders\AllyReader\integration\AllyHardwareIntegrationTest.cs" />
<Compile Include="Rig\TestMethods\AllyCalibration\TestMethodConfigTest.cs" /> <Compile Include="Rig\TestMethods\AllyCalibration\TestMethodConfigTest.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\common\OptoTelegramRawTest.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\FakeSerialDriver.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\IperlResponseFactory.cs" /> <Compile Include="Rig\TestMethods\iPerlCommunication\communication\IperlResponseFactory.cs" />
<Compile Include="Rig\TestMethods\iPerlCommunication\communication\RadioServiceTest.cs" /> <Compile Include="Rig\TestMethods\iPerlCommunication\communication\RadioServiceTest.cs" />
@@ -206,10 +214,12 @@
</Choose> </Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" /> <Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.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"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <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> <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> </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.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'))" /> <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> </Target>
@@ -221,4 +231,11 @@
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
<ItemGroup><Compile Include="GenesisRecoveryTests.cs" /><Compile Include="GenesisParallelSchedulingTests.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> </Project>
+4 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Castle.Core" version="5.1.1" targetFramework="net472" /> <package id="Castle.Core" version="5.1.1" targetFramework="net472" />
<package id="JetBrains.Annotations" version="2023.3.0" 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.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
<package id="System.ValueTuple" version="4.5.0" targetFramework="net472" /> <package id="System.ValueTuple" version="4.5.0" targetFramework="net472" />
<package id="log4net" version="2.0.15" 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> </packages>