Compare commits

...
Author SHA1 Message Date
michal 33ef914984 Update GenesisReaderTests: add real input parsing, format variation handling, and rollover cases; remove outdated Q3 calibration tests; increment version to 3.9.3056.1 2026-04-15 09:56:54 +02:00
michal 3eb0b87f0d Add Q3 calibration tests for GenesisSmartReader and refactor test cases:
- Introduce new `GenesisSmartReader_Q3Test` class with comprehensive Q3 calibration scenarios.
- Refactor existing tests to utilize simulation data for better consistency and validation.
- Adjust Q3 calibration logic in `GenesisSmartReader` to enhance calculation accuracy and validation criteria.
2026-04-15 09:22:09 +02:00
michal 711dfc03ef Fix Q3 calibration logic in GenesisSmartReader: replace _rawStartEndByChannel with recalculatedVariablesByChannel for accurate data computation. 2026-04-14 15:54:28 +02:00
michal 93459ccfab Refactor water meter handling in SequenceBase and FlyingStartMassCollectionSeq:
- Enhance logic for enabling/disabling channels based on water meter data.
- Introduce Q3 calibration validation in `GenesisSmartReader` with configurable thresholds.
- Update `GenesisCfgCtrl` UI to disable certain inputs.
2026-04-14 15:41:13 +02:00
michal 009aefb7c9 Refactor GenesisSmartReader and SequenceBase logic, enhance channel handling in water meters, and update GUI components:
- Adjust `Disabled` logic for water meters in `SequenceBase` allowing conditional enabling of channels based on serial number.
- Introduce `EnableShowChanels` property in `GenesisSmartReader` and integrate into `FlyingStartMassCollectionSeq`.
- Modify `StartupFlushMs` to derive configuration dynamically.
- Update `GenesisCfgCtrl` with a checkbox for enabling channels and input for flush timing.
- Increment assembly version to `3.9.3054.1`.
2026-04-14 08:03:02 +02:00
michal d2e18d615d Bump assembly version to 3.9.3053.1. 2026-04-13 14:17:56 +02:00
michal 69b475eb77 Refactor GenesisSmartReader for improved simulation and testing:
- Add `PrepareCalculatedChannelDataSimulationForTest` and supporting utility methods for generating and cloning test data.
- Update `StartupFlushMs` and sampling intervals for increased flexibility.
- Revise water meter handling logic in `FlyingStartMassCollectionSeq` to support multiple channels.
2026-04-13 14:17:34 +02:00
michal 2210bb8240 Enhance FlyingStartMassCollectionSeq with GenesisSmartReader support; add water meter handling logic and bump assembly version to 3.9.3050.1. 2026-04-11 21:55:34 +02:00
michal 65ab19290f simulate mode - flow reached 2026-04-09 12:49:00 +02:00
michal 94c7279da1 Add localized string resources for "Test in calculation" and integrate it into FlyingStartMassCollectionSeq
- Add `Test_in_calculation` localized strings to `.resx` files for English, Slovak, and Czech.
- Update `FlyingStartMassCollectionSeq` to display the new activity message during the calculation phase.
2026-04-09 10:08:50 +02:00
michal 2136695b80 Fix Stop 2026-04-08 16:37:27 +02:00
michal c0f9b0ffa0 Refactor GenesisSmartReader stop logic for enhanced thread safety and post-processing; bump assembly version to 3.9.3047.1. 2026-04-08 15:44:59 +02:00
michal c4ef22436f Update GenesisSmartReader Stop logic and bump assembly version to 3.9.3045.1:
- Refactor `Stop` for improved thread safety using `lock` on `_stateSync` and `_stopSync`.
- Enhance `Stop` behavior by introducing state updates during immediate stop requests.
- Fix redundant operations and reorganize log and processing statements for clarity.
- Update `RequestImmediateStopInternal` to accept a new state parameter.
2026-04-08 15:06:45 +02:00
michal 0b73871518 Refactor GenesisSmartReader and add advanced test utilities:
- Introduce `ForceProcessingEnabled`, `InstallSuccessfulProcessingOverride`, and `EnqueueIncomingLine` helpers to streamline test setup.
- Add new test suite `GenesisSmartReaderStopTests` to validate `Stop` behavior for different processing states.
- Enhance `GenesisSmartReader` state management with a dedicated `Reset` method and improved `Stop` logic.
- Improve buffer initialization in `ProcessOptoLine` to address null-value edge cases.
- Update assembly version to `3.9.3043.1`.
2026-04-08 13:51:55 +02:00
michal f7dc6fee5e immediate Stop improve 2026-04-07 16:37:19 +02:00
michal ba848a167d improve 2026-04-07 15:44:32 +02:00
michal dcb0bf9a88 Update GenesisSmartReader logging and bump assembly version to 3.9.3040.1:
- Enhance `Stop` logs with `registerReader` and `optoSerialPort` details for improved traceability.
2026-04-07 15:27:20 +02:00
michal f6433134c1 Add WaitForStartupFlushToFinish and EnableProcessingLoopForTests utilities for improved test reliability:
- Incorporate `WaitForStartupFlushToFinish` in `GenesisSmartReaderThreadedReadTests` to ensure startup flush completion before processing.
- Introduce `EnableProcessingLoopForTests` in `GenesisSmartReaderTest` to streamline test initialization.
- Fix thread-safety issues in startup flush handling and reorder operations in `GenesisSmartReader.Stop`.
- Bump assembly version to `3.9.3039.1`.
2026-04-07 14:45:37 +02:00
michal 7e3df77b5b Refactor GenesisSmartReader processing logic and tests for robust handling:
- Replace property getters with concise expressions in `GenesisSmartReader`.
- Implement FIFO and concurrent producer-consumer unit tests for `ProcessingLoop`.
- Add startup flush mechanism for improved input handling on initialization.
- Refactor processing loop with structured start/stop tasks and cancellation tokens.
- Fix thread-safety issues and enhance logic in `Stop` and `StopDataStreamProcessing`.
- Add internal visibility for tests in `TBFTests` via `InternalsVisibleTo`.
- Update assembly version to `3.9.3038.1`.
2026-04-07 10:42:27 +02:00
michal 08dff1272b Add overflow volume/timestamp handling and related tests for GenesisSmartReader:
- Extend `CalibrationRecord` constructor to include overflow fields (`OverflowVolumeCm`, `OverflowTimeS`).
- Implement additional unit tests in `OptoTelegramRawTest` to validate multi-rollover scenarios and extended calculations.
- Add `GenesisSmartReaderThreadedReadTests` to cover threaded read-loop functionality and serial processing.
- Enhance logging in `Pump` classes (`TurnOff` methods).
- Update project file to include new test files.
2026-04-02 09:25:17 +02:00
michal d5c8fd1c8f Refactor GenesisSmartReader with enhanced channel-specific volume and timestamp processing:
- Replace `AverageNullable` with `AverageCachedTime` and introduce `GetCachedVolume`/`GetCachedTime` helpers.
- Add recalibration logic with `_recalculatedStartEndByChannel` and `_rawStartEndByChannel`.
- Implement `PrepareCalculatedChannelData` for refined multi-channel synchronization.
- Add unit tests (`GenesisSmartReaderChannelTests`) for improved coverage and validation of new methods.
- Update `OptoTelegramRaw.Copy` to fix field assignment issues.
2026-03-27 15:12:43 +01:00
michal a34740e838 Update GenesisSmartReaderTest to handle DiscardOutCalls and configure block repetition resets:
- Modify tests to validate `DiscardOutCalls` alongside `DiscardInCalls`.
- Add support for configuring `ResetAfterBlockRepetitions` in test initialization.
2026-03-27 13:19:10 +01:00
michal 836ba5a41f Refactor GenesisSmartReader processing logic and add advanced multi-channel volume and timestamp handling:
- Introduce `TimeDeltaPerChannel` and `GroupRecordsPerChannel` for calculating time and volume deltas across channels.
- Add recalibration logic to synchronize volume and timestamp data with channel-specific maxima.
- Fix `ResetDataBuffer` implementation and replace redundant `ReasetDataBuffer` calls.
- Update handling of flow markers and block completion logic for improved buffer management.
- Add `Copy` method to `OptoTelegramRaw` for efficient cloning and recalculation.
- Bump `AssemblyVersion` and `AssemblyFileVersion` to `3.9.3019.1`.
2026-03-27 13:18:37 +01:00
michal 23c83ab452 Enhance GenesisSmartReader multi-channel processing and add tests for telegram handling:
- Refactor `ProcessOptoLine` logic to support output buffer resets at configurable repetition intervals.
- Add tests:
  - `ProcessOptoLine_Block_f_h1_h2_h3_f_ShouldResetBuffersOnlyAfterLastF`
  - `ReadOptoData_FullBlock_ShouldDiscardBuffersAfterClosingF`
  - `ProcessOptoLine_LastF_AfterH3_ShouldRequestBufferReset`
  - `ProcessOptoLine_ShouldReset
2026-03-26 14:45:36 +01:00
michal bc9e583c29 FIX Add ChannelAverages to project and remove unused imports in GenesisHead. 2026-03-26 07:29:13 +01:00
michal 18cef617da Enhance Genesis SmartReader with channel-specific volume and timestamp tracking:
- Add per-channel `VolumeStart`, `VolumeEnd`, `TimeStart`, and `TimeEnd` processing for improved multi-channel support.
- Introduce `ChannelAverages` for centralized average calculations across channels.
- Refactor `GenesisSmartReader` to improve data decoding and logging for calibration and flow test records.
- Add telemetry unwrapping helpers for precise volume and timestamp calculations.
- Update `OptoTelegramRaw` to support channel-specific updates and robust data processing.
- Update `AssemblyVersion` and `AssemblyFileVersion` to `3.9.3013.1`.
2026-03-26 07:19:22 +01:00
michal 4ee736cff5 Add advanced multi-channel processing for Genesis SmartReader:
- Refactor `VolumeLtrStart` and `VolumeLtrEnd` to compute channel-specific averages at test boundaries.
- Implement `ProcessOptoLine_ShouldInsertTelegrams` tests for validating telegram insertion and channel updates.
- Add `VolumeLtrEnd_ShouldAverageOnlyAvailableChannels` to handle partial channel data gracefully.
- Introduce `VolumeFromChannelsAtStart` and `VolumeFromChannelsAtEnd` helpers for refined channel data handling.
- Update Opto telegram processing logic to support robust volume and timestamp unwrapping.
- Enhance `GenesisSmartReader.ToString()` for clearer configuration representation.
- Add `OptoTelegramRawTest` to validate telegram behavior, flag handling, and delta computations.
- Update `AssemblyVersion` to `3.9.3012.1`.
2026-03-24 15:23:06 +01:00
michal 4e40dc8601 Comment out Detect_BaudRate_By_ViewFactoryId test method in TouchReadBaudRateDetectionTests. 2026-03-24 10:07:24 +01:00
michal a5a68ec457 Refactor Genesis serial communication handling (ISerialDriver interface compatible with SerialPort):
- Replace method-based `IsOpen()` checks with property-based `IsOpen` usage in `ISerialDriver` and related classes.
- Simplify `Open()` and add `Close()` methods to unify connection management.
- Introduce `InitializeThreeChannelState()` helper in `GenesisSmartReaderTest` for multi-channel state initialization.
- Adjust calculations in `CalculateTimeByChannels()` and `CalculateVolumeByChannels()` for clarity.
- Add new tests for multi-channel processing and validation of updated telegram handling logic.
2026-03-24 10:04:30 +01:00
michal e26d88d437 Refactor Genesis use 3 chanels reader structure:
- Replace `Xylem.Common` namespace references with `TBF.Rig.RegisterReaders`.
- Introduce `GenesisSmartReaderTest` and `FakeSerialDriver` for unit testing.
- Revamp `FlowDirectionDetection` to support multiple channels.
- Make constants in `StreamingDecoder` public for enhanced accessibility.
- Update `AssemblyVersion` and `AssemblyFileVersion` to `3.9.3010.1`.
2026-03-24 08:53:16 +01:00
michal 2522d1da9f Add Genesis radio/opto communication and configuration components
- Replace `OpthoHeadService` with `RadioService` to enhance Genesis communication handling via serial interface.
- Introduce `OptoReceivedEventArgs` to manage event-driven communication.
- Add foundational protocol implementations (`BaseProtocol`, `IProtocol`, etc.) for Genesis configuration and processing.
- Implement `SirtConfig`, `MeterConfig`, and `ProcessConfig` classes for flexible Genesis configuration management.
- Expand Genesis framework to support diagnostics, activity modes, and version retrieval.
2026-03-18 15:57:04 +01:00
michal 8983c01591 Add integration tests for IperlHat communication and diagnostic LED parsing
- Implement `IperlHatIntegrationTests` to validate serial communication, diagnostic LED states, and Opto data handling.
- Add `HexFormatterTest` for verifying Hex formatting utilities.
- Add `DiagnosticLedParserTest` to test parsing for all diagnostic LED states.
2026-03-17 12:15:52 +01:00
michal 61b1320d4f Add Genesis COM port configuration and update Genesis meter setup logic
- Introduce `OptoComPortNr` and `HeadComPortNr` properties in `GenesisHead`.
- Update `SetupGenesisMeter` to use new COM port configurations.
- Add reference to `Xylem.Common.Hardware.WaterMeter.Genesis.GenesisConfig`.
2026-03-16 15:48:41 +01:00
michal 2c8a0922d1 Genesis dialog with Com ports 2026-03-16 15:47:12 +01:00
michal b078d9586d Add detailed logging for Genesis operations in FlyingStartMassCollectionSeq and FlyingStartSeq.
- Introduce debug and info logs for Genesis initialization, calibration, measurement, and data storage.
- Ensure proper logging during Genesis operations' start and stop phases.
2026-03-16 15:42:45 +01:00
michal 6e35de5f4d Update AssemblyVersion and AssemblyFileVersion to 3.9.3001.1 2026-03-16 15:36:25 +01:00
michal 2dbe4cded4 Genesis slot max count 10, Add DB writer component
Integrate `DatabaseWriter` component and update GenesisHead configuration:

- Add `DatabaseWriter` factory and related classes (`WriteDBCfgCtrl`, `WriterCfg`, `WritingToDB`).
- Extend GenesisHead slot number range to 1-10 and adjust UI layout accordingly.
2026-03-16 15:36:02 +01:00
michal 10865c6878 Update AssemblyVersion and AssemblyFileVersion to 3.9.3000.1 2026-03-16 15:35:06 +01:00
michal ee1353fe54 Add DatabaseWriter component:
- Implement `DatabaseWriter` factory and configuration logic.
- Add `WriteDbCfgCtrl` for component configuration UI.
- Introduce database connection handling, network adapter selection, and tracing record management.
2026-03-16 15:31:58 +01:00
michal 5cefd7fc3a Integrate Genesis meter support into FlyingStartMassCollectionSeq:
- Add initialization, calibration, and measurement logic for Genesis meters.
- Extend result handling to capture Genesis-specific data (timestamps, volumes, errors).
- Ensure proper cleanup of Genesis meters at test conclusion.
2026-03-16 15:31:49 +01:00
michal ab0e5ea82a Genesis heap 2026-03-16 15:17:56 +01:00
365 changed files with 283993 additions and 407 deletions
+6 -3
View File
@@ -10,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Sensus")]
[assembly: AssemblyProduct("TestBenchFramework")]
[assembly: AssemblyCopyright("Copyright © 2013 - 2023 Sensus Slovensko, a.s.")]
[assembly: AssemblyCopyright("Copyright © 2013 - 2025 Sensus Slovensko, a.s.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -19,6 +19,9 @@ using System.Runtime.InteropServices;
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
//Visibility to TBFTests internal classes
[assembly: InternalsVisibleTo("TBFTests")]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("2c13538e-15ee-48b2-af0f-2c95afb67186")]
@@ -29,5 +32,5 @@ using System.Runtime.InteropServices;
// Build Number
// Revision
//
[assembly: AssemblyVersion("3.9.2149.4")]
[assembly: AssemblyFileVersion("3.9.2149.4")]
[assembly: AssemblyVersion("3.9.3056.1")]
[assembly: AssemblyFileVersion("3.9.3056.1")]
+3 -3
View File
@@ -1,5 +1,5 @@
## Version History
| Version | Target Environment | Title | Description |
|------------|--------------------------------------------------------------|-----------------------------------------------------|------------------------------------|
| 3.9.2149.0 | HeatMeters, Heat meter sensors, Procedure Dilog, Tab Process | Excanged columns value 'Sensor' and 'Heat meter sensor' | Fix in code ProcedureDlg, row 1851 |
| Version | Source of change | Target Environment | Title | Description |
|------------|----------------------|--------------------------------------------------------------|-----------------------------------------------------|------------------------------------|
| 3.9.2149.0 | Michal databse error | HeatMeters, Heat meter sensors, Procedure Dilog, Tab Process | Excanged columns value 'Sensor' and 'Heat meter sensor' | Fix in code ProcedureDlg, row 1851 |
+9 -1
View File
@@ -1,7 +1,6 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@@ -6270,6 +6269,15 @@ namespace TBF.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Test in calculation.
/// </summary>
internal static string Test_in_calculation {
get {
return ResourceManager.GetString("Test_in_calculation", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Test in progress.
/// </summary>
File diff suppressed because it is too large Load Diff
+3
View File
@@ -1764,4 +1764,7 @@
<data name="Units" xml:space="preserve">
<value />
</data>
<data name="Test_in_calculation" xml:space="preserve">
<value>Probíhá výpočet</value>
</data>
</root>
+3
View File
@@ -2482,4 +2482,7 @@
<data name="Units" xml:space="preserve">
<value>Units</value>
</data>
<data name="Test_in_calculation" xml:space="preserve">
<value>Test in calculation</value>
</data>
</root>
+3
View File
@@ -259,4 +259,7 @@
<data name="Units" xml:space="preserve">
<value />
</data>
<data name="Test_in_calculation" xml:space="preserve">
<value>Prebieha výpočet</value>
</data>
</root>
+2
View File
@@ -111,6 +111,7 @@ namespace TBF.Rig.BuiltIn.PumpTandem
public void TurnOff()
{
log.DebugFormat("{0}.TurnOff()", Name);
if ((pumpFm1 != null) && (pumpFm2 != null))
{
pumpFm1.TurnOff();
@@ -120,6 +121,7 @@ namespace TBF.Rig.BuiltIn.PumpTandem
{
// TODO
}
log.DebugFormat("DONE {0}.TurnOff()", Name);
}
+2
View File
@@ -332,6 +332,8 @@ namespace TBF.Rig.Danfoss.VLT2800
Telegram.UpdateTelegramChecksum(msg);
}
SendData(msg);
log.DebugFormat("DONE {0}.TurnOff()", Name);
}
+2
View File
@@ -264,6 +264,8 @@ namespace TBF.Rig.Modbus.PumpFM.DanfossVLT
modbus.SendMessage(msg, Name);
}
log.DebugFormat("DONE {0}.TurnOff()", Name);
}
+2
View File
@@ -416,6 +416,8 @@ namespace TBF.Rig.Modbus.PumpFM.Grundfoss
// --- DIAGNOSTIKA ---
LiveLogDiag.Log1("GF TurnOff msg = " + BitConverter.ToString(msg));
}
log.DebugFormat("DONE {0}.TurnOff()", Name);
}
/// <summary>
@@ -0,0 +1,33 @@
///
/// Copyright (c) 2018 Sensus Slovensko a.s.
///
using System.Collections.Generic;
using TBF.Rig.Generic;
namespace TBF.Rig.Output.DB.DatabaseWriter
{
/// <summary>
/// Factory component 'DatabaseWriter' implements more storing modules
/// Mosules:
/// * store whole results in database
/// * store to specific mexico database structure - not implemented yet
/// * store configurable data SQL template to a specific database - not implemented yet
/// </summary>
public class Factory : IComponentFactory
{
public string ClassName { get { return GetType().Namespace.Substring(8); } } /// For backward compatibility
public override string ToString() { return ClassName; }
public IComponent DummyComponent() { return new WritingToDb(); }
public IComponent GetComponent(IComponentCfg cfg, IList<IComponent> components) { return new WritingToDb(cfg); }
public IComponentCfg DefaultConfig() { return new WriterCfg("DatabaseWriter", this); }
public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component)
{
return ComponentCfgBase.CreateFromDbEntity(WriterCfg.Serializer, component, this);
}
}
}
@@ -0,0 +1,148 @@
///
/// Copyright (c) 2018-2019 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using System.Net;
using Common;
using TBF.Rig.Generic;
namespace TBF.Rig.Output.DB.DatabaseWriter
{
public partial class WriteDbCfgCtrl : Configs.ConfigCtrlUtils, IComponentCfgCtrl
{
public bool ShowMore { get { return false; } }
WriterCfg config;
public IComponentCfg Config
{
get { return config as IComponentCfg; }
set
{
config = value as WriterCfg;
Redraw();
}
}
public WriteDbCfgCtrl()
{
InitializeComponent();
}
private void WriterCfgCtrl_Load(object sender, EventArgs e)
{
if (config == null) return;
Redraw();
}
public void Closing()
{
}
void Redraw()
{
if (config == null) return; /// Control was not loaded, settings were not changed
classNameLabel.Text = config.Factory.ClassName;
nameTextBox.Text = config.Name;
connStrTextBox.Text = config.ConnStr;
connStr2TextBox.Text = config.ConnStr2;
checkPreviousRecordsCheckBox.Checked = config.CheckPreviousRecords;
saveTracingRecordsCheckBox.Checked = config.SaveTracingRecords;
Network.AdapterInfo.RefreshNetAdaptersInfo();
IList<Network.AdapterInfo> adapters = Network.AdapterInfo.NetAdapters;
string cfgAdapter = string.Empty;
foreach (Network.AdapterInfo ai in adapters)
{
string record;
if (ai.IPAddress == null)
{
/// This happens with network adapters that currently have no IP address,
/// for instance not connected wireless or dial-up adapters
record = "???.???.???.???";
}
else
{
/// Do not consider IPv6 adapters as well as "Any", "Broadcast", "Loopback" or "None" addresses
if ((ai.IPAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6) ||
ai.IPAddress.Equals(IPAddress.Any) ||
ai.IPAddress.Equals(IPAddress.Broadcast) ||
ai.IPAddress.Equals(IPAddress.IPv6Any) ||
ai.IPAddress.Equals(IPAddress.IPv6Loopback) ||
ai.IPAddress.Equals(IPAddress.IPv6None) ||
ai.IPAddress.Equals(IPAddress.Loopback) ||
ai.IPAddress.Equals(IPAddress.None))
{
continue;
}
record = ai.IPAddress.ToString();
}
record += " - " + ai.Description;
int i = netAdapterComboBox.Items.Add(record);
if (ai.Description == config.NetAdapter)
{
/// Select the adapter currently in the configuration
netAdapterComboBox.SelectedIndex = i;
}
}
/// Select the first one if the adapter from the configuration does not exist on the system
if (netAdapterComboBox.SelectedIndex < 0 && netAdapterComboBox.Items.Count > 0)
{
netAdapterComboBox.SelectedIndex = 0;
}
}
public void Unlock()
{
nameTextBox.Enabled = true;
connStrTextBox.Enabled = true;
connStr2TextBox.Enabled = true;
netAdapterComboBox.Enabled = true;
checkPreviousRecordsCheckBox.Enabled = true;
saveTracingRecordsCheckBox.Enabled = true;
}
public CfgUpdateFlags VerifyCfg(ref string message)
{
return CfgUpdateFlags.None;
}
public CfgUpdateFlags UpdateCfg()
{
CfgUpdateFlags flags = CfgUpdateFlags.None;
if (config == null) return CfgUpdateFlags.Error; /// Control was not loaded, settings were not changed
if (config.Name != nameTextBox.Text)
{
config.Name = nameTextBox.Text;
flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
}
flags |= UpdateDifferent(ref config.ConnStr, connStrTextBox.Text, CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
flags |= UpdateDifferent(ref config.ConnStr2, connStr2TextBox.Text, CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
flags |= UpdateDifferent(ref config.CheckPreviousRecords, checkPreviousRecordsCheckBox.Checked, CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
flags |= UpdateDifferent(ref config.SaveTracingRecords, saveTracingRecordsCheckBox.Checked, CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
/// Network adapter
string strAdapter = netAdapterComboBox.Text;
int iDash = strAdapter.IndexOf(" - ");
string netAdapter = (iDash < 0) ? "" : strAdapter.Substring(iDash + 3);
if (config.NetAdapter != netAdapter)
{
config.NetAdapter = netAdapter;
flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
}
return flags;
}
}
}
@@ -0,0 +1,184 @@
///
/// Copyright (c) 2018 Sensus Slovensko a.s.
///
namespace TBF.Rig.Output.DB.DatabaseWriter
{
partial class WriteDbCfgCtrl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.nameTextBox = new System.Windows.Forms.TextBox();
this.nameLabel = new System.Windows.Forms.Label();
this.classNameLabel = new System.Windows.Forms.Label();
this.connStrTextBox = new System.Windows.Forms.TextBox();
this.connStrLabel = new System.Windows.Forms.Label();
this.saveTracingRecordsCheckBox = new System.Windows.Forms.CheckBox();
this.checkPreviousRecordsCheckBox = new System.Windows.Forms.CheckBox();
this.netAdapterLabel = new System.Windows.Forms.Label();
this.netAdapterComboBox = new System.Windows.Forms.ComboBox();
this.connStr2TextBox = new System.Windows.Forms.TextBox();
this.connStr2Label = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// nameTextBox
//
this.nameTextBox.Enabled = false;
this.nameTextBox.Location = new System.Drawing.Point(117, 32);
this.nameTextBox.Name = "nameTextBox";
this.nameTextBox.Size = new System.Drawing.Size(326, 20);
this.nameTextBox.TabIndex = 2;
//
// nameLabel
//
this.nameLabel.AutoSize = true;
this.nameLabel.Location = new System.Drawing.Point(5, 35);
this.nameLabel.Name = "nameLabel";
this.nameLabel.Size = new System.Drawing.Size(38, 13);
this.nameLabel.TabIndex = 1;
this.nameLabel.Text = "Name:";
//
// classNameLabel
//
this.classNameLabel.AutoSize = true;
this.classNameLabel.Location = new System.Drawing.Point(114, 10);
this.classNameLabel.Name = "classNameLabel";
this.classNameLabel.Size = new System.Drawing.Size(83, 13);
this.classNameLabel.TabIndex = 0;
this.classNameLabel.Text = "ComonentName";
//
// connStrTextBox
//
this.connStrTextBox.Enabled = false;
this.connStrTextBox.Location = new System.Drawing.Point(8, 74);
this.connStrTextBox.Name = "connStrTextBox";
this.connStrTextBox.Size = new System.Drawing.Size(435, 20);
this.connStrTextBox.TabIndex = 4;
//
// connStrLabel
//
this.connStrLabel.AutoSize = true;
this.connStrLabel.Location = new System.Drawing.Point(5, 58);
this.connStrLabel.Name = "connStrLabel";
this.connStrLabel.Size = new System.Drawing.Size(92, 13);
this.connStrLabel.TabIndex = 3;
this.connStrLabel.Text = "Connection string:";
//
// saveTracingRecordsCheckBox
//
this.saveTracingRecordsCheckBox.AutoSize = true;
this.saveTracingRecordsCheckBox.Enabled = false;
this.saveTracingRecordsCheckBox.Location = new System.Drawing.Point(117, 221);
this.saveTracingRecordsCheckBox.Name = "saveTracingRecordsCheckBox";
this.saveTracingRecordsCheckBox.Size = new System.Drawing.Size(177, 17);
this.saveTracingRecordsCheckBox.TabIndex = 10;
this.saveTracingRecordsCheckBox.Text = "Save production tracing records";
this.saveTracingRecordsCheckBox.UseVisualStyleBackColor = true;
//
// checkPreviousRecordsCheckBox
//
this.checkPreviousRecordsCheckBox.AutoSize = true;
this.checkPreviousRecordsCheckBox.Enabled = false;
this.checkPreviousRecordsCheckBox.Location = new System.Drawing.Point(117, 199);
this.checkPreviousRecordsCheckBox.Name = "checkPreviousRecordsCheckBox";
this.checkPreviousRecordsCheckBox.Size = new System.Drawing.Size(138, 17);
this.checkPreviousRecordsCheckBox.TabIndex = 9;
this.checkPreviousRecordsCheckBox.Text = "Check previous records";
this.checkPreviousRecordsCheckBox.UseVisualStyleBackColor = true;
//
// netAdapterLabel
//
this.netAdapterLabel.AutoSize = true;
this.netAdapterLabel.Location = new System.Drawing.Point(5, 147);
this.netAdapterLabel.Name = "netAdapterLabel";
this.netAdapterLabel.Size = new System.Drawing.Size(89, 13);
this.netAdapterLabel.TabIndex = 7;
this.netAdapterLabel.Text = "Network adapter:";
//
// netAdapterComboBox
//
this.netAdapterComboBox.Enabled = false;
this.netAdapterComboBox.FormattingEnabled = true;
this.netAdapterComboBox.Location = new System.Drawing.Point(8, 163);
this.netAdapterComboBox.Name = "netAdapterComboBox";
this.netAdapterComboBox.Size = new System.Drawing.Size(435, 21);
this.netAdapterComboBox.TabIndex = 8;
//
// connStr2TextBox
//
this.connStr2TextBox.Enabled = false;
this.connStr2TextBox.Location = new System.Drawing.Point(8, 118);
this.connStr2TextBox.Name = "connStr2TextBox";
this.connStr2TextBox.Size = new System.Drawing.Size(434, 20);
this.connStr2TextBox.TabIndex = 6;
//
// connStr2Label
//
this.connStr2Label.AutoSize = true;
this.connStr2Label.Location = new System.Drawing.Point(5, 102);
this.connStr2Label.Name = "connStr2Label";
this.connStr2Label.Size = new System.Drawing.Size(101, 13);
this.connStr2Label.TabIndex = 5;
this.connStr2Label.Text = "Connection string 2:";
//
// TracingCfgCtrl
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.connStr2TextBox);
this.Controls.Add(this.connStr2Label);
this.Controls.Add(this.netAdapterComboBox);
this.Controls.Add(this.netAdapterLabel);
this.Controls.Add(this.saveTracingRecordsCheckBox);
this.Controls.Add(this.checkPreviousRecordsCheckBox);
this.Controls.Add(this.connStrTextBox);
this.Controls.Add(this.connStrLabel);
this.Controls.Add(this.nameTextBox);
this.Controls.Add(this.nameLabel);
this.Controls.Add(this.classNameLabel);
this.Name = "WriteDbCfgCtrl";
this.Size = new System.Drawing.Size(450, 250);
this.Load += new System.EventHandler(this.WriterCfgCtrl_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox nameTextBox;
private System.Windows.Forms.Label nameLabel;
private System.Windows.Forms.Label classNameLabel;
private System.Windows.Forms.TextBox connStrTextBox;
private System.Windows.Forms.Label connStrLabel;
private System.Windows.Forms.CheckBox saveTracingRecordsCheckBox;
private System.Windows.Forms.CheckBox checkPreviousRecordsCheckBox;
private System.Windows.Forms.Label netAdapterLabel;
private System.Windows.Forms.ComboBox netAdapterComboBox;
private System.Windows.Forms.TextBox connStr2TextBox;
private System.Windows.Forms.Label connStr2Label;
}
}
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -0,0 +1,57 @@
///
/// Copyright (c) 2018 Sensus Slovensko a.s.
///
using System.Collections.Generic;
using System.Xml.Serialization;
using TBF.Rig.Generic;
namespace TBF.Rig.Output.DB.DatabaseWriter
{
///
/// Class and file name is preserved for backward compatibility
///
public class WriterCfg : ComponentCfgBase, Generic.IComponentCfg
{
public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(WriterCfg) })[0];
public override XmlSerializer GetSerializer() { return Serializer; }
public IComponentCfgCtrl GetControl(IList<Config.Entities.Component> cmpntEntities) { return new WriteDbCfgCtrl(); }
public string ConnStr;
public string ConnStr2;
public string NetAdapter;
public bool CheckPreviousRecords;
public bool SaveTracingRecords;
/// Private parameterless constructor invoked by all other (public) constructors
WriterCfg()
{
ParentName = string.Empty;
NetAdapter = string.Empty;
ConnStr = "server=10.42.128.24; database=sledovanie2020; uid=vyroba2020; pwd=qwerty; charset=utf8;";
ConnStr2 = "server=10.42.128.24; database=sledovanie2019; uid=vyroba2019; pwd=qwerty; charset=utf8;";
CheckPreviousRecords = true;
SaveTracingRecords = true;
}
public WriterCfg(string name, IComponentFactory factory)
: this()
{
this.Name = name;
this.Factory = factory;
}
public string ToString(int i)
{
return string.Format("Name={0}, CheckPrev.={1}, SaveRecords={2}, ConnStr={3}, , ConnStr2={4}",
Name,
CheckPreviousRecords,
SaveTracingRecords,
ConnStr,
ConnStr2);
}
}
}
@@ -0,0 +1,577 @@
///
/// Copyright (c) 2018-2023 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Common;
using log4net;
using NHibernate;
using SharedDatabase;
using SharedDatabase.Entities;
using TBF.Rig.Sequences;
namespace TBF.Rig.Output.DB.DatabaseWriter
{
public enum Retv
{
OK,
Error,
}
class DateTimeComparer : IComparer<DateTime>
{
public int Compare(DateTime x, DateTime y)
{
return DateTime.Compare(x, y);
}
}
public class WritingToDb : ComponentBase, IOperation, GenericDevices.IResultsWriter, GenericDevices.IStartInfoReader, Generic.IDevice
{
private static readonly ILog log = LogManager.GetLogger(typeof(WritingToDb));
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
public const string WorkstepName = "Test_bench";
public OrderInfo DefaultOrder;
WriterCfg tracingCfg;
string workplace
{
get
{
TBF.Rig.GenericDevices.IBenchInfo benchInfo = TBF.Rig.Sequences.ProcessData.BenchInfo;
return (benchInfo != null) ? benchInfo.TestBenchName : "TestBench";
}
}
IPAddress ipAddress;
IPAddress netMask;
public IPAddress IPAddress { get { return ipAddress; } }
public IPAddress NetMask { get { return netMask; } }
enum OpState
{
None,
CheckPreviousRecordsScheduled,
CheckPreviousRecordsRunning,
SaveTracingRecordsScheduled,
SaveTracingRecordsRunning,
}
///
OpState currentOpState;
bool opCompleted;
bool anyError;
/// <summary>
/// Watermeters to check at the beginning of the cycle
/// </summary>
IList<Results.Entities.WaterMeter> waterMeters;
/// <summary>
/// Data (tracing records) to write at the end of cycle
/// </summary>
Results.Entities.Batch batch;
public ISessionFactory SessionFactory; /// Factory to create database sessions that is initialized in the constructor
public WritingToDb() {}
public WritingToDb(Generic.IComponentCfg cfg)
: base(cfg)
{
tracingCfg = cfg as WriterCfg;
if (tracingCfg == null) throw new ArgumentException("tracingCfg");
Network.AdapterInfo.RefreshNetAdaptersInfo();
Network.AdapterInfo netadapter = Network.AdapterInfo.GetNetAdapter(tracingCfg.NetAdapter);
ipAddress = netadapter.IPAddress;
netMask = netadapter.NetMask;
currentOpState = OpState.None;
log.Warn(this.ToString());
DefaultOrder = null;
}
///
/// IDevice interface implementation
///
public override void Initialize()
{
if (tracingCfg.DebugLevel == DebugMode.Simulate) return;
string ipAddress = GetIPAddress();
///
/// Session factory is used to create database sessions
///
SharedDatabase.TracingDB.SessionFactory = this.SessionFactory =
FluentNHibernate.Cfg.Fluently.Configure()
.Database(FluentNHibernate.Cfg.Db.MySQLConfiguration.Standard.ConnectionString(tracingCfg.ConnStr))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Process>())
.ExposeConfiguration(SharedDatabase.TracingDB.BuildSchema)
.BuildSessionFactory();
try
{
using (var session = SessionFactory.OpenSession())
{
///
/// Initialize DefaultOrder
///
var dfltOrders = session.QueryOver<OrderInfo>()
.Where(x => x.POName == "0000001")
.And(x => (x.POState == (sbyte)OrderState.New || x.POState == (sbyte)OrderState.Active))
.List();
if (dfltOrders.Count > 0) DefaultOrder = dfltOrders[0];
#if !DEBUG
///
/// Register this test bench in the tracing DB for approx. 2 weeks (RELEASE version only)
///
SharedDatabase.TracingDB.RegisterWorkplaceObsolete(session,
workplace,
CurrentUser.UserName(),
GetIPAddress(),
"<multiple>",
WorkstepName,
DateTime.Now + new TimeSpan(15, 0, 0, 0));
session.Flush();
#endif
session.Close();
}
}
catch (Exception ex)
{
log.ErrorFormat("Cannot load a default order from a database or regster this workplace: {0}", ex);
DefaultOrder = null;
}
}
///
string GetIPAddress()
{
return (ipAddress != null) ? ipAddress.ToString() : "1.2.3.4";
}
///
public void RunDeviceBefore() { }
public void RunDeviceAfter() { }
///
public void StopDevice()
{
if (tracingCfg.DebugLevel != DebugMode.Normal) return;
using (ISession session = SessionFactory.OpenSession())
{
SharedDatabase.TracingDB.UnregisterWorkplaceObsolete(session, workplace);
session.Flush();
}
}
public void StopDevice2() {}
public IList<OrderInfo> ReadOrders()
{
try
{
using (var session = SessionFactory.OpenSession())
{
var result = session.QueryOver<OrderInfo>()
.Where(x => (x.POState == (sbyte)OrderState.New || x.POState == (sbyte)OrderState.Active))
.List();
session.Close();
return result;
}
}
catch (Exception ex)
{
log.ErrorFormat("ReadOrders() failed: {0}", ex.Message);
return new List<OrderInfo>();
}
}
/// <summary>
/// Read reference records belonging to a specified order.
/// Used when retrieving housing S/N-s belonging to obtained eRegister numbers.
/// </summary>
/// <param name="session">DB sesson</param>
/// <param name="order">Order</param>
/// <returns>List of reference records-s</returns>
public IList<ReferenceRecord> ReadRefRecords(ISession session, OrderInfo order, OrderInfo dfltOrder = null, Process dfltWFlow = null)
{
try
{
var refRecords = session.QueryOver<ReferenceRecord>()
.Where(x => (x.POName == order.POName))
.List();
refRecords.Reverse();
if (dfltOrder != null && dfltWFlow != null)
{
var moreRecords = session.QueryOver<ReferenceRecord>()
.Where(x => (x.POName == dfltOrder.POName))
.And(x => (x.Workflow == dfltWFlow.Name))
.List();
for (int i = moreRecords.Count - 1; i >= 0; i--) refRecords.Add(moreRecords[i]);
}
return refRecords;
}
catch (Exception exc)
{
log.ErrorFormat("Cannot read ref.records from the tracing DB: {0}", exc.Message);
return new List<ReferenceRecord>();
}
}
/// <summary>
/// Read records belonging to a specified order
/// </summary>
/// <param name="session">DB sesson</param>
/// <param name="order">Order</param>
/// <returns>List of records</returns>
public IList<Record> ReadRecords(ISession session, OrderInfo order, OrderInfo dfltOrder = null, Process dfltWFlow = null)
{
try
{
var records = session.QueryOver<Record>()
.JoinQueryOver<ReferenceRecord>(rec => rec.ReferenceRecord)
.Where(rr => rr.POName == order.POName)
.List();
records.Reverse();
if (dfltOrder != null && dfltWFlow != null)
{
var moreRecords = session.QueryOver<Record>()
.JoinQueryOver<ReferenceRecord>(rec => rec.ReferenceRecord)
.Where(rr => (rr.POName == dfltOrder.POName))
.And(rr => (rr.Workflow == dfltWFlow.Name))
.List();
for (int i = moreRecords.Count - 1; i >= 0; i--) records.Add(moreRecords[i]);
}
return records;
}
catch (Exception exc)
{
log.ErrorFormat("Cannot read records from the tracing DB: {0}", exc.Message);
return new List<Record>();
}
}
/// <summary>
/// Reads information on start of a cycle, Events: Event.InfoRead
/// </summary>
/// <param name="waterMeters">Results of water meters</param>
/// <returns>Reference to the operation</returns>
public IOperation ReadStartInfoOp(IList<Results.Entities.WaterMeter> waterMeters)
{
if (!tracingCfg.CheckPreviousRecords)
{
return null;
}
else if ((currentOpState == OpState.CheckPreviousRecordsRunning) || (currentOpState == OpState.SaveTracingRecordsRunning))
{
throw new Exception("Sequence error");
}
else
{
this.waterMeters = waterMeters;
currentOpState = OpState.CheckPreviousRecordsScheduled;
return this;
}
}
/// <summary>
/// Writes the test cycle results into a file, Events: Event.ResultsWritten
/// </summary>
/// <param name="procedure">Procedure to print the results of</param>
/// <param name="unsortedResults">Results to write into the file</param>
/// <returns>Reference to the operation</returns>
public IOperation ProcessResultsOp(Results.Entities.Batch batch)
{
if (!tracingCfg.SaveTracingRecords)
{
return null;
}
else if ((currentOpState == OpState.CheckPreviousRecordsRunning) || (currentOpState == OpState.SaveTracingRecordsRunning))
{
throw new Exception("Sequence error");
}
else
{
this.batch = batch;
currentOpState = OpState.SaveTracingRecordsScheduled;
return this;
}
}
/// <summary>Start this operation</summary>
public void Start()
{
if (currentOpState == OpState.CheckPreviousRecordsScheduled)
{
currentOpState = OpState.CheckPreviousRecordsRunning;
}
else if (currentOpState == OpState.SaveTracingRecordsScheduled)
{
currentOpState = OpState.SaveTracingRecordsRunning;
}
opCompleted = false;
anyError = false;
}
/// <summary>Run this operation</summary>
/// <returns>Event.ResultsWritten or Event.Error</returns>
public Event Run()
{
log.WarnFormat("{0} : Run() : currentOp = {1}", Name, currentOpState);
if (currentOpState == OpState.CheckPreviousRecordsRunning)
{
if (tracingCfg.DebugLevel == DebugMode.Simulate) return Event.InfoRead;
if (opCompleted) return anyError ? Event.InfoNotRead : Event.InfoRead;
/// Run once
opCompleted = true;
if (CheckPreviousRecords(waterMeters) != Retv.OK) anyError = true;
return anyError ? Event.InfoNotRead : Event.InfoRead;
}
if (currentOpState == OpState.SaveTracingRecordsRunning)
{
if (tracingCfg.DebugLevel == DebugMode.Simulate || batch.WaterMeters.Count == 0) return Event.ResultsWritten;
if (opCompleted) return anyError ? Event.ErrorProcessingResults : Event.ResultsWritten;
/// Run once
opCompleted = true;
if (SaveTracingRecords(batch) != Retv.OK) anyError = true;
if (anyError && !string.IsNullOrEmpty(SharedDatabase.EventsDB.ConnectionString))
{
try
{
NHibernate.ISession session = SharedDatabase.EventsDB.CreateSession();
SharedDatabase.EventsDB.LoadSubscribers(session);
TBF.UiBridge.Bridge.TriggerEvent(session, Name, EventClass.ComputerResources, Severity.Error,
string.Format("Nepodarilo sa uložiť výsledky do sledovacej DB: Číslo dávky={0}", batch.BatchNr),
string.Format("Nepodarilo sa uložiť výsledky do sledovacej DB\r\nČíslo dávky = {0}", batch.BatchNr),
SubscriberGroup.Metrology | SubscriberGroup.Maintenance | SubscriberGroup.Production);
}
catch (Exception exc)
{
log.ErrorFormat("Triggering event(s) failed: {0}", exc.Message);
}
}
return anyError ? Event.ErrorProcessingResults : Event.ResultsWritten;
}
return Event.None;
}
/// <summary>Stop this operation</summary>
public void Stop()
{
currentOpState = OpState.None;
}
Retv CheckPreviousRecords(IList<Results.Entities.WaterMeter> waterMeters)
{
Retv retVal = Retv.Error;
var sampleWM = waterMeters.FirstOrDefault<Results.Entities.WaterMeter>(x => x != null && x.Disabled == false);
if (sampleWM != null)
{
using (ISession session = SessionFactory.OpenSession())
{
for (int i = 0; i < waterMeters.Count; i++)
{
Results.Entities.WaterMeter wm = waterMeters[i];
if ((wm != null) && !wm.Disabled)
{
CheckPreviousRecordOfSingleWM(session, wm);
}
}
retVal = Retv.OK; /// Successfully completed (regardless of wm.LastRecordIsNok)
}
}
return retVal; /// Returns Retv.Error if checking not completed successfully
}
Retv CheckPreviousRecordOfSingleWM(ISession session, Results.Entities.WaterMeter wm)
{
if (!string.IsNullOrEmpty(wm.SerialNr) && ProcessData.WorkflowSummary != null)
{
/// Get all reference records with Code == SerialNr from all worksteps different from 'Test_bench'
var refRecords = session.QueryOver<ReferenceRecord>()
.Where(rr => (rr.Code1 == wm.SerialNr))
.List();
if (refRecords.Count == 1)
{
ReferenceRecord refRecord = refRecords[0];
StepRecord previousStep = string.IsNullOrEmpty(ProcessData.WorkflowSummary.PreviousWorkstepName) ? null
: refRecord.StepRecords.FirstOrDefault(x => x.Workstep == ProcessData.WorkflowSummary.PreviousWorkstepName);
if (refRecord.Name1 == ProcessData.WorkflowSummary.Part1Name &&
refRecord.Name2 == ProcessData.WorkflowSummary.Part2Name &&
(ProcessData.WorkflowSummary.PreviousWorkstepName == null ||
(previousStep != null && previousStep.Workstep == ProcessData.WorkflowSummary.PreviousWorkstepName)))
{
wm.Workflow = ProcessData.WorkflowSummary.Workflow.Name;
wm.LastRecordIsNok = false; /// OK
return Retv.OK;
}
}
}
/// S/N is missing OR no records found OR workflows do not match OR previous StepRecord is missing
wm.Workflow = string.Empty;
wm.LastRecordIsNok = true; /// NOK
return Retv.OK;
}
/// <summary>
/// Write results of a batch of water meters to the DB
/// </summary>
/// <param name="session">DB session</param>
/// <param name="batch">Batch entity</param>
Retv SaveTracingRecords(Results.Entities.Batch batch)
{
ITransaction transaction = null;
ISession session = null;
var order = ProcessData.OrderInfo as SharedDatabase.Entities.OrderInfo;
if (order != null && ProcessData.WorkflowSummary != null && !string.IsNullOrEmpty(ProcessData.WorkflowSummary.WorkstepName))
{
///
/// Save to regular tracing DB
///
try
{
session = SessionFactory.OpenSession();
transaction = session.BeginTransaction();
string workplace = (ProcessData.BenchInfo != null) ? ProcessData.BenchInfo.TestBenchName : "TestBench";
int writtenRecordsCount = 0;
foreach (var wm in batch.WaterMeters)
{
if (!wm.Disabled && !string.IsNullOrEmpty(wm.SerialNr))
{
writtenRecordsCount += SaveSingleWM2DB(session, wm, order.POName, ProcessData.WorkflowSummary, workplace);
}
}
transaction.Commit();
session.Flush();
log.WarnFormat("Batch {0}: {1} of {2} watermeters written to the regular production tracing DB",
batch.BatchNr, writtenRecordsCount, batch.WaterMeters.Count);
}
catch (Exception exc)
{
if (transaction != null && !transaction.WasCommitted) transaction.Rollback();
log.ErrorFormat("Failed to write results of batch {0} to production tracing DB: {1}",
batch.BatchNr, exc.Message);
return Retv.Error;
}
finally
{
if (session != null)
{
session.Close();
session.Dispose();
}
}
}
return Retv.OK;
}
/// <summary>
/// Write one meter results to the DB
/// </summary>
/// <param name="session">DB session</param>
/// <param name="wm">Watermeter entity</param>
int SaveSingleWM2DB(ISession session, Results.Entities.WaterMeter wm, string poName, WorkflowSummary wflowSummary, string worplace)
{
if (string.IsNullOrEmpty(wflowSummary.WorkstepName)) return 0;
IList<ReferenceRecord> existingRecords = session.QueryOver<ReferenceRecord>()
.Where(x => (x.Code1 == wm.SerialNr))
.List();
if (existingRecords.Count > 1)
{
/// Unexpected error
log.ErrorFormat("Multiple reference records exist: Code1 = {0}", wm.SerialNr);
return 0;
}
///
/// Reference record
///
ReferenceRecord refRecord = null;
if (existingRecords.Count == 1)
{
/// TODO: Check if the selected worflow is the same as in the found record
refRecord = existingRecords[0];
refRecord.POName = poName;
refRecord.Workflow = wflowSummary.Workflow.Name;
refRecord.Code2 = wm.SerialNrAux;
refRecord.Code3 = wm.CompleteSerialNr;
refRecord.Code4 = wm.RadioAddress;
}
else
{
refRecord = new ReferenceRecord(poName,
wflowSummary.Workflow.Name,
wflowSummary.Part1Name, /// PCB (iPERL) or housing (620/640) SAP number
wm.SerialNr, /// PcbNumber (iPERL) or housing S/N (620/640)
wflowSummary.Part2Name, /// Flowtube SAP nummber (iPERL) or "eRegister#" (640) or empty
wm.SerialNrAux, /// Flowtube S/N (iPERL) or eRegister number (640) or empty
wm.CompleteSerialNr, /// Complete assigned S/N
wm.RadioAddress); /// Radio address (iPERL and 640)
}
///
/// Step record
///
StepRecord stepRecord = new StepRecord(refRecord,
wflowSummary.WorkstepName,
workplace,
Common.CurrentUser.UserName(),
wm.PassedFromTests() ? 0 : 1);
if (refRecord.StepRecords == null)
{
refRecord.StepRecords = new List<StepRecord> { stepRecord };
}
else
{
refRecord.StepRecords.Add(stepRecord);
}
session.SaveOrUpdate(refRecord);
session.SaveOrUpdate(stepRecord);
return 1;
}
}
}
@@ -0,0 +1,25 @@
using System.Collections.Generic;
using TBF.Rig.Generic;
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
using TBF.Rig.RegisterReaders.iPerlASICReader.implementations;
namespace TBF.Rig.RegisterReaders.GenesisRegReader
{
public class Factory: IComponentFactory
{
public string ClassName { get { return this.GetType().Namespace.Substring(8); } }
public override string ToString() { return ClassName; }
public IComponent DummyComponent() { return new GenesisSmartReader(); }
public IComponent GetComponent(IComponentCfg cfg, IList<IComponent> components) { return new GenesisSmartReader(cfg); }
public IComponentCfg DefaultConfig() { return new GenesisCfg(this); }
public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component)
{
return ComponentCfgBase.CreateFromDbEntity(GenesisCfg.Serializer, component, this);
}
}
}
@@ -0,0 +1,75 @@
using System.Collections.Generic;
using System.Xml.Serialization;
using Common;
using Config.Entities;
using TBF.Rig.Generic;
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
namespace TBF.Rig.RegisterReaders.GenesisRegReader
{
public class GenesisCfg : ComponentCfgBase, Generic.IComponentCfg
{
public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(GenesisCfg) })[0];
public override XmlSerializer GetSerializer() { return Serializer; }
public IComponentCfgCtrl GetControl(IList<Component> cmpntEntities)
{
return new GenesisCfgCtrl();
}
///
/// Serialized parameters
///
public bool UseTcpIP;
public string OptoIPAddress;
public ushort OptoTcpipPortNr;
public int HeadCommunicationComPortNr;
public int OptoComPortNr;
public int RfidComPortNr; /// 0 = use MuxBoardNr
public int MuxBoardNr; /// 0 = use RfidComPort(Nr), otherwise mux. board nr. 1 .. 4
public int Group; /// Number written to QuidoRS to connct the watermeter to RfidComPort, 1 .. 10
public CommunicationInterface CommunicationInterface; /// Communication Interface: RFID or NFC
public bool EnableShowChanels;
public int IBeginDataFlush;
/// <summary> Procedure parameters </summary>
[XmlIgnore]
public ProcParams ProcParams;
public override IParamsProvider GetRuntimeProcParamsProvider() { return ProcParams; }
public override IParamsProvider CreateProcParamsProvider() { return new ProcParams(true); }
[XmlIgnore]
public MeterType MeterType { get { return (ProcParams != null) ? ProcParams.MeterType : MeterType.AutoDetect; } }
/// Private parameterless constructor invoked by all other (public) constructors
GenesisCfg()
{
Name = "Genesis";
ParentName = string.Empty;
OptoComPortNr = 10;
RfidComPortNr = 0; /// = use mux. board
MuxBoardNr = 1;
ProcParams = CreateProcParamsProvider() as ProcParams;
CommunicationInterface = CommunicationInterface.RFID;
HeadCommunicationComPortNr = 0;
EnableShowChanels = false;
IBeginDataFlush = 2000;
}
public GenesisCfg(IComponentFactory factory)
: this()
{
this.Factory = factory;
}
public string ToString(int i)
{
return $"{Name} Group1 (mux#)={MuxBoardNr}, Group2={Group}, Opto=Com{OptoComPortNr}, {CommunicationInterface}=Com{RfidComPortNr}, EnableShowChanels={EnableShowChanels}, IBeginDataFlush={IBeginDataFlush}";
}
}
}
@@ -0,0 +1,178 @@
///
/// Copyright (c) 2015-2017 Sensus Metering Systems
///
using System;
using System.Net;
using System.Windows.Forms;
using Common;
using TBF.Resources;
using TBF.Rig.Generic;
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
namespace TBF.Rig.RegisterReaders.GenesisRegReader
{
public partial class GenesisCfgCtrl : UserControl, IComponentCfgCtrl
{
public bool ShowMore { get { return false; } }
GenesisCfg config;
public IComponentCfg Config
{
get { return config as IComponentCfg; }
set
{
config = value as GenesisCfg;
Redraw();
}
}
public GenesisCfgCtrl()
{
InitializeComponent();
}
private void WaterMeterCfgCtrl_Load(object sender, EventArgs e)
{
nameLabel.Text = Strings.Name;
classNameLabel.Text = config.Factory.ClassName;
Redraw();
}
public void Closing()
{
}
void Redraw()
{
if (config == null) return; /// Control was not loaded, settings were not changed
nameTextBox.Text = config.Name;
radioButton1.Checked = config.UseTcpIP;
radioButton2.Checked = !config.UseTcpIP;
ipAddressTextBox.Text = (config.OptoIPAddress != null) ? config.OptoIPAddress : "0.0.0.0";
tcpipPortTextBox.Text = config.OptoTcpipPortNr.ToString();
headPortNrTextBox.Text = config.HeadCommunicationComPortNr.ToString();
optoSerialPortTextBox.Text = config.OptoComPortNr.ToString();
rfidPortNrTextBox.Text = config.RfidComPortNr.ToString();
muxBoardNrTextBox.Text = config.MuxBoardNr.ToString();
groupTextBox.Text = config.Group.ToString();
comboBoxCommunicationInterface.SelectedItem = config.CommunicationInterface.ToString();
tBBeginDataFlush.Text = config.IBeginDataFlush.ToString();
checkBox_EnableShowChanels.Checked = config.EnableShowChanels;
tabPage2.Controls.Add(new GenesisHeadTestCtrl(config));
}
public void Unlock()
{
nameTextBox.Enabled = true;
radioButton1.Enabled = true;
radioButton2.Enabled = true;
ipAddressTextBox.Enabled = true;
tcpipPortTextBox.Enabled = true;
optoSerialPortTextBox.Enabled = true;
rfidPortNrTextBox.Enabled = true;
headPortNrTextBox.Enabled = true;
muxBoardNrTextBox.Enabled = true;
groupTextBox.Enabled = true;
comboBoxCommunicationInterface.Enabled = true;
tBBeginDataFlush.Enabled = true;
checkBox_EnableShowChanels.Enabled = true;
}
public CfgUpdateFlags VerifyCfg(ref string message)
{
CfgUpdateFlags flags = CfgUpdateFlags.None;
int dummy;
if (radioButton1.Checked)
{
IPAddress dummyIPAddress;
if (!IPAddress.TryParse(ipAddressTextBox.Text, out dummyIPAddress))
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'IP address' is not valid";
}
ushort sdummy;
if (!ushort.TryParse(tcpipPortTextBox.Text, out sdummy))
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'TCP/IP port nr.' is not valid";
}
}
else
{
if (!int.TryParse(optoSerialPortTextBox.Text, out dummy) || dummy < 1 || dummy > 999)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'Opto serial port nr.' is not valid";
}
}
if (!int.TryParse(rfidPortNrTextBox.Text, out dummy) || dummy < 0 || dummy > 999)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'RFID serial port nr.' is not valid";
}
if (!int.TryParse(headPortNrTextBox.Text, out dummy) || dummy < 0 || dummy > 999)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'Head communication serial port nr.' is not valid";
}
if (!int.TryParse(muxBoardNrTextBox.Text, out dummy) || dummy < 1 || dummy > 4)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + string.Format(Strings.Invalid_0, muxBoardNrLabel.Text);
}
if (!int.TryParse(groupTextBox.Text, out dummy) || dummy < 1 || dummy > 10)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + string.Format(Strings.Invalid_0, groupLabel.Text);
}
if (!int.TryParse(tBBeginDataFlush.Text, out dummy) || dummy < 0 || dummy > 10000)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + string.Format(Strings.Invalid_0, tBBeginDataFlush.Text);
}
return flags;
}
public CfgUpdateFlags UpdateCfg()
{
CfgUpdateFlags flags = CfgUpdateFlags.RestartRqrd;
if (config == null) return CfgUpdateFlags.Error; /// Control was not loaded, settings were not changed
config.Name = nameTextBox.Text;
if (radioButton1.Checked)
{
config.UseTcpIP = true;
config.OptoIPAddress = ipAddressTextBox.Text;
config.OptoTcpipPortNr = ushort.Parse(tcpipPortTextBox.Text);
}
else
{
config.UseTcpIP = false;
config.OptoComPortNr = int.Parse(optoSerialPortTextBox.Text);
}
config.RfidComPortNr = int.Parse(rfidPortNrTextBox.Text);
config.MuxBoardNr = int.Parse(muxBoardNrTextBox.Text);
config.Group = int.Parse(groupTextBox.Text);
config.CommunicationInterface = (CommunicationInterface)comboBoxCommunicationInterface.SelectedIndex;
config.HeadCommunicationComPortNr = int.Parse(headPortNrTextBox.Text);
config.IBeginDataFlush = int.Parse(tBBeginDataFlush.Text);
config.EnableShowChanels = checkBox_EnableShowChanels.Checked;
return flags;
}
}
}
@@ -0,0 +1,486 @@
///
/// Copyright (c) 2015-2017 Sensus Metering Systems
///
namespace TBF.Rig.RegisterReaders.GenesisRegReader
{
partial class GenesisCfgCtrl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.tabControl1 = new System.Windows.Forms.TabControl();
this.tabPage1 = new System.Windows.Forms.TabPage();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.headPortNrTextBox = new System.Windows.Forms.TextBox();
this.label2 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.comboBoxCommunicationInterface = new System.Windows.Forms.ComboBox();
this.label1 = new System.Windows.Forms.Label();
this.rfidPortNrTextBox = new System.Windows.Forms.TextBox();
this.rfidSerialPortNrLabel = new System.Windows.Forms.Label();
this.optoDataGroupBox = new System.Windows.Forms.GroupBox();
this.checkBox_EnableShowChanels = new System.Windows.Forms.CheckBox();
this.tBBeginDataFlush = new System.Windows.Forms.TextBox();
this.labelFlush = new System.Windows.Forms.Label();
this.tcpipPortLabel = new System.Windows.Forms.Label();
this.tcpipPortTextBox = new System.Windows.Forms.TextBox();
this.ipAddressLabel = new System.Windows.Forms.Label();
this.ipAddressTextBox = new System.Windows.Forms.TextBox();
this.radioButton1 = new System.Windows.Forms.RadioButton();
this.radioButton2 = new System.Windows.Forms.RadioButton();
this.optoSerialPortLabel = new System.Windows.Forms.Label();
this.optoSerialPortTextBox = new System.Windows.Forms.TextBox();
this.groupTextBox = new System.Windows.Forms.TextBox();
this.groupLabel = new System.Windows.Forms.Label();
this.muxBoardNrTextBox = new System.Windows.Forms.TextBox();
this.muxBoardNrLabel = new System.Windows.Forms.Label();
this.nameTextBox = new System.Windows.Forms.TextBox();
this.nameLabel = new System.Windows.Forms.Label();
this.classNameLabel = new System.Windows.Forms.Label();
this.tabPage2 = new System.Windows.Forms.TabPage();
this.tabControl1.SuspendLayout();
this.tabPage1.SuspendLayout();
this.groupBox2.SuspendLayout();
this.groupBox1.SuspendLayout();
this.optoDataGroupBox.SuspendLayout();
this.SuspendLayout();
//
// tabControl1
//
this.tabControl1.Controls.Add(this.tabPage1);
this.tabControl1.Controls.Add(this.tabPage2);
this.tabControl1.Location = new System.Drawing.Point(3, 4);
this.tabControl1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = 0;
this.tabControl1.Size = new System.Drawing.Size(687, 540);
this.tabControl1.TabIndex = 0;
//
// tabPage1
//
this.tabPage1.Controls.Add(this.groupBox2);
this.tabPage1.Controls.Add(this.label4);
this.tabPage1.Controls.Add(this.label3);
this.tabPage1.Controls.Add(this.groupBox1);
this.tabPage1.Controls.Add(this.optoDataGroupBox);
this.tabPage1.Controls.Add(this.groupTextBox);
this.tabPage1.Controls.Add(this.groupLabel);
this.tabPage1.Controls.Add(this.muxBoardNrTextBox);
this.tabPage1.Controls.Add(this.muxBoardNrLabel);
this.tabPage1.Controls.Add(this.nameTextBox);
this.tabPage1.Controls.Add(this.nameLabel);
this.tabPage1.Controls.Add(this.classNameLabel);
this.tabPage1.Location = new System.Drawing.Point(4, 29);
this.tabPage1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.tabPage1.Name = "tabPage1";
this.tabPage1.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.tabPage1.Size = new System.Drawing.Size(679, 507);
this.tabPage1.TabIndex = 0;
this.tabPage1.Text = "Config";
this.tabPage1.UseVisualStyleBackColor = true;
//
// groupBox2
//
this.groupBox2.Controls.Add(this.headPortNrTextBox);
this.groupBox2.Controls.Add(this.label2);
this.groupBox2.Location = new System.Drawing.Point(11, 450);
this.groupBox2.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.groupBox2.Size = new System.Drawing.Size(621, 52);
this.groupBox2.TabIndex = 26;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "Head Communication";
//
// headPortNrTextBox
//
this.headPortNrTextBox.Enabled = false;
this.headPortNrTextBox.Location = new System.Drawing.Point(494, 19);
this.headPortNrTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.headPortNrTextBox.Name = "headPortNrTextBox";
this.headPortNrTextBox.Size = new System.Drawing.Size(49, 26);
this.headPortNrTextBox.TabIndex = 8;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(361, 22);
this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(107, 20);
this.label2.TabIndex = 7;
this.label2.Text = "Serial port nr.:";
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(234, 126);
this.label4.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(52, 20);
this.label4.TabIndex = 25;
this.label4.Text = "1 .. 10";
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(234, 90);
this.label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(43, 20);
this.label3.TabIndex = 24;
this.label3.Text = "1 .. 4";
//
// groupBox1
//
this.groupBox1.Controls.Add(this.comboBoxCommunicationInterface);
this.groupBox1.Controls.Add(this.label1);
this.groupBox1.Controls.Add(this.rfidPortNrTextBox);
this.groupBox1.Controls.Add(this.rfidSerialPortNrLabel);
this.groupBox1.Location = new System.Drawing.Point(11, 363);
this.groupBox1.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Padding = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.groupBox1.Size = new System.Drawing.Size(621, 85);
this.groupBox1.TabIndex = 23;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "RFID / NFC communication (in case mux. board is not used)";
//
// comboBoxCommunicationInterface
//
this.comboBoxCommunicationInterface.Enabled = false;
this.comboBoxCommunicationInterface.FormattingEnabled = true;
this.comboBoxCommunicationInterface.Items.AddRange(new object[] { "RFID", "NFC" });
this.comboBoxCommunicationInterface.Location = new System.Drawing.Point(226, 34);
this.comboBoxCommunicationInterface.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.comboBoxCommunicationInterface.Name = "comboBoxCommunicationInterface";
this.comboBoxCommunicationInterface.Size = new System.Drawing.Size(79, 28);
this.comboBoxCommunicationInterface.TabIndex = 9;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(46, 38);
this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(187, 20);
this.label1.TabIndex = 8;
this.label1.Text = "Communication Interface";
//
// rfidPortNrTextBox
//
this.rfidPortNrTextBox.Enabled = false;
this.rfidPortNrTextBox.Location = new System.Drawing.Point(494, 32);
this.rfidPortNrTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.rfidPortNrTextBox.Name = "rfidPortNrTextBox";
this.rfidPortNrTextBox.Size = new System.Drawing.Size(49, 26);
this.rfidPortNrTextBox.TabIndex = 7;
//
// rfidSerialPortNrLabel
//
this.rfidSerialPortNrLabel.AutoSize = true;
this.rfidSerialPortNrLabel.Location = new System.Drawing.Point(361, 38);
this.rfidSerialPortNrLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.rfidSerialPortNrLabel.Name = "rfidSerialPortNrLabel";
this.rfidSerialPortNrLabel.Size = new System.Drawing.Size(107, 20);
this.rfidSerialPortNrLabel.TabIndex = 6;
this.rfidSerialPortNrLabel.Text = "Serial port nr.:";
//
// optoDataGroupBox
//
this.optoDataGroupBox.Controls.Add(this.checkBox_EnableShowChanels);
this.optoDataGroupBox.Controls.Add(this.tBBeginDataFlush);
this.optoDataGroupBox.Controls.Add(this.labelFlush);
this.optoDataGroupBox.Controls.Add(this.tcpipPortLabel);
this.optoDataGroupBox.Controls.Add(this.tcpipPortTextBox);
this.optoDataGroupBox.Controls.Add(this.ipAddressLabel);
this.optoDataGroupBox.Controls.Add(this.ipAddressTextBox);
this.optoDataGroupBox.Controls.Add(this.radioButton1);
this.optoDataGroupBox.Controls.Add(this.radioButton2);
this.optoDataGroupBox.Controls.Add(this.optoSerialPortLabel);
this.optoDataGroupBox.Controls.Add(this.optoSerialPortTextBox);
this.optoDataGroupBox.Location = new System.Drawing.Point(11, 164);
this.optoDataGroupBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.optoDataGroupBox.Name = "optoDataGroupBox";
this.optoDataGroupBox.Padding = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.optoDataGroupBox.Size = new System.Drawing.Size(621, 189);
this.optoDataGroupBox.TabIndex = 18;
this.optoDataGroupBox.TabStop = false;
this.optoDataGroupBox.Text = "Opto-data";
//
// checkBox_EnableShowChanels
//
this.checkBox_EnableShowChanels.Checked = true;
this.checkBox_EnableShowChanels.CheckState = System.Windows.Forms.CheckState.Checked;
this.checkBox_EnableShowChanels.Enabled = false;
this.checkBox_EnableShowChanels.Location = new System.Drawing.Point(351, 147);
this.checkBox_EnableShowChanels.Name = "checkBox_EnableShowChanels";
this.checkBox_EnableShowChanels.Size = new System.Drawing.Size(238, 24);
this.checkBox_EnableShowChanels.TabIndex = 10;
this.checkBox_EnableShowChanels.Text = "Enable Show Channels";
this.checkBox_EnableShowChanels.UseVisualStyleBackColor = true;
//
// tBBeginDataFlush
//
this.tBBeginDataFlush.Enabled = false;
this.tBBeginDataFlush.Location = new System.Drawing.Point(474, 105);
this.tBBeginDataFlush.MaxLength = 8;
this.tBBeginDataFlush.Name = "tBBeginDataFlush";
this.tBBeginDataFlush.Size = new System.Drawing.Size(69, 26);
this.tBBeginDataFlush.TabIndex = 9;
this.tBBeginDataFlush.Text = "2000";
this.tBBeginDataFlush.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
//
// labelFlush
//
this.labelFlush.Location = new System.Drawing.Point(328, 109);
this.labelFlush.Name = "labelFlush";
this.labelFlush.Size = new System.Drawing.Size(140, 22);
this.labelFlush.TabIndex = 8;
this.labelFlush.Text = "Begin Data Flush:";
//
// tcpipPortLabel
//
this.tcpipPortLabel.AutoSize = true;
this.tcpipPortLabel.Location = new System.Drawing.Point(46, 109);
this.tcpipPortLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.tcpipPortLabel.Name = "tcpipPortLabel";
this.tcpipPortLabel.Size = new System.Drawing.Size(68, 20);
this.tcpipPortLabel.TabIndex = 4;
this.tcpipPortLabel.Text = "Port nr..:";
//
// tcpipPortTextBox
//
this.tcpipPortTextBox.Enabled = false;
this.tcpipPortTextBox.Location = new System.Drawing.Point(161, 105);
this.tcpipPortTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.tcpipPortTextBox.Name = "tcpipPortTextBox";
this.tcpipPortTextBox.Size = new System.Drawing.Size(57, 26);
this.tcpipPortTextBox.TabIndex = 5;
//
// ipAddressLabel
//
this.ipAddressLabel.AutoSize = true;
this.ipAddressLabel.Location = new System.Drawing.Point(46, 74);
this.ipAddressLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.ipAddressLabel.Name = "ipAddressLabel";
this.ipAddressLabel.Size = new System.Drawing.Size(93, 20);
this.ipAddressLabel.TabIndex = 2;
this.ipAddressLabel.Text = "IP address.:";
//
// ipAddressTextBox
//
this.ipAddressTextBox.Enabled = false;
this.ipAddressTextBox.Location = new System.Drawing.Point(161, 69);
this.ipAddressTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.ipAddressTextBox.Name = "ipAddressTextBox";
this.ipAddressTextBox.Size = new System.Drawing.Size(145, 26);
this.ipAddressTextBox.TabIndex = 3;
//
// radioButton1
//
this.radioButton1.AutoSize = true;
this.radioButton1.Checked = true;
this.radioButton1.Enabled = false;
this.radioButton1.Location = new System.Drawing.Point(33, 29);
this.radioButton1.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.radioButton1.Name = "radioButton1";
this.radioButton1.Size = new System.Drawing.Size(109, 24);
this.radioButton1.TabIndex = 0;
this.radioButton1.TabStop = true;
this.radioButton1.Text = "Use TCP/IP";
this.radioButton1.UseVisualStyleBackColor = true;
//
// radioButton2
//
this.radioButton2.AutoSize = true;
this.radioButton2.Enabled = false;
this.radioButton2.Location = new System.Drawing.Point(351, 29);
this.radioButton2.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.radioButton2.Name = "radioButton2";
this.radioButton2.Size = new System.Drawing.Size(129, 24);
this.radioButton2.TabIndex = 1;
this.radioButton2.Text = "Use serial port";
this.radioButton2.UseVisualStyleBackColor = true;
//
// optoSerialPortLabel
//
this.optoSerialPortLabel.AutoSize = true;
this.optoSerialPortLabel.Location = new System.Drawing.Point(361, 69);
this.optoSerialPortLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.optoSerialPortLabel.Name = "optoSerialPortLabel";
this.optoSerialPortLabel.Size = new System.Drawing.Size(107, 20);
this.optoSerialPortLabel.TabIndex = 6;
this.optoSerialPortLabel.Text = "Serial port nr.:";
//
// optoSerialPortTextBox
//
this.optoSerialPortTextBox.Enabled = false;
this.optoSerialPortTextBox.Location = new System.Drawing.Point(494, 65);
this.optoSerialPortTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.optoSerialPortTextBox.Name = "optoSerialPortTextBox";
this.optoSerialPortTextBox.Size = new System.Drawing.Size(49, 26);
this.optoSerialPortTextBox.TabIndex = 7;
//
// groupTextBox
//
this.groupTextBox.Enabled = false;
this.groupTextBox.Location = new System.Drawing.Point(172, 121);
this.groupTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.groupTextBox.Name = "groupTextBox";
this.groupTextBox.Size = new System.Drawing.Size(49, 26);
this.groupTextBox.TabIndex = 22;
//
// groupLabel
//
this.groupLabel.AutoSize = true;
this.groupLabel.Location = new System.Drawing.Point(7, 126);
this.groupLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.groupLabel.Name = "groupLabel";
this.groupLabel.Size = new System.Drawing.Size(67, 20);
this.groupLabel.TabIndex = 21;
this.groupLabel.Text = "Group 2";
//
// muxBoardNrTextBox
//
this.muxBoardNrTextBox.Enabled = false;
this.muxBoardNrTextBox.Location = new System.Drawing.Point(172, 86);
this.muxBoardNrTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.muxBoardNrTextBox.Name = "muxBoardNrTextBox";
this.muxBoardNrTextBox.Size = new System.Drawing.Size(49, 26);
this.muxBoardNrTextBox.TabIndex = 20;
//
// muxBoardNrLabel
//
this.muxBoardNrLabel.AutoSize = true;
this.muxBoardNrLabel.Location = new System.Drawing.Point(7, 90);
this.muxBoardNrLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.muxBoardNrLabel.Name = "muxBoardNrLabel";
this.muxBoardNrLabel.Size = new System.Drawing.Size(159, 20);
this.muxBoardNrLabel.TabIndex = 19;
this.muxBoardNrLabel.Text = "Group 1 (mux. board)";
//
// nameTextBox
//
this.nameTextBox.Enabled = false;
this.nameTextBox.Location = new System.Drawing.Point(172, 50);
this.nameTextBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.nameTextBox.Name = "nameTextBox";
this.nameTextBox.Size = new System.Drawing.Size(180, 26);
this.nameTextBox.TabIndex = 17;
//
// nameLabel
//
this.nameLabel.AutoSize = true;
this.nameLabel.Location = new System.Drawing.Point(7, 55);
this.nameLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.nameLabel.Name = "nameLabel";
this.nameLabel.Size = new System.Drawing.Size(51, 20);
this.nameLabel.TabIndex = 16;
this.nameLabel.Text = "Name";
//
// classNameLabel
//
this.classNameLabel.AutoSize = true;
this.classNameLabel.Location = new System.Drawing.Point(168, 14);
this.classNameLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.classNameLabel.Name = "classNameLabel";
this.classNameLabel.Size = new System.Drawing.Size(90, 20);
this.classNameLabel.TabIndex = 15;
this.classNameLabel.Text = "ClassName";
//
// tabPage2
//
this.tabPage2.Location = new System.Drawing.Point(4, 29);
this.tabPage2.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.tabPage2.Name = "tabPage2";
this.tabPage2.Padding = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.tabPage2.Size = new System.Drawing.Size(679, 507);
this.tabPage2.TabIndex = 1;
this.tabPage2.Text = "Test";
this.tabPage2.UseVisualStyleBackColor = true;
//
// GenesisCfgCtrl
//
this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.tabControl1);
this.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5);
this.Name = "GenesisCfgCtrl";
this.Size = new System.Drawing.Size(694, 548);
this.Load += new System.EventHandler(this.WaterMeterCfgCtrl_Load);
this.tabControl1.ResumeLayout(false);
this.tabPage1.ResumeLayout(false);
this.tabPage1.PerformLayout();
this.groupBox2.ResumeLayout(false);
this.groupBox2.PerformLayout();
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.optoDataGroupBox.ResumeLayout(false);
this.optoDataGroupBox.PerformLayout();
this.ResumeLayout(false);
}
private System.Windows.Forms.CheckBox checkBox_EnableShowChanels;
private System.Windows.Forms.Label labelFlush;
private System.Windows.Forms.TextBox tBBeginDataFlush;
#endregion
private System.Windows.Forms.TabControl tabControl1;
private System.Windows.Forms.TabPage tabPage1;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.ComboBox comboBoxCommunicationInterface;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox rfidPortNrTextBox;
private System.Windows.Forms.Label rfidSerialPortNrLabel;
private System.Windows.Forms.GroupBox optoDataGroupBox;
private System.Windows.Forms.Label tcpipPortLabel;
private System.Windows.Forms.TextBox tcpipPortTextBox;
private System.Windows.Forms.Label ipAddressLabel;
private System.Windows.Forms.TextBox ipAddressTextBox;
private System.Windows.Forms.RadioButton radioButton1;
private System.Windows.Forms.RadioButton radioButton2;
private System.Windows.Forms.Label optoSerialPortLabel;
private System.Windows.Forms.TextBox optoSerialPortTextBox;
private System.Windows.Forms.TextBox groupTextBox;
private System.Windows.Forms.Label groupLabel;
private System.Windows.Forms.TextBox muxBoardNrTextBox;
private System.Windows.Forms.Label muxBoardNrLabel;
private System.Windows.Forms.TextBox nameTextBox;
private System.Windows.Forms.Label nameLabel;
private System.Windows.Forms.Label classNameLabel;
private System.Windows.Forms.TabPage tabPage2;
private System.Windows.Forms.GroupBox groupBox2;
private System.Windows.Forms.TextBox headPortNrTextBox;
private System.Windows.Forms.Label label2;
}
}
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -0,0 +1,137 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader
{
partial class GenesisHeadTestCtrl
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.optoTestGroupBox = new System.Windows.Forms.GroupBox();
this.optoListBox = new System.Windows.Forms.ListBox();
this.rfidOutputListBox = new System.Windows.Forms.ListBox();
this.RfidTestGroupBox = new System.Windows.Forms.GroupBox();
this.label2 = new System.Windows.Forms.Label();
this.rfidCommandComboBox = new System.Windows.Forms.ComboBox();
this.commandTestButton = new System.Windows.Forms.Button();
this.optoTestGroupBox.SuspendLayout();
this.RfidTestGroupBox.SuspendLayout();
this.SuspendLayout();
//
// optoTestGroupBox
//
this.optoTestGroupBox.Controls.Add(this.optoListBox);
this.optoTestGroupBox.Location = new System.Drawing.Point(5, 4);
this.optoTestGroupBox.Name = "optoTestGroupBox";
this.optoTestGroupBox.Size = new System.Drawing.Size(591, 161);
this.optoTestGroupBox.TabIndex = 2;
this.optoTestGroupBox.TabStop = false;
this.optoTestGroupBox.Text = "Opto-data";
//
// optoListBox
//
this.optoListBox.FormattingEnabled = true;
this.optoListBox.ItemHeight = 16;
this.optoListBox.Location = new System.Drawing.Point(7, 22);
this.optoListBox.Name = "optoListBox";
this.optoListBox.Size = new System.Drawing.Size(573, 132);
this.optoListBox.TabIndex = 0;
//
// rfidOutputListBox
//
this.rfidOutputListBox.FormattingEnabled = true;
this.rfidOutputListBox.ItemHeight = 16;
this.rfidOutputListBox.Location = new System.Drawing.Point(5, 54);
this.rfidOutputListBox.Name = "rfidOutputListBox";
this.rfidOutputListBox.SelectionMode = System.Windows.Forms.SelectionMode.None;
this.rfidOutputListBox.Size = new System.Drawing.Size(575, 164);
this.rfidOutputListBox.TabIndex = 3;
//
// RfidTestGroupBox
//
this.RfidTestGroupBox.Controls.Add(this.rfidOutputListBox);
this.RfidTestGroupBox.Controls.Add(this.label2);
this.RfidTestGroupBox.Controls.Add(this.rfidCommandComboBox);
this.RfidTestGroupBox.Controls.Add(this.commandTestButton);
this.RfidTestGroupBox.Location = new System.Drawing.Point(5, 171);
this.RfidTestGroupBox.Name = "RfidTestGroupBox";
this.RfidTestGroupBox.Size = new System.Drawing.Size(591, 224);
this.RfidTestGroupBox.TabIndex = 3;
this.RfidTestGroupBox.TabStop = false;
this.RfidTestGroupBox.Text = "RFID / NFC data";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(2, 25);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(69, 16);
this.label2.TabIndex = 2;
this.label2.Text = "Command";
//
// rfidCommandComboBox
//
this.rfidCommandComboBox.FormattingEnabled = true;
this.rfidCommandComboBox.Location = new System.Drawing.Point(86, 19);
this.rfidCommandComboBox.Name = "rfidCommandComboBox";
this.rfidCommandComboBox.Size = new System.Drawing.Size(341, 24);
this.rfidCommandComboBox.TabIndex = 1;
//
// commandTestButton
//
this.commandTestButton.Location = new System.Drawing.Point(449, 19);
this.commandTestButton.Name = "commandTestButton";
this.commandTestButton.Size = new System.Drawing.Size(126, 24);
this.commandTestButton.TabIndex = 0;
this.commandTestButton.Text = "Send command";
this.commandTestButton.UseVisualStyleBackColor = true;
this.commandTestButton.MouseClick += new System.Windows.Forms.MouseEventHandler(this.CommandTestButtonClick);
//
// IperlHeadTestCtrl
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.optoTestGroupBox);
this.Controls.Add(this.RfidTestGroupBox);
this.Name = "GenesisHeadTestCtrl";
this.Size = new System.Drawing.Size(611, 432);
this.Load += new System.EventHandler(this.UserControl_Load);
this.optoTestGroupBox.ResumeLayout(false);
this.RfidTestGroupBox.ResumeLayout(false);
this.RfidTestGroupBox.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.GroupBox optoTestGroupBox;
private System.Windows.Forms.ListBox rfidOutputListBox;
private System.Windows.Forms.GroupBox RfidTestGroupBox;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.ComboBox rfidCommandComboBox;
private System.Windows.Forms.Button commandTestButton;
private System.Windows.Forms.ListBox optoListBox;
}
}
@@ -0,0 +1,90 @@
using System;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
using TBF.Rig.RegisterReaders.iPerlReaderUNI.common;
using TBF.Rig.Sequences;
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication;
namespace TBF.Rig.RegisterReaders.GenesisRegReader
{
public partial class GenesisHeadTestCtrl : UserControl
{
private IUniHeadTestCtrl<OptoReceivedEventArgs> _ctrl;
private IUniHeadTestCtrl<OptoReceivedEventArgs> Ctrl { get => _ctrl; }
private GenesisCfg _genesisCfg;
private GenesisSmartReader _genesisSmartReader;
Thread optoThread;
private bool stopWorkerThread;
public GenesisHeadTestCtrl(GenesisCfg config)
{
this._ctrl = new GenesisImplHeadTestCtrl();
_genesisCfg = config;
Ctrl.config = config;
InitializeComponent();
if (config == null) return;
//SmartCommunicationForm.TestMethodCfg = new TestMethodCfg(null); // default values for iPerlCommunication
foreach(var head in ProcessData.SmartHeadsUni)
{
if (head != null && head.Name == config.Name) { Ctrl.ISmartReader = head; }
}
rfidCommandComboBox.DisplayMember = "Name";
rfidCommandComboBox.ValueMember = "Value";
var items = Ctrl.GetComboOperationsPairs();
/*
// CTRL+ALT+double click - hidden poweruser menu
if (((Keyboard.ModifierKeys & Keys.Control) == Keys.Control) && ((Keyboard.ModifierKeys & Keys.Alt) == Keys.Alt) && Users.CurrentUser.AuthorizedAs == AuthorizedAs.PowerUser)
{
Array.Resize(ref items, items.Length + 1);
items[items.Length - 1] = new { Name = "Kluc", Value = "Kluc" };
}
*/
rfidCommandComboBox.DataSource = items.Select(i => i.Name).ToArray();
Ctrl.Initialize();
Ctrl.OptoReceivedHandler += (EventHandler<OptoReceivedEventArgs>)((sndr, args) =>
{
if (this.InvokeRequired)
this.Invoke((Delegate)new EventHandler<OptoReceivedEventArgs>(this.OnOptoReceived2), sndr, (object)args);
else
this.OnOptoReceived2(sndr, args);
});
}
public void OnOptoReceived2(object sender, OptoReceivedEventArgs args)
{
optoListBox.Items.Insert(0,args.Data);
}
private void CommandTestButtonClick(object sender, MouseEventArgs e)
{
Ctrl.CommandTestButtonClick(sender, e, new Arguments()
{
ISmartReader = Ctrl.ISmartReader,
OptoListBox = optoListBox,
RfidCommandComboBox = rfidCommandComboBox,
RfidOutputListBox = rfidOutputListBox
});
}
private void UserControl_Load(object sender, EventArgs e)
{
this.ParentForm.FormClosing += new FormClosingEventHandler(ParentForm_FormClosing);
}
void ParentForm_FormClosing(object sender, FormClosingEventArgs e)
{
//OnHandleDestroyed(new EventArgs());
Ctrl.Destroy();
}
}
}
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -0,0 +1,175 @@
using System;
using System.Globalization;
using System.Linq;
using System.Text;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.common
{
public static class HexFormatter
{
/// <summary>
/// Byte to hex string.
/// Formats a single byte as 0xNN.
/// Example: 0x0D
/// </summary>
public static string ToHex(byte value)
{
return "0x" + value.ToString("X2");
}
/// <summary>
/// int to byte - securely
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static byte ToHexByte(int value)
{
if (value < 0 || value > 255)
throw new ArgumentOutOfRangeException(nameof(value),
"Value must be between 0 and 255.");
return (byte)value;
}
/// <summary>
/// Formats a byte array as 0xNN 0xNN ...
/// </summary>
public static string ToHex(byte[] data)
{
if (data == null || data.Length == 0)
return "<empty>";
var sb = new System.Text.StringBuilder();
for (int i = 0; i < data.Length; i++)
{
if (i > 0)
sb.Append(' ');
sb.Append("0x");
sb.Append(data[i].ToString("X2"));
}
return sb.ToString();
}
/// <summary>
/// Formats a byte array exactly as shown in serial terminals.
/// Example: "0D 04 08 01 00 1A"
/// </summary>
public static string ToSerialHex(byte[] data)
{
if (data == null || data.Length == 0)
return string.Empty;
var sb = new System.Text.StringBuilder();
for (int i = 0; i < data.Length; i++)
{
if (i > 0)
sb.Append(' ');
sb.Append(data[i].ToString("X2"));
}
return sb.ToString();
}
public static string ToHexWithAscii(byte value)
{
char c = (value >= 32 && value <= 126) ? (char)value : '.';
return $"0x{value:X2} ('{c}')";
}
public static string ToSerialHexWithAscii(byte[] data)
{
if (data == null || data.Length == 0)
return string.Empty;
var hex = new StringBuilder(data.Length * 3);
var ascii = new StringBuilder(data.Length);
foreach (byte b in data)
{
hex.Append(b.ToString("X2")).Append(' ');
// Printable ASCII range
if (b >= 32 && b <= 126)
{
ascii.Append((char)b);
}
// Binary numbers 09 -> show digit
else if (b <= 9)
{
ascii.Append((char)('0' + b));
}
else
{
ascii.Append('.');
}
}
// remove last trailing space in hex
if (hex.Length > 0)
hex.Length--;
return $"{hex} | {ascii}";
}
public static string ToHex(int value)
{
return $"0x{(byte)value:X2}";
}
public static byte[] IntToBytesBE(int value, int byteCount)
{
var result = new byte[byteCount];
for (int i = 0; i < byteCount; i++)
result[byteCount - 1 - i] = (byte)(value >> (8 * i));
return result;
}
public static byte[] IntToBytesLE(int value, int byteCount)
{
var result = new byte[byteCount];
for (int i = 0; i < byteCount; i++)
result[i] = (byte)(value >> (8 * i));
return result;
}
public static byte[] AsciiToBytes(string text)
{
return string.IsNullOrEmpty(text)
? Array.Empty<byte>()
: System.Text.Encoding.ASCII.GetBytes(text);
}
/// <summary>
/// Converts a hex string to a byte array.
/// Like: string hex = "3F 76 65 72 73 3A 20 48 61 72 72 79 20 54 3A 42 38 30 30 2C 20";
/// </summary>
/// <param name="hex"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static byte[] HexStringToByteArray(string hex)
{
if (hex == null)
throw new ArgumentNullException(nameof(hex));
return hex
.Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(b => byte.Parse(b, NumberStyles.HexNumber, CultureInfo.InvariantCulture))
.ToArray();
}
}
}
@@ -0,0 +1,391 @@
///
/// Copyright (c) 2015-2021 Sensus Metering Systems
///
using System;
using System.Globalization;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.StreamingProtocol;
using Xylem.Common.Metrology.Measurements;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.common
{
public enum OptoTelegramFlags : byte
{
OK = 0,
OK_TestStart,
OK_TestEnd,
InvalidTelegram, /// Wrong telegram format of checksum error
SyncError,
}
public class OptoTelegramRaw
{
public static readonly int Length = 42;
private static CultureInfo culture;
public MeasurementRecord data;
///
/// Strobed value
///
public static decimal TestStartTimestampDec;
///
/// Stored values
///
public OptoTelegramFlags Flags;
public DateTime DateTime;
/// From PC
public float RefFlow;
/// [m3/h]
public int Counter;
public Int32 EmfRaw;
/// Signed EMF from iPerl opto data
public Int16 MagneticFieldRaw;
public Int16 FlowRaw;
public double VolumeRaw;
public double VolumeRawExt;
public Int16 Impedance;
public double Timestamp;
public double TimestampExt;
public byte CheckSum;
public int iChannel = -1;
public int IChannel()
{
return iChannel;
}
///
/// Calculated values
///
public double EMF()
{
return 0.000000333 * (double)EmfRaw;
}
public double MagneticField()
{
return (double)MagneticFieldRaw;
}
public double Flow(double scalingFactor)
{
return 0.225 * scalingFactor * (double)FlowRaw;
}
public double Volume(double scalingFactor)
{
return 0.0000625 * scalingFactor * (double)VolumeRawExt;
}
public Int32 FlipTime()
{
return Impedance;
}
public decimal TimestampDec()
{
return (decimal)TimestampExt / (decimal)8192;
}
public double VolumeDelta(double scalingFactor, OptoTelegramRaw previous)
{
return (previous == null) ? 0 : Volume(scalingFactor) - previous.Volume(scalingFactor);
}
public decimal TimeDelta()
{
return TimestampDec() - TestStartTimestampDec;
}
public string Label()
{
if (Flags == OptoTelegramFlags.OK_TestStart) return "#### start test ####";
else if (Flags == OptoTelegramFlags.OK_TestEnd) return "#### end of test ####";
else return string.Empty;
}
static OptoTelegramRaw()
{
culture = CultureInfo.CreateSpecificCulture("DE"); /// This is to use comma as decimal number separator
}
public OptoTelegramRaw()
{
}
/// <summary>
/// update data by CalibrationRecord
/// </summary>
/// <param name="data"></param>
/// <param name="counter"></param>
/// <param name="refFlow"></param>
/// <param name="volumeRawExtLast"></param>
/// <param name="timestampExtLast"></param>
/// <exception cref="ArgumentNullException"></exception>
public void UpdateFromSmart(
CalibrationRecord data,
int counter,
float refFlow,
ref double volumeRawExtLast,
ref double timestampExtLast)
{
if (data == null)
throw new ArgumentNullException(nameof(data));
this.data = data;
UpdateData( data.Channel, data.VolumeCm, data.OverflowVolumeCm, data.TimeS, data.OverflowTimeS, counter, refFlow, ref volumeRawExtLast, ref timestampExtLast);
}
/// <summary>
/// /// update data by FlowTestRecord
/// </summary>
/// <param name="data"></param>
/// <param name="counter"></param>
/// <param name="refFlow"></param>
/// <param name="volumeRawExtLast"></param>
/// <param name="timestampExtLast"></param>
/// <exception cref="ArgumentNullException"></exception>
public void UpdateFromSmart(
FlowTestRecord data,
int counter,
float refFlow,
ref double volumeRawExtLast,
ref double timestampExtLast)
{
if (data == null)
throw new ArgumentNullException(nameof(data));
this.data = data;
UpdateData( data.Channel, data.VolumeCm, data.OverflowVolumeCm, data.TimeS, data.OverflowTimeS, counter, refFlow, ref volumeRawExtLast, ref timestampExtLast);
}
/// <summary>
/// update data by OptoTelegramRaw
/// </summary>
/// <param name="channel"></param>
/// <param name="volumeCm"></param>
/// <param name="timeS"></param>
/// <param name="counter"></param>
/// <param name="refFlow"></param>
/// <param name="volumeRawExtLast"></param>
/// <param name="timestampExtLast"></param>
private void UpdateData(
int channel,
double volumeCm,
double OverflowVolumeCm,
double timeS,
double OverflowTimeS,
int counter,
float refFlow,
ref double volumeRawExtLast,
ref double timestampExtLast)
{
iChannel = channel - 1;
DateTime = DateTime.Now;
Counter = counter;
RefFlow = refFlow;
Flags = OptoTelegramFlags.OK;
FlowRaw = 0;
double nV = NormalizeByOverflow(volumeCm, OverflowVolumeCm);
VolumeRaw = nV * 1000.0; // liters
CheckSum = 0;
Impedance = 0;
EmfRaw = 0;
MagneticFieldRaw = 0;
double ts = NormalizeByOverflow(timeS, OverflowTimeS);
Timestamp = ts;
VolumeRawExt = UnwrapVolume(VolumeRaw,OverflowVolumeCm * 1000, ref volumeRawExtLast);
TimestampExt = UnwrapTimestamp(ts,OverflowTimeS, ref timestampExtLast);
}
private static double NormalizeByOverflow(double value, double overfValue = 0)
{
double nValue = value % overfValue;
if (nValue < 0)
nValue += overfValue;
return nValue;
}
private static double UnwrapVolume(double currentVolume, double overfValue, ref double volumeRawExtLast)
{
double result;
if (double.IsNaN(volumeRawExtLast))
{
result = currentVolume;
}
else
{
double lastMod = volumeRawExtLast % overfValue;
if (lastMod < 0)
lastMod += overfValue;
double delta = currentVolume - lastMod;
if (delta < -overfValue / 2.0)
delta += overfValue;
else if (delta > overfValue / 2.0)
delta -= overfValue;
result = volumeRawExtLast + delta;
}
volumeRawExtLast = result;
return result;
}
private static double UnwrapTimestamp(double currentTimestamp,double overfValue, ref double timestampExtLast)
{
double result;
if (double.IsNaN(timestampExtLast))
{
result = currentTimestamp;
}
else
{
double lastMod = timestampExtLast % overfValue;
if (lastMod < 0)
lastMod += overfValue;
double delta = currentTimestamp - lastMod;
if (delta < -overfValue / 2.0)
delta += overfValue;
else if (delta > overfValue / 2.0)
delta -= overfValue;
result = timestampExtLast + delta;
}
timestampExtLast = result;
return result;
}
/// <summary>
/// Alternative to UpdateFromString(...) when data are flushed
/// </summary>
public bool UpdateFromStringDummy(string telegram)
{
DateTime = DateTime.Now;
RefFlow = 0;
if ((telegram == null) || (telegram.Length < Length) ||
(telegram[6] != '\t') || (telegram[11] != '\t') || (telegram[16] != '\t') ||
(telegram[23] != '\t') || (telegram[28] != '\t') || (telegram[37] != '\t') ||
(telegram[40] != '\r') || (telegram[41] != '\n'))
{
Flags = OptoTelegramFlags.InvalidTelegram;
return false;
}
bool f7 = byte.TryParse(telegram.Substring(38, 2), NumberStyles.HexNumber, CultureInfo.CurrentCulture,
out CheckSum);
byte calculatedCheckSum = 0;
for (int i = 0; i < Length - 4; i++)
{
calculatedCheckSum += (byte)telegram[i];
}
bool allOk = f7 && (calculatedCheckSum == CheckSum);
Flags = allOk ? OptoTelegramFlags.OK : OptoTelegramFlags.InvalidTelegram;
return allOk;
}
public void SetFlags(OptoTelegramFlags flags)
{
this.Flags = flags;
}
public string ToString(double scalingFactor, OptoTelegramRaw previous)
{
if (Flags == OptoTelegramFlags.SyncError)
{
return "Sychronization error";
}
else if (Flags == OptoTelegramFlags.InvalidTelegram)
{
return "Invalid telegram";
}
else /// if (flags == OptoTelegramFlags.OK / OptoTelegramFlags.OK_TestStart / OptoTelegramFlags.OK_TestEnd)
{
return string.Format(
"{0}:{1}:{2}.{3}\t{4} :\t{5}\t{6}\t{7}\t{8}\t{9}\t{10}\t{11}\t{12}\t{13}\t{14}\t{15}\t{16}\t{17}\t{18}\t{19}\t{20}\t{21}\t{22}",
DateTime.Hour.ToString("D2"),
DateTime.Minute.ToString("D2"),
DateTime.Second.ToString("D2"),
DateTime.Millisecond.ToString("D4"),
Counter,
(EmfRaw & 0x00FFFFFF).ToString("X6"),
MagneticFieldRaw.ToString("X4"),
FlowRaw.ToString("X4"),
((int)VolumeRaw).ToString("X6"),
Impedance.ToString("X4"),
((int)Timestamp).ToString("X8"),
CheckSum.ToString("X2"),
EMF().ToString("F4", culture),
MagneticField().ToString("F0", culture),
Flow(scalingFactor).ToString("F2", culture),
Volume(scalingFactor).ToString("F4", culture),
FlipTime().ToString("F0", culture),
TimestampDec().ToString("F4", culture),
(RefFlow * 1000).ToString("F2", culture),
VolumeDelta(scalingFactor, previous).ToString("F4", culture),
TimeDelta().ToString("F3", culture),
scalingFactor.ToString("F1", culture),
Label());
}
}
public void Copy(OptoTelegramRaw optoTelegramRaw)
{
this.Flags = optoTelegramRaw.Flags;
this.DateTime = optoTelegramRaw.DateTime;
this.RefFlow = optoTelegramRaw.RefFlow;
this.Counter = optoTelegramRaw.Counter;
this.EmfRaw = optoTelegramRaw.EmfRaw;
this.MagneticFieldRaw = optoTelegramRaw.MagneticFieldRaw;
this.FlowRaw = optoTelegramRaw.FlowRaw;
this.VolumeRaw = optoTelegramRaw.VolumeRaw;
this.VolumeRawExt = optoTelegramRaw.VolumeRawExt;
this.Impedance = optoTelegramRaw.Impedance;
this.Timestamp = optoTelegramRaw.Timestamp;
this.TimestampExt = optoTelegramRaw.TimestampExt;
this.CheckSum = optoTelegramRaw.CheckSum;
this.iChannel = optoTelegramRaw.iChannel;
}
public string rawDataToString()
{
if (data != null)
{
return string.Format("Channel: {0}, VolumeCm:{1}, OverflowVolumeCm:{2}, TimeS: {3}, OverflowTimeS:{4} ",
data.Channel, data.VolumeCm, data.OverflowVolumeCm, data.TimeS, data.OverflowTimeS);
}
return string.Empty;
}
}
}
@@ -0,0 +1,28 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
{
public class ChannelAverages
{
public double?[] VolumeStart { get; } = new double?[3];
public double?[] VolumeEnd { get; } = new double?[3];
public double?[] TimeStart { get; } = new double?[3];
public double?[] TimeEnd { get; } = new double?[3];
public static double Average(double?[] values)
{
double sum = 0;
int count = 0;
foreach (var v in values)
{
if (v.HasValue)
{
sum += v.Value;
count++;
}
}
return count > 0 ? sum / count : 0;
}
}
}
@@ -0,0 +1,227 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis
{
/// <summary>
/// Class for CRC16 CCITT calculation
/// </summary>
public static class Crc16Ccitt
{
/// <summary>
/// Initial CRC value
/// </summary>
private const UInt16 CrcSeedFfff = 0xFFFF;
/// <summary>
/// Initial CRC value
/// </summary>
private const UInt16 CrcSeed3791 = 0x3791;
/// <summary>
/// Generator polynomial MagFlux - Modbus RTU
/// </summary>
private const UInt16 CrcGpA001 = 0xA001;
/// <summary>
/// Generator polynomial GENESIS
/// </summary>
private const UInt16 CrcGp1021 = 0x1021;
/// <summary>
/// Generator polynomial RFID
/// </summary>
private const UInt16 CrcGp0408 = 0x0408;
/// <summary>
/// Generator polynomial for reversed IrDA
/// </summary>
private const UInt16 CrcGp8408 = 0x8408;
/// <summary>
/// Calculates the CRC (LSB first, reversed, CRC CCITT 0x8408) from a data array of bytes.
/// </summary>
/// <param name="data">data to be processed</param>
/// <returns>Calculated CRC</returns>
public static UInt16 CalculateReversedLsb8408(Byte[] data)
{
var crc = CrcSeedFfff;
foreach (var t in data)
{
//copy data byte to lower word because of LSB will be XORed
var dataWord = (UInt16)(t & 0x00FF);
for (var i = 0; i < 8; i++)
{
//test if CRC lowest bit is XORed set to one
if (0x0001 == ((crc ^ dataWord) & 0x0001))
{
crc >>= 1;
//apply generator polynomial
crc ^= CrcGp8408;
}
else
{
crc >>= 1;
}
dataWord >>= 1;
}
}
return ((UInt16)~crc);
}
/// <summary>
/// Calculates the bitwise inverted CRC (MSB first, CRCCCITT 0x1021)
/// from a data array of bytes.
/// </summary>
/// <param name="data">data to be processed</param>
/// <returns>Calculated CRC</returns>
public static UInt16 CalculateInvertedMsb1021(Byte[] data)
{
return (UInt16)~CalculateMsb1021(data);
}
/// <summary>
/// Calculates the Modbus RTU CRC (LSB first, CRCCCITT 0xA001)
/// from a data array of bytes.
/// </summary>
/// <param name="data">data to be processed</param>
/// <returns>Calculated CRC</returns>
public static UInt16 ModbusRtuLsbA001(Byte[] data)
{
var crc = CrcSeedFfff;
for (var t = 0; t < data.Length; t++)
{
crc ^= data[t]; // XOR byte into least sig. byte of crc
for (var i = 8; i != 0; i--)
{
if ((crc & 0x0001) != 0)
{
crc >>= 1;
crc ^= CrcGpA001;
}
else
{
crc >>= 1;
}
}
}
return crc;
}
/// <summary>
/// Calculates the CRC (MSB first, CRCCCITT 0x1021) from a data array of bytes.
/// <para>18.07.2024 - optional parameters start and length added - for more flexibility by calculating a CRC of a message.</para>
/// <para>18.07.2024 - length checks: if length less or equal 0 or grater than the data length: the data length is taken as length.</para>
/// </summary>
/// <param name="start">optional, default = 0</param>
/// <param name="length">optional, default = 0</param>
/// <param name="data">data to be processed</param>
/// <returns>Calculated CRC</returns>
public static UInt16 CalculateMsb1021(Byte[] data, Int32 start = 0, Int32 length = 0)
{
var dataLength = data.Length;
if (length <= 0 || length > dataLength)
{
length = dataLength;
}
var crc = CrcSeedFfff;
for (var b = start; b < length; b++)
{
var t = data[b];
//copy data byte to higher word because of MSB will be XORed
var dataWord = (UInt16)((t << 8) & 0xFF00);
for (var i = 0; i < 8; i++)
{
//test if CRC highest bit or data input highest bit is XORed set to one
if (0x8000 == ((crc ^ dataWord) & 0x8000)) //shifted in MSB first
{
//shift CRC high bit out
crc <<= 1;
//apply generator polynomial
crc ^= CrcGp1021;
}
else //CRC highest bit and data input highest bit is equal
{
crc <<= 1;
}
dataWord <<= 1; //MSB first shifted out
}
}
//foreach (var t in data)
//{
// //copy data byte to higher word because of MSB will be XORed
// var dataWord = (UInt16)((t << 8) & 0xFF00);
// for (var i = 0; i < 8; i++)
// {
// //test if CRC highest bit or data input highest bit is XORed set to one
// if (0x8000 == ((crc ^ dataWord) & 0x8000)) //shifted in MSB first
// {
// //shift CRC high bit out
// crc <<= 1;
// //apply generator polynomial
// crc ^= CrcGp1021;
// }
// else //CRC highest bit and data input highest bit is equal
// {
// crc <<= 1;
// }
// dataWord <<= 1; //MSB first shifted out
// }
// }
return crc;
}
public static Byte[] CalculateCRC1021LSBFirst(Byte[] bytes, Int32 start, Int32 length)
{
var crc = CalculateMsb1021(bytes, start, length);
return BitConverter.GetBytes(crc);
}
/// <summary>
/// Calculates the CRC (LSB first, CRCCCITT 0x0408) from a data array of bytes.
/// </summary>
/// <param name="data">data to be processed</param>
/// <returns>Calculated CRC</returns>
public static UInt16 CalculateLsb0408(Byte[] data)
{
var crc = CrcSeed3791;
foreach (var t in data)
{
var dataByte = t;
for (var i = 0; i < 8; i++)
{
//test if CRC lowest bit is set to one
if (1 == (crc & 1))
{
//shift CRC low bit out
crc >>= 1;
//test lowest bit of data word
if (1 == (dataByte & 1))
crc |= 0x8000;
//invert CRC MSB
crc ^= 0x8000;
}
else //CRC lowest bit not set
{
//shift CRC low bit out
crc >>= 1;
//test lowest bit of data word
if (1 == (dataByte & 1))
crc |= 0x8000;
}
if (0x8000 == (crc & 0x8000))
//apply generator polynomial
crc ^= CrcGp0408;
dataByte >>= 1; //LSB first shifted out
}
}
return crc;
}
}
}
@@ -0,0 +1,33 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments
{
/// <inheritdoc />
/// <summary>
/// abstract for set structure for BaseDataEventArgs
/// </summary>
public abstract class BaseDataEventArgs : EventArgs
{
/// <summary>
/// 'Base' get event record form real child.
/// </summary>
/// <returns></returns>
public Object GetData()
{
return GetEventData();
}
/// <summary>
/// get real Event record
/// </summary>
/// <returns></returns>
public abstract Object GetEventData();
/// <summary>
/// Holds the record before decoding, for logging
/// </summary>
public String RawData;
}
}
@@ -0,0 +1,20 @@
using System;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments
{
/// <inheritdoc />
public class BendDetectDataEventArgs : BaseDataEventArgs
{
/// <summary>
/// new record from Stream
/// </summary>
public BendDetectionRecord NewData;
/// <inheritdoc />
public override Object GetEventData()
{
return NewData;
}
}
}
@@ -0,0 +1,20 @@
using System;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments
{
/// <inheritdoc />
public class CalibDataEventArgs : BaseDataEventArgs
{
/// <summary>
/// Stream record for calibration on channel
/// </summary>
public CalibrationRecord CalibChl;
/// <inheritdoc />
public override Object GetEventData()
{
return CalibChl;
}
}
}
@@ -0,0 +1,20 @@
using System;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments
{
/// <inheritdoc />
public class FlowDataEventArgs : BaseDataEventArgs
{
/// <summary>
/// new record from Stream
/// </summary>
public FlowTestRecord NewData;
/// <inheritdoc />
public override Object GetEventData()
{
return NewData;
}
}
}
@@ -0,0 +1,31 @@
using System;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments
{
/// <inheritdoc />
public class RegisterUpdatedEventArgs : EventArgs
{
/// <inheritdoc />
/// <summary>
/// Ctor with base record
/// </summary>
/// <param name="register">base record <see cref="F:Register" /></param>
/// <param name="value">base record <see cref="F:Value" /></param>
public RegisterUpdatedEventArgs(RegisterDefinition register, Byte[] value)
{
Register = register;
Value = value;
}
/// <summary>
/// Register witch has updated
/// </summary>
public RegisterDefinition Register;
/// <summary>
/// New Value in Register
/// </summary>
public Byte[] Value;
}
}
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments
{
/// <inheritdoc />
/// <summary>
/// EventArgs for Request Responses
/// </summary>
public class RequestResponseDataEventArgs : BaseDataEventArgs
{
/// <summary>
/// Request response from meter
/// </summary>
public List<Byte> RequestResponseData;
/// <inheritdoc />
public override Object GetEventData()
{
return RequestResponseData;
}
}
}
@@ -0,0 +1,114 @@
using System;
using System.Text;
using Xylem.Common.Metrology.Measurements;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords
{
/// <inheritdoc />
/// <summary>
/// Streaming record for protocol M (information about bend detection and correction)
/// </summary>
public class BendDetectionRecord : MeasurementRecord
{
/// <summary>
/// The status of the U0 detection for this measurement
/// </summary>
public enum StatusBendU0Enum
{
/// <summary>
/// Status okay
/// </summary>
OKAY = 0,
/// <summary>
/// Error code as defined by field name
/// </summary>
ERROR_GENESISFLOW_INSTALLATION_HIGH_TIME_DIFF = 0x0F41,
/// <summary>
/// Error code as defined by field name
/// </summary>
ERROR_GENESISFLOW_INSTALLATION_LOW_FLOW_IGNORE = 0x0F42,
/// <summary>
/// Error code as defined by field name
/// </summary>
ERROR_GENESISFLOW_INSTALLATION_BAD_CHANNELS = 0x0F43,
/// <summary>
/// Error code as defined by field name
/// </summary>
ERROR_GENESISFLOW_INSTALLATION_FIXED = 0x0F44
}
/// <summary>
/// An enum describing the installation type detected
/// </summary>
public enum InstallationTypeEnum
{
/// <summary>
/// Installation type code as defined by field name
/// </summary>
INSTALLATION_U0_180_360 = 0,
/// <summary>
/// Installation type code as defined by field name
/// </summary>
INSTALLATION_U0_90_270 = 1,
/// <summary>
/// Installation type code as defined by field name
/// </summary>
INSTALLATION_UNDISTURBED = 2
}
/// <summary>
/// Status of U0 Bend detection
/// </summary>
public StatusBendU0Enum StatusBendU0;
/// <summary>
/// Installation type code
/// </summary>
public InstallationTypeEnum InstallationType;
/// <summary>
/// The proportion of the installation correction to apply based on the detected
/// installation. 100% == 0x8000
/// </summary>
public Double CorrectionFactor_percent;
/// <summary>
/// The default proportion of the installation correction to apply based on the
/// detected installation. 100% == 0x8000
/// </summary>
private const UInt32 DefaultCorrectionFactor = 0x8000;
/// <summary>
/// Scale for correction factor to apply to convert it to percentage value 100% == 0x8000
/// </summary>
public const Double CorrectionFactorScale = 100.0 / DefaultCorrectionFactor;
/// <summary>
/// The volume in internal units before correction applied
/// </summary>
public Double PreCorrectionVolumeRaw;
/// <summary>
/// The volume in internal units after correction applied
/// </summary>
public Double PostCorrectionVolumeRaw;
/// <summary>
/// Get result as string
/// </summary>
/// <returns></returns>
public override String ToString()
{
var sb = new StringBuilder();
sb.Append($"Status={StatusBendU0}").Append(",")
.Append($"Installation={InstallationType}").Append(",")
.Append($"Factor={CorrectionFactor_percent}").Append(",")
.Append($"PreVolume={PreCorrectionVolumeRaw}").Append(",")
.Append($"PostVolume={PostCorrectionVolumeRaw}");
return sb.ToString();
}
}
}
@@ -0,0 +1,152 @@
using System;
using System.Text;
using Xylem.Common.Metrology.Measurements;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords
{
/// <inheritdoc />
/// <summary>
/// hold streaming record for protocol H (contains calibration record)
/// </summary>
public class CalibrationRecord : MeasurementRecord
{
/// <summary>
/// record validation
/// </summary>
public UInt16 Validation;
/// <summary>
/// total time of flight in seconds
/// </summary>
public Double TotalTimeOfFlightS;
/// <summary>
/// delta time of flight in seconds
/// </summary>
public Double DeltaTimeOfFlightS;
/// <summary>
/// total time of flight in cordonel units
/// </summary>
public Int32 RawTotalTimeOfFlight;
/// <summary>
/// delta time of flight in cordonel units
/// </summary>
public Int32 RawDeltaTimeOfFlight;
/// <summary>
/// volume scale (default: 1024) means
/// 1024digits = 1ml
/// </summary>
public Double VolumeScaleRawPerMl;
/// <summary>
/// volume factor to convert raw to m³
/// uses 1E-6 (ml to m³) / VolumeScaleRawPerMl
/// </summary>
public Double VolumeFactorRawToQm;
/// <summary>
/// raw volume between two samples
/// </summary>
public Double DeltaVolumeRaw;
/// <summary>
/// calculated out of dRawVolume * VolumeFactorRawToQm .
/// in cubic meters
/// </summary>
public Double DeltaVolumeQm;
/// <summary>
/// accumulated raw volume
/// </summary>
public Double AccuVolumeRaw;
/// <summary>
/// sample interval between two samples in seconds
/// </summary>
public Double SampleIntervalS;
/// <summary>
/// high threshold amplitude in V
/// </summary>
public Double AmplitudeUpV;
/// <summary>
/// low threshold amplitude in V
/// </summary>
public Double AmplitudeDownV;
/// <summary>
/// high ratio for pulse width
/// </summary>
public Double PulseWidthRatioUp;
/// <summary>
/// low ratio for pulse width
/// </summary>
public Double PulseWidthRatioDown;
/// <summary>
/// raw temperature
/// </summary>
public Double TemperatureRaw;
/// <summary>
/// temperature scale
/// </summary>
public Double TemperaturePowFactor;
/// <summary>
/// calculated temperature in degree C
/// </summary>
public Double TemperatureDegC;
/// <summary>
/// Get result as string
/// </summary>
/// <returns></returns>
public override String ToString()
{
var sb = new StringBuilder();
sb.Append($"Channel={Channel}").Append(",")
.Append($"Validation={Validation}").Append(",")
.Append($"TotalTimeOfFlightS={TotalTimeOfFlightS}").Append(",")
.Append($"DeltaTimeOfFlightS={DeltaTimeOfFlightS}").Append(",")
.Append($"DeltaVolumeRaw={DeltaVolumeRaw}").Append(",")
.Append($"VolumeScaleRawPerMl={VolumeScaleRawPerMl}").Append(",")
.Append($"VolumeFactorRawToQm={VolumeFactorRawToQm}").Append(",")
.Append($"DeltaVolumeRaw={DeltaVolumeRaw}").Append(",")
.Append($"DeltaVolumeQm={DeltaVolumeQm}").Append(",")
.Append($"AccuVolumeRaw={AccuVolumeRaw}").Append(",")
.Append($"VolumeCm={VolumeCm}").Append(",")
.Append($"AccuDutOverflowVolumeCm={OverflowVolumeCm}").Append(",")
.Append($"SampleIntervalS={SampleIntervalS}").Append(",")
.Append($"AmplitudeUpV={AmplitudeUpV}").Append(",")
.Append($"AmplitudeDownV={AmplitudeDownV}").Append(",")
.Append($"PulseWidthRatioUp={PulseWidthRatioUp}").Append(",")
.Append($"PulseWidthRatioDown={PulseWidthRatioDown}").Append(",")
.Append($"TemperatureRaw={TemperatureRaw}").Append(",")
.Append($"TemperaturePowFactor={TemperaturePowFactor}").Append(",")
.Append($"TemperatureDegC={TemperatureDegC}").Append(",")
.Append($"TimeS={TimeS}").Append(",")
.Append($"OverflowTimeS={OverflowTimeS}").Append(",")
.Append($"CRC={Crc}").Append(",")
.Append($"IsValid={IsValid}").Append(",")
.Append($"ReceivedTimeUtc={ReceivedTime}").Append(",")
.Append($"DecodedTimeUtc={DecodedTime}").Append(",")
.Append($"SyncMarkRecord={SyncMarkRecord}");
return sb.ToString();
}
}
}
@@ -0,0 +1,32 @@
using System;
using System.Text;
using Xylem.Common.Metrology.Measurements;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords
{
/// <inheritdoc />
/// <summary>
/// struct to hold Led record for protocol F (contains measurement record)
/// </summary>
public class FlowTestRecord : MeasurementRecord
{
/// <summary>
/// Get result as string
/// </summary>
/// <returns></returns>
public override String ToString()
{
var sb = new StringBuilder();
sb = sb.Append($"VolumeCm={VolumeCm}").Append(",")
.Append($"OverflowVolumeCm={OverflowVolumeCm}").Append(",")
.Append($"TimeS={TimeS}").Append(",")
.Append($"OverflowTimeS={OverflowTimeS}").Append(",")
.Append($"CRC={Crc}").Append(",")
.Append($"IsValid={IsValid}").Append(",")
.Append($"ReceivedTimeUtc={ReceivedTime}").Append(",")
.Append($"DecodedTimeUtc={DecodedTime}").Append(",")
.Append($"SyncMarkRecord={SyncMarkRecord}");
return sb.ToString();
}
}
}
@@ -0,0 +1,49 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages
{
/// <summary>
/// All parameters needed for radio setup
/// </summary>
public class RadioConfigurationParams
{
/// <summary>
/// Pcb identification
/// </summary>
public String PcbId;
/// <summary>
/// Serial number
/// </summary>
public UInt32 SerialNumber;
/// <summary>
/// Radio address
/// </summary>
public UInt32 RadioAddress;
/// <summary>
/// Power level
/// </summary>
public UInt32 PowerLevel;
/// <summary>
/// Power level option
/// </summary>
public UInt32 PowerLevelOption;
/// <summary>
/// New code for impedance
/// </summary>
public UInt32 ImpedanceCodeNew;
/// <summary>
/// Impedance code option
/// </summary>
public UInt32 ImpedanceCodeOption;
/// <summary>
/// Encryption key
/// </summary>
public Byte[] EncryptionKey;
/// <summary>
/// Frequency offset
/// </summary>
public Int16? FrqOffset { get; set; }
}
}
@@ -0,0 +1,29 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages
{
/// <summary>
///
/// </summary>
public class RegisterToDb
{
/// <summary>
/// Register address and value to DB
/// </summary>
/// <param name="adresse"></param>
/// <param name="value"></param>
public RegisterToDb(Byte[] adresse, Byte[] value)
{
Adresse = adresse;
Value = value;
}
/// <summary>
/// Address
/// </summary>
public Byte[] Adresse;
/// <summary>
/// Value
/// </summary>
public Byte[] Value;
}
}
@@ -0,0 +1,27 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages
{
/// <summary>
/// Temperature Time Of Flight calculation
/// </summary>
public struct TempTofCalc
{
private DateTime _refDate;
private Double _refTemp;
private Double _tof;
/// <summary>
/// Reference data
/// </summary>
public DateTime RefDate { get => _refDate; set => _refDate = value; }
/// <summary>
/// Reference temperature
/// </summary>
public Double RefTemp { get => _refTemp; set => _refTemp = value; }
/// <summary>
/// Time Of Flight
/// </summary>
public Double Tof { get => _tof; set => _tof = value; }
}
}
@@ -0,0 +1,88 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig
{
/// <summary>
/// Setup of communication timeouts and retries for request and streaming protocol,
/// this timeout starts the retry.
///
/// The individual timings of the hardware will be covert by the
/// <see cref="TransmitPortSettings.ResponseTimeoutMs"/>
/// injected by the different transmit protocols:
/// <see cref="IrdaTransmitProtocol"/>
/// <see cref="LedTransmitProtocol"/>
/// <see cref="RfidTransmitProtocol"/>
/// <see cref="UartTransmitProtocol"/>
/// </summary>
public static class CommunicationConfig
{
private static Int32 _requestRetries = DefaultRequestRetries;
/// <summary>
/// Maximum retries for one command at request protocol
/// at missing response.
/// </summary>
public static Int32 RequestRetries
{
get => _requestRetries;
set => _requestRetries = value > MaxRequestRetries ? MaxRequestRetries : value;
}
/// <summary>
/// Acceptable error code as valid answer to skip retries,
/// this might be 0x0004 for not installed application.
/// </summary>
public const UInt16 SkipRetryErrorCode = 0x0004;
/// <summary>
/// Default retries for one command at request protocol
/// at missing response.
/// </summary>
public const Int32 DefaultRequestRetries = 2;
/// <summary>
/// Limit retries to this value.
/// </summary>
public const Int32 MaxRequestRetries = 6;
/// <summary>
/// Send delay between records in milliseconds before
/// new record is going to be sent or between retries,
/// this time is independent of the timeout, it is
/// used to delay the next send record after successful
/// response from meter.
/// </summary>
public const Int32 InterRecordSendDelayMs = 5;
//public const Int32 InterRecordSendDelayMs = 50;
/// <summary>
/// Timeout before retry will be initiated in milliseconds
/// for the response of the request protocol. This is an
/// offset value, the transmission specific timing will be
/// added from the transmit protocol timeout. This timeout
/// will be multiplied with the (retry + 1)!
/// </summary>
public const Int32 ResponseTimeoutMs = 250;
// public const Int32 ResponseTimeoutMs = 100;
/// <summary>
/// Timeout before retry will be initiated if the meter is
/// not ready indicated by a wakeup-message or decoding error.
/// </summary>
public const Int32 BusyTimeoutMs = 500;
// public const Int32 BusyTimeoutMs = 1000;
/// <summary>
/// Time to avoid automatic lock off of genesis device due to
/// missing communication with request protocol in seconds
/// </summary>
public const Int32 KeepSessionTimeS = 60 * 3;
/// <summary>
/// Time to update intermediate record for overflow detection
/// during a measurement and update of short-term measurement
/// in seconds
/// </summary>
public const Int32 MeasurementUpdateTimeS = 10;
}
}
@@ -0,0 +1,24 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig.Const
{
public static class MeterConfig
{
/// <summary>
/// Calibration Factor on init from meter
/// Is also the factor to convert CalibrationFactors to readable number,
/// this is the default if the ProccessConfig.json file cannot be read.
/// </summary>
public const UInt16 CalibrationDefault = 15625;
/// <summary>
/// Channels for calibration
/// </summary>
public const Int32 CalibChannels = 3;
/// <summary>
/// Maximum calibration value in percent
/// </summary>
public const Double DefaultMaxCalibFactorTolerancePercent = 100.0;
}
}
@@ -0,0 +1,86 @@
using System;
using System.IO;
using Newtonsoft.Json;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig
{
/// <summary>
/// Configuration of meter
/// </summary>
public class MeterConfig
{
/// <summary>
/// ctor
/// </summary>
public MeterConfig()
{
UseRegisterWatchService = false;
UseErrorLogger = false;
}
/// <inheritdoc />
public MeterConfig(String file)
{
var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(Xylem.Common.Hardware.WaterMeter.Genesis), file);
if (!File.Exists(configFile))
{
configFile = Path.Combine(file);
if (!File.Exists(configFile))
{
UseRegisterWatchService = false;
UseErrorLogger = false;
return;
}
}
using (var tr = new StreamReader(configFile))
{
var meterConfig = JsonConvert.DeserializeObject<MeterConfig>(tr.ReadToEnd());
UseRegisterWatchService = meterConfig.UseRegisterWatchService;
RegisterWatchServiceUrl = meterConfig.RegisterWatchServiceUrl;
UseMinMaxCheck = meterConfig.UseMinMaxCheck;
UseErrorLogger = meterConfig.UseErrorLogger;
ErrorLoggerServiceUrl = meterConfig.ErrorLoggerServiceUrl;
UseCalibrationLogger = meterConfig.UseCalibrationLogger;
CalibrationLoggerServiceUrl = meterConfig.CalibrationLoggerServiceUrl;
}
}
/// <summary>
///
/// </summary>
public Boolean UseRegisterWatchService { get; set; }
/// <summary>
///
/// </summary>
public String RegisterWatchServiceUrl { get; set; }
/// <summary>
///
/// </summary>
public Boolean UseMinMaxCheck { get; set; }
/// <summary>
///
/// </summary>
public Boolean UseErrorLogger { get; set; }
/// <summary>
///
/// </summary>
public String ErrorLoggerServiceUrl { get; set; }
/// <summary>
///
/// </summary>
public Boolean UseCalibrationLogger { get; set; }
/// <summary>
///
/// </summary>
public String CalibrationLoggerServiceUrl { get; set; }
}
}
@@ -0,0 +1,114 @@
using System;
using System.ComponentModel;
using System.IO;
using Newtonsoft.Json;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig
{
/// <summary>
/// Configuration of meter
/// </summary>
public class ProccessConfig
{
/// <summary>
/// ctor
/// </summary>
public ProccessConfig()
{
UseRegisterWatchService = false;
UseErrorLogger = false;
AutoUpdateFiles = false;
ProductionMode = true;
}
/// <inheritdoc />
public ProccessConfig(String file)
{
var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(Xylem.Common.Hardware.WaterMeter.Genesis), file);
if (!File.Exists(configFile))
{
configFile = Path.Combine(file);
if (!File.Exists(configFile))
{
UseRegisterWatchService = false;
UseErrorLogger = false;
return;
}
}
using (var tr = new StreamReader(configFile))
{
var meterConfig = JsonConvert.DeserializeObject<ProccessConfig>(tr.ReadToEnd());
UseRegisterWatchService = meterConfig.UseRegisterWatchService;
RegisterWatchServiceUrl = meterConfig.RegisterWatchServiceUrl;
UseMinMaxCheck = meterConfig.UseMinMaxCheck;
UseErrorLogger = meterConfig.UseErrorLogger;
ErrorLoggerServiceUrl = meterConfig.ErrorLoggerServiceUrl;
UseCalibrationLogger = meterConfig.UseCalibrationLogger;
CalibrationLoggerServiceUrl = meterConfig.CalibrationLoggerServiceUrl;
AutoUpdateFiles = meterConfig.AutoUpdateFiles;
ProductionMode = meterConfig.ProductionMode;
}
}
public void Update(String file)
{
var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(Xylem.Common.Hardware.WaterMeter.Genesis), file);
if (!File.Exists(configFile))
{
configFile = Path.Combine(file);
}
File.WriteAllText(file, JsonConvert.SerializeObject(this));
}
/// <summary>
///
/// </summary>
public Boolean UseRegisterWatchService { get; set; }
/// <summary>
///
/// </summary>
public String RegisterWatchServiceUrl { get; set; }
/// <summary>
///
/// </summary>
public Boolean UseMinMaxCheck { get; set; }
/// <summary>
///
/// </summary>
public Boolean UseErrorLogger { get; set; }
/// <summary>
///
/// </summary>
public String ErrorLoggerServiceUrl { get; set; }
/// <summary>
///
/// </summary>
public Boolean UseCalibrationLogger { get; set; }
/// <summary>
///
/// </summary>
public String CalibrationLoggerServiceUrl { get; set; }
/// <summary>
///
/// </summary>
public bool AutoUpdateFiles { get; set; }
[DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public bool ProductionMode { get; set; } = true;
}
}
@@ -0,0 +1,111 @@
using System;
using System.ComponentModel;
using System.IO;
using Newtonsoft.Json;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig
{
/// <summary>
/// Configuration of meter
/// </summary>
public class ProcessConfig
{
/// <summary>
/// Load configuration file from AppRoaming or working directory
/// </summary>
public Boolean ReadProcessConfig()
{
// Check for AppRoaming
var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
nameof(Xylem.Common.Hardware.WaterMeter.Genesis), ProgramConfig.MeterConfigFileName);
if (!File.Exists(configFile))
{
// Take actual working directory
configFile = Path.Combine(ProgramConfig.MeterConfigFileName);
if (!File.Exists(configFile))
{
UseRegisterWatchService = false;
UseErrorLogger = false;
AutoUpdateFiles = false;
ProductionMode = true;
return false;
}
}
using (var tr = new StreamReader(configFile))
{
var meterConfig = JsonConvert.DeserializeObject<ProcessConfig>(tr.ReadToEnd());
UseRegisterWatchService = meterConfig.UseRegisterWatchService;
RegisterWatchServiceUrl = meterConfig.RegisterWatchServiceUrl;
UseMinMaxCheck = meterConfig.UseMinMaxCheck;
UseErrorLogger = meterConfig.UseErrorLogger;
ErrorLoggerServiceUrl = meterConfig.ErrorLoggerServiceUrl;
UseCalibrationLogger = meterConfig.UseCalibrationLogger;
CalibrationLoggerServiceUrl = meterConfig.CalibrationLoggerServiceUrl;
AutoUpdateFiles = meterConfig.AutoUpdateFiles;
ProductionMode = meterConfig.ProductionMode;
}
return true;
}
/// <summary>
/// Write file to AppRoaming and backup it to actual working directory
/// </summary>
public void Update()
{
var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
nameof(Xylem.Common.Hardware.WaterMeter.Genesis), ProgramConfig.MeterConfigFileName);
File.WriteAllText(configFile, JsonConvert.SerializeObject(this));
File.WriteAllText(ProgramConfig.MeterConfigFileName, JsonConvert.SerializeObject(this));
}
/// <summary>
///
/// </summary>
public Boolean UseRegisterWatchService { get; set; }
/// <summary>
///
/// </summary>
public String RegisterWatchServiceUrl { get; set; }
/// <summary>
///
/// </summary>
public Boolean UseMinMaxCheck { get; set; }
/// <summary>
///
/// </summary>
public Boolean UseErrorLogger { get; set; }
/// <summary>
///
/// </summary>
public String ErrorLoggerServiceUrl { get; set; }
/// <summary>
///
/// </summary>
public Boolean UseCalibrationLogger { get; set; }
/// <summary>
///
/// </summary>
public String CalibrationLoggerServiceUrl { get; set; }
/// <summary>
///
/// </summary>
public Boolean AutoUpdateFiles { get; set; }
[DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public Boolean ProductionMode { get; set; }
}
}
@@ -0,0 +1,96 @@
using System;
using System.IO;
using Newtonsoft.Json;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig
{
/// <summary>
/// Configuration of SIRT
/// </summary>
public class SirtConfig
{
/// <summary>
/// Load SIRT configuration file from AppRoaming or working directory
/// </summary>
public Boolean ReadSirtConfig()
{
// Check for AppRoaming
var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
nameof(Xylem.Common.Hardware.WaterMeter.Genesis), ProgramConfig.SirtConfigFileName);
if (!File.Exists(configFile))
{
// Take actual working directory
configFile = Path.Combine(ProgramConfig.SirtConfigFileName);
if (!File.Exists(configFile))
{
SirtComport433MHz = null;
SirtComport868MHz = null;
ServiceSirtComport868MHz = null;
ServiceSirtComport433MHz = null;
SirtBoxNo = null;
StationId = null;
return false;
}
}
using (var tr = new StreamReader(configFile))
{
var sirtConfig = JsonConvert.DeserializeObject<SirtConfig>(tr.ReadToEnd());
SirtComport433MHz = sirtConfig.SirtComport433MHz;
SirtComport868MHz = sirtConfig.SirtComport868MHz;
ServiceSirtComport433MHz = sirtConfig.ServiceSirtComport433MHz; ;
ServiceSirtComport868MHz = sirtConfig.ServiceSirtComport868MHz; ;
SirtBoxNo = sirtConfig.SirtBoxNo;
StationId = sirtConfig.StationId;
}
return true;
}
/// <summary>
/// Write file to AppRoaming and backup it to actual working directory
/// </summary>
public void Update()
{
var configFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
nameof(Xylem.Common.Hardware.WaterMeter.Genesis), ProgramConfig.SirtConfigFileName);
File.WriteAllText(configFile, JsonConvert.SerializeObject(this));
File.WriteAllText(ProgramConfig.SirtConfigFileName, JsonConvert.SerializeObject(this));
}
/// <summary>
/// Comport for SIRT interface with 433MHz radio frequency
/// </summary>
public String SirtComport433MHz { get; set; }
/// <summary>
/// Comport for SIRT interface with 868MHz radio frequency
/// </summary>
public String SirtComport868MHz { get; set; }
/// <summary>
/// Box number for calibration settings
/// </summary>
public Int32? SirtBoxNo { get; set; }
/// <summary>
/// Station ID where the SIRT is used
/// </summary>
public Int32? StationId { get; set; }
/// <summary>
/// Service Comport for SIRT interface with 433MHz radio frequency
/// </summary>
public String ServiceSirtComport433MHz { get; set; }
/// <summary>
/// Service Comport for SIRT interface with 868MHz radio frequency
/// </summary>
public String ServiceSirtComport868MHz { get; set; }
}
}
@@ -0,0 +1,46 @@
using System;
using Xylem.Common.CommonCore.Consts;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments
{
/// <inheritdoc cref="EventArgs" />
public abstract class BasePortDataEventArgs : EventArgs, IPortDataEventArgs
{
/// <summary>
/// Marker for incoming record at time of the PC
/// </summary>
private DateTimeOffset _receivedTime = default(DateTimeOffset);
/// <inheritdoc />
public abstract Object GetData();
/// <inheritdoc />
public abstract void SetData(Object data);
/// <inheritdoc />
public void SetReceivedTime(DateTimeOffset receivedTime)
{
_receivedTime = receivedTime;
}
/// <inheritdoc />
public DateTimeOffset GetReceivedTime()
{
return _receivedTime;
}
private SyncMarkRecord _syncMarkRecord;
/// <inheritdoc />
public void SetSyncMarkRecord(SyncMarkRecord syncMarkRecord)
{
_syncMarkRecord = syncMarkRecord;
}
/// <inheritdoc />
public SyncMarkRecord GetSyncMarkRecord()
{
return _syncMarkRecord;
}
}
}
@@ -0,0 +1,48 @@
using System;
using Xylem.Common.CommonCore.Consts;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments
{
/// <summary>
/// Interface for port data event arguments
/// </summary>
public interface IPortDataEventArgs
{
/// <summary>
/// Read data
/// </summary>
/// <returns></returns>
Object GetData();
/// <summary>
/// Write data
/// </summary>
/// <param name="data"></param>
void SetData(Object data);
/// <summary>
/// Reading received time
/// </summary>
/// <returns></returns>
DateTimeOffset GetReceivedTime();
/// <summary>
/// Writing received time
/// </summary>
/// <param name="receivedTime"></param>
void SetReceivedTime(DateTimeOffset receivedTime);
/// <summary>
/// Setting the data marker
/// </summary>
/// <param name="syncMarkRecord"></param>
void SetSyncMarkRecord(SyncMarkRecord syncMarkRecord);
/// <summary>
/// Getting the data marker
/// </summary>
/// <returns></returns>
SyncMarkRecord GetSyncMarkRecord();
}
}
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using Xylem.Common.CommonCore.Consts;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments
{
/// <inheritdoc />
public class ListBytePortDataEventArgs : BasePortDataEventArgs
{
private List<Byte> _data;
/// <inheritdoc />
public ListBytePortDataEventArgs(List<Byte> data, DateTimeOffset readTimeStampPc = default(DateTimeOffset),
SyncMarkRecord syncMarkRecord = SyncMarkRecord.SkipDecoding)
{
_data = data;
SetSyncMarkRecord(syncMarkRecord);
SetReceivedTime(readTimeStampPc);
}
/// <inheritdoc />
public override Object GetData()
{
return _data;
}
/// <inheritdoc />
public override void SetData(Object data)
{
_data = (List<Byte>)data;
}
}
}
@@ -0,0 +1,34 @@
using System;
using Xylem.Common.CommonCore.Consts;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments
{
/// <inheritdoc />
public class StringPortDataEventArgs : BasePortDataEventArgs
{
private String _data;
/// <inheritdoc />
public StringPortDataEventArgs(String data, DateTimeOffset readTimeStampPc = default(DateTimeOffset),
SyncMarkRecord syncMarkRecord = SyncMarkRecord.SkipDecoding)
{
_data = data;
SetSyncMarkRecord(syncMarkRecord);
SetReceivedTime(readTimeStampPc);
}
/// <inheritdoc />
public override Object GetData()
{
return _data;
}
/// <inheritdoc />
public override void SetData(Object data)
{
_data = (String)data;
}
}
}
@@ -0,0 +1,63 @@
using System;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments;
using Xylem.Common.CommonCore.Consts;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore
{
/// <summary>
/// Interface for Ports
/// </summary>
public interface IPort
{
/// <summary>
/// event for record received, either bytes or string
/// </summary>
event EventHandler<BasePortDataEventArgs> OnRawRecordReceived;
/// <summary>
/// event for record received, either bytes or string
/// </summary>
event EventHandler<BasePortDataEventArgs> OnRawRecordSendOut;
/// <summary>
/// set specific mark
/// and flush or delete all incoming byte from buffer when SyncMarkRecord is <see cref="SyncMarkRecord.SyncStart"/> or
/// <see cref="SyncMarkRecord.SyncEnd"/>
/// </summary>
/// <param name="syncMarkRecord"></param>
void SynchronizeReceiveBuffer(SyncMarkRecord syncMarkRecord);
/// <summary>
/// if the port needs stuff to open, always open for better logical handling
/// </summary>
void Open();
/// <summary>
/// close and dispose all connections
/// </summary>
void Dispose();
/// <summary>
/// indicates if the Port is open (also on Ports that did not have an open state)
/// </summary>
/// <returns></returns>
Boolean IsOpen();
/// <summary>
/// Write byte[] to the Stream/Port on Child Class
/// wrapped with base class error handling
/// </summary>
/// <param name="data">Array of bytes to write</param>
void PortWrite(Byte[] data);
/// <summary>
/// discard all buffers
/// </summary>
void Clear();
/// <summary>
/// Return the port name
/// </summary>
/// <returns>name of the port as string</returns>
String GetPortName();
}
}
@@ -0,0 +1,63 @@
using System;
using System.IO.Ports;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore
{
/// <summary>
/// Collection of port settings
/// </summary>
public struct PortConfig
{
private String _portName;
/// <summary>
/// Assigns a port name and creates the serial port object
/// Port name e.g. "COM2"
/// </summary>
public String PortName
{
get =>
//if the serial port object does not exists
GetSerialPort() == null ? "NA" : _portName;
set
{
if (value == null) return;
_portName = value;
//create a serial port object
if (GetSerialPort() == null)
{
SetSerialPort(new SerialPort(value));
}
else
{
GetSerialPort().PortName = value;
}
}
}
/// <summary>
/// setup of port type from name (referenced as "Type" in config file)
/// </summary>
public String Type { get; set; }
private SerialPort _serialPort;
/// <summary>
/// using dotNets <see cref="GetSerialPort()"/> for connection
/// </summary>
public SerialPort GetSerialPort()
{
return _serialPort;
}
/// <summary>
/// using dotNets <see cref="GetSerialPort()"/> for connection
/// </summary>
private void SetSerialPort(SerialPort value)
{
_serialPort = value;
}
}
}
@@ -0,0 +1,30 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore
{
/// <summary>
/// Container for port configuration
/// </summary>
public class SlotConfig
{
/// <summary>
/// Slot number
/// </summary>
public Int32 Slot { get; set; }
/// <summary>
/// Request port settings
/// </summary>
public PortConfig Request { get; set; }
/// <summary>
/// Streaming port settings
/// </summary>
public PortConfig Streaming { get; set; }
/// <summary>
/// Streaming port settings
/// </summary>
public SlotType Type { get; set; }
}
}
@@ -0,0 +1,17 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore
{
/// <summary>
/// Definition of type for slot
/// </summary>
public enum SlotType
{
/// <summary>
/// Cordonel used as DUT with two serial ports
/// </summary>
DutMeter,
/// <summary>
/// Cordonel used as temperature meter with one serial port streaming the temperature
/// </summary>
TemperatureMeter,
}
}
@@ -0,0 +1,109 @@
using System;
using System.IO.Ports;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore
{
/// <summary>
/// Store Port settings set most likely from transmit protocol
/// </summary>
public struct TransmitPortSettings
{
/// <summary>
/// The transmission protocol needs to inform the communication port how to set up,
/// this is the data container.
/// </summary>
/// <param name="protSyncByte"></param>
/// <param name="protLengthIndex"></param>
/// <param name="protAddLength"></param>
/// <param name="responseTimeoutMs"></param>
/// <param name="baudRate"></param>
/// <param name="receiveBufferFlushThreshold"></param>
/// <param name="doubleSyncByte"></param>
/// <param name="stringDelimiter"></param>
/// <param name="dataBits"></param>
/// <param name="parity"></param>
/// <remarks date="2026-Jan-05" author="T.Wiedebusch">
/// - String delimiter for ASCII to support others than LF "\n",
/// - Flexible DataBits,
/// - Flexible parity.
/// </remarks>
public TransmitPortSettings(Byte? protSyncByte, UInt16? protLengthIndex, UInt16 protAddLength, Int32 responseTimeoutMs,
UInt32 baudRate, UInt32? receiveBufferFlushThreshold, Boolean doubleSyncByte = false, Char stringDelimiter = '\n',
Int32 dataBits = 8, Parity parity = Parity.None )
{
ProtSyncByte = protSyncByte;
ProtLengthIndex = protLengthIndex;
ProtAddLength = protAddLength;
ResponseTimeoutMs = responseTimeoutMs;
BaudRate = baudRate;
ReceiveBufferFlushThreshold = receiveBufferFlushThreshold;
DoubleSyncByte = doubleSyncByte;
StringDelimiter = stringDelimiter;
DataBits = dataBits;
Parity = parity;
}
/// <summary>
/// String delimiter for ASCII to support others than LF "\n"
/// </summary>
public readonly Char StringDelimiter;
/// <summary>
/// Flexible DataBits to support 7 bits.
/// </summary>
public readonly Int32 DataBits;
/// <summary>
/// Flexible parity
/// </summary>
public readonly Parity Parity;
/// <summary>
/// start of receiving synchronization byte at byte receive routine
/// syncByte == null: use the readLine routine (ASCII) and NOT the BYTE routine,
/// lengthPosition and additionalLength are not used
/// </summary>
public readonly Byte? ProtSyncByte;
/// <summary>
/// position of length information field in received BYTE record
/// lengthIndex == null: take the constant receive length of additionalLength
/// because the record doesn't contain length information
/// </summary>
public readonly UInt16? ProtLengthIndex;
/// <summary>
/// additional record length NOT covert by the record length information field
/// lengthIndex == null: constant length for received record
/// Being used for the BYTE records indicated by a valid syncByte,
/// not being used for ASCII records.
/// </summary>
public readonly UInt16 ProtAddLength;
/// <summary>
/// Response time out in milliseconds
/// </summary>
public readonly Int32 ResponseTimeoutMs;
/// <summary>
/// BaudRate for Port
/// </summary>
public readonly UInt32 BaudRate;
/// <summary>
/// lower threshold for buffer flushing if dataMarker != SkipDecoding
/// receiveBufferFlushThreshold == null: never flush the communication buffer
/// receiveBufferFlushThreshold == 0: flush always the communication buffer
/// receiveBufferFlushThreshold == x: flush communication buffer if it exceeds x Byte
/// waste old records if an up-to-date record is being needed for start/stop synchronization
/// of a measurement. This is the level at which the old data have to be flushed because the
/// data have been dammed up in the communication port input buffer which means they are to
/// old for synchronization purposes.
/// /// </summary>
public readonly UInt32? ReceiveBufferFlushThreshold;
/// <summary>
/// a special implementation may require the doubling of the sync byte to re-synchronize
/// </summary>
public readonly Boolean DoubleSyncByte;
}
}
@@ -0,0 +1,702 @@
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Threading;
using log4net;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments;
using Xylem.Common.CommonCore.Consts;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.SerialPorts
{
/// <summary>
/// All Ports must use BasePort as an base class
/// its support some wrapping event/function handling
/// </summary>
public abstract class BaseSerialPort : IPort, IDisposable
{
private static readonly ILog _byteDataLogger = LogManager.GetLogger(typeof(BaseSerialPort));
internal ILog AsciiDataLogger = LogManager.GetLogger("LedRawData");
/// <inheritdoc />
public virtual event EventHandler<BasePortDataEventArgs> OnRawRecordReceived;
/// <summary>
/// The port is not assigned.
/// </summary>
public const String PortNotAssigned = "NA";
/// <inheritdoc />
public virtual event EventHandler<BasePortDataEventArgs> OnRawRecordSendOut;
//initially do not signal event
private readonly AutoResetEvent _onSyncReceiveThread = new AutoResetEvent(false);
private readonly Thread _receiveThread;
private readonly CancellationTokenSource _receiveToken = new CancellationTokenSource();
/// <summary>
/// Delay between bytes if receiving has started in milliseconds
/// </summary>
private const Int32 InterByteReadDelayMs = 50;
/// <summary>
/// internal SerialPort class
/// </summary>
private readonly SerialPort _serialPort;
/// <summary>
/// Activate raw record recording
/// </summary>
public Boolean RecordStreamingRawData = false;
//public Boolean RecordStreamingRawData
//{
// get => _recordStreamingRawData;
// set => _recordStreamingRawData = value;
//}
/// <summary>
/// date and time of incoming first date interrupt on serial IO to set the
/// PC time-stamp to the record
/// </summary>
private DateTimeOffset _receiveTimeStampPc;
/// <summary>
/// Individual port settings depending on transmit protocol
/// </summary>
protected TransmitPortSettings PortSettingsForTransmitProtocol;
/// <inheritdoc />
public String GetPortName()
{
return _serialPort.PortName;
}
/// <summary>
/// store a identification of a port, this contains the Slot, Port, Protocol and Type
/// e.g. "Slot:1, Port:COM4, Protocol:Request, Type:IrDA -"
/// </summary>
public String Ident;
/// <summary>
/// ctor for SerialComPort with and full constructed <see cref="T:System.IO.Ports.SerialPort" /> properties
/// </summary>
/// <param name="ident">set port as unique</param>
/// <param name="serialPort">Class for communication, all parameters for serial communication needs to be set</param>
/// <param name="setting">Class for store all parameters for serial communication needs to be set came from transmit protocol</param>
protected BaseSerialPort(String ident, SerialPort serialPort, TransmitPortSettings setting)
{
Ident = ident;
//communication port settings
_serialPort = serialPort;
_serialPort.BaudRate = (Int32)setting.BaudRate;
PortSettingsForTransmitProtocol = setting;
//assign thread to loop
_receiveThread = new Thread(ReadingThreadLoop) { Name = $"{Ident} Reading thread" };
}
public void RefreshLogger()
{
}
/// <summary>
/// received bytes in buffer of SerialComPort, calls base function
/// </summary>
/// <returns>number of bytes in Rx buffer</returns>
protected Int32 BytesToRead()
{
try
{
return _serialPort.BytesToRead;
}
catch (Exception ex)
{
_byteDataLogger.Error(ex);
return 0;
}
}
/// <summary>
/// read single byte from Rx buffer of SerialComPort, calls base function
/// </summary>
/// <returns></returns>
protected Int32 ReadByte()
{
try
{
return _serialPort.ReadByte();
}
catch (Exception ex)
{
_byteDataLogger.Error(ex);
return 0;
}
}
/// <inheritdoc />
public void PortWrite(Byte[] record)
{
try
{
SpecificPortWrite(record);
}
catch (Exception ex)
{
throw new SystemException($"{Ident} Communication error while sending data to port", ex);
}
}
/// <inheritdoc />
/// <summary>
/// flush Rx and Tx buffer of SerialComPort, calls base functions
/// </summary>
public void Clear()
{
try
{
if (!_serialPort.IsOpen)
{
return;
}
_serialPort.DiscardInBuffer();
_serialPort.DiscardOutBuffer();
}
catch (Exception ex)
{
_byteDataLogger.Error(ex);
}
}
/// <summary>
/// Dispose serial port
/// </summary>
/// <remarks date="2025-Mai-12..14" author="T.Wiedebusch">
/// - Dispose procedure changed.
/// </remarks>
public void Dispose()
{
try
{
//remove registration for receive delegate
_serialPort.DataReceived -= PhysicalDataReceived;
//Cancel send and receive tokens
_receiveToken.Cancel();
//Run thread again to notice CancellationToken has changed
_onSyncReceiveThread.Set();
// dispose directly called from here to overcome glitches caused by USB to serial interface
_serialPort.Dispose();
}
catch (Exception ex)
{
_byteDataLogger.Error(ex);
}
}
/// <inheritdoc />
public void Open()
{
try
{
//set receive interrupt threshold for immediate execution
_serialPort.ReceivedBytesThreshold = 1;
_serialPort.DataReceived += PhysicalDataReceived;
//start reading thread, will be immediately put to waitSleepJoin in ReadingThreadLoop
//to avoid side effects with RFID communication
ThreadWatcher.Instance.Start(_receiveThread);
if (!_serialPort.IsOpen)
{
_serialPort.Open();
if (!_serialPort.IsOpen)
{
_byteDataLogger.Error($"{Ident} Port cannot be opened");
throw new ApplicationException($"{Ident} Port cannot be opened");
}
_byteDataLogger.Info($"{Ident} Port is opened");
}
else
{
_byteDataLogger.Warn($"{Ident} Port was already opened");
}
}
catch (Exception ex)
{
_byteDataLogger.Error($"{Ident} {ex.Message}");
throw new ApplicationException($"{Ident} {ex.Message}");
}
}
/// <summary>
/// Getting the base port
/// </summary>
protected SerialPort GetBaseComport()
{
return _serialPort;
}
/// <summary>
/// Fill FIFO with received data
/// </summary>
/// <remarks date="2018-Feb-16" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2018-Feb-21" author="T.Wiedebusch">
/// - Time stamp added
/// </remarks>
/// <remarks date="2018-Dec-14" author="T.Wiedebusch">
/// - Timeout moved from <see cref="ReadingThreadLoop"/> to PhysicalDataReceived to avoid
/// <see cref="TimeoutException"/> of serial port while waiting on first incoming
/// data.
/// </remarks>
/// <remarks date="2019-Dec-12" author="T.Wiedebusch">
/// - Inter byte read delay used instead of response delay!!!
/// </remarks>
/// <param name="sender"></param>
/// <param name="e"></param>
public virtual void PhysicalDataReceived(Object sender, SerialDataReceivedEventArgs e)
{
if (!_receiveToken.IsCancellationRequested)
{
//remove event delegate to avoid repeated execution during one record
_serialPort.DataReceived -= PhysicalDataReceived;
//put the actual time stamp to this record being able to assign it correctly
//even if the decoding is delayed. This time stamp will be used to do the first
//synchronization at start and stop of the measurement. Therefore, the serial buffer
//has to be flushed in advance to avoid wrong time stamp to "old" records
_receiveTimeStampPc = DateTimeOffset.UtcNow;
//start timeout for hanging read communication called inter byte delay
_serialPort.ReadTimeout = InterByteReadDelayMs;
// _serialPort.ReadTimeout = PortSettingsForTransmitProtocol.ResponseTimeoutMs;
//wake up the reading threat from JoinWaitSleep
_onSyncReceiveThread.Set();
}
}
/// <summary>
/// <see cref="SyncMarkRecord" />
/// </summary>
private SyncMarkRecord _syncMarkRecord;
/// <inheritdoc />
public void SynchronizeReceiveBuffer(SyncMarkRecord syncMarkRecord)
{
try
{
//not in test bench situation only for temp logging
if (syncMarkRecord == SyncMarkRecord.FlushBuffer)
{
FlushBuffer();
return;
}
//DecodeEveryPackage is ongoing and SkipDecoding is requested = do nothing
if (_syncMarkRecord == SyncMarkRecord.DecodeEveryPackage &&
syncMarkRecord == SyncMarkRecord.SkipDecoding)
{
return;
}
//deny intermediate record when a start or end record is requested
if (syncMarkRecord == SyncMarkRecord.DecodeIntermediate &&
(_syncMarkRecord == SyncMarkRecord.SyncEnd ||
_syncMarkRecord == SyncMarkRecord.SyncStart))
{
return;
}
//being able to detect the first incoming record after a sync is requested, the buffer is going to be flushed
//if the measurement will be started or stopped
if (syncMarkRecord == SyncMarkRecord.SyncEnd ||
syncMarkRecord == SyncMarkRecord.SyncStart ||
syncMarkRecord == SyncMarkRecord.DecodeIntermediate)
{
FlushBuffer();
}
_byteDataLogger.Info($"{Ident} Mark next incoming record as {syncMarkRecord} (was {_syncMarkRecord})");
_syncMarkRecord = syncMarkRecord;
}
catch (Exception ex)
{
_byteDataLogger.Error($"{Ident} {ex.Message}");
}
}
private void FlushBuffer()
{
//check if flushing is allowed
if (null != PortSettingsForTransmitProtocol.ReceiveBufferFlushThreshold)
{
//flush buffer above threshold to get an accurate actual record with next record coming in
if (_serialPort.IsOpen && _serialPort.BytesToRead > PortSettingsForTransmitProtocol.ReceiveBufferFlushThreshold)
{
_byteDataLogger.Warn($"{Ident} Flushed receive buffer({_serialPort.BytesToRead}Byte)");
_serialPort.DiscardInBuffer();
}
}
}
/// <summary>
/// Reading thread loop getting data from serial port
/// </summary>
/// <remarks date="2018-Feb-16" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2018-Mar-08" author="T.Wiedebusch">
/// - Reading will be executed and repeated until buffer is empty or timeout
/// </remarks>
/// <remarks date="2018-Mar-09" author="T.Wiedebusch">
/// - Receive byte protocol more dynamically on position of length information in record
/// and additional length,
/// - decoding of data controlled by dataSyncMarker!= SyncMarkRecord.SkipDecoding to speed up recording
/// </remarks>
/// <remarks date="2018-Oct-23" author="T.Wiedebusch">
/// - Directly invoked onRawRecord Received event,
/// - Doubling of sync byte implemented
/// </remarks>
/// <remarks date="2018-Dec-14" author="T.Wiedebusch">
/// - Timeout moved from ReadingThreadLoop to <see cref="PhysicalDataReceived"/> to avoid
/// <see cref="TimeoutException"/> of serial port while waiting on first incoming
/// data and flush receive buffer at timeout to force task to enter JoinWaitSleep state.
/// </remarks>
/// <remarks date="2019-Apr-11" author="T.Wiedebusch">
/// - Hide data in logging for e.g. passwords
/// </remarks>
/// <remarks date="2019-Dec-03" author="T.Wiedebusch">
/// - Raw record recording for missing sync-byte issues.
/// </remarks>
/// <remarks date="2019-Dec-04" author="T.Wiedebusch">
/// - Try to recover raw record on missing SYNC byte by adding the SYNC upfront and sending
/// the record to the decoding thread which will detect if just the SYNC byte had been
/// missed, then the CRC will match and the record can be decoded.
/// </remarks>
/// <remarks date="2019-Dec-05" author="T.Wiedebusch">
/// - Improved recovering of data sets at byte records with preceding SYNC byte:
/// - used payload length == 0 to skip decoding, it makes no sense to decode something where
/// the payload is empty,
/// - wait a certain time to complete incoming data.
/// </remarks>
/// <remarks date="2019-Dec-12" author="T.Wiedebusch">
/// - Inter byte read delay used instead of response delay.
/// </remarks>
/// <remarks date="2019-Dec-14" author="T.Wiedebusch">
/// - Flush buffer if received length is 0.
/// </remarks>
/// <remarks date="2022-Jul-15" author="T.Wiedebusch">
/// - Flush buffer (DiscardInBuffer) removed as some bytes are missing from time to time.
/// The IrDA sniffer showed these on the communication line, but they are incompletely received.
/// Assuming the DiscardInBuffer may be delayed, so the incoming data will be scrapped.
/// </remarks>
/// <remarks date="2025-Mai-12..14" author="T.Wiedebusch">
/// - Dispose procedure changed.
/// </remarks>
/// <remarks date="2026-Jan-06" author="T.Wiedebusch">
/// - Replaced 'ReadLine' with 'ReadTo' using a string delimiter to support others than LF "\n".
/// </remarks>
private void ReadingThreadLoop()
{
//put the receive thread to JoinWaitSleep to avoid reading and logger output for timeout on
//RFID communication, because RFID will handle the physical receive by itself
//here the assignment of the _synReadingThreadEvent to the reading thread is being done
_onSyncReceiveThread.WaitOne();
try
{
while (!_receiveToken.IsCancellationRequested)
{
try
{
//remind data marker for this Thread execution time slice
var dataSyncMarkThisRun = _syncMarkRecord;
//read line of ASCII indicated by syncByte == null
if (null == PortSettingsForTransmitProtocol.ProtSyncByte)
{
//exit is timeout from serialPort or received line
var rxStringRecord = _serialPort.ReadLine();
if (!string.IsNullOrEmpty(rxStringRecord))
{
//decode optional at synchronized data SyncStart, SyncEnd or DecodeIntermediate
//todo remove after air problem
if (SyncMarkRecord.SkipDecoding != dataSyncMarkThisRun)
{
OnRawRecordReceived?.Invoke(this, new StringPortDataEventArgs(rxStringRecord,
_receiveTimeStampPc, dataSyncMarkThisRun));
}
if (RecordStreamingRawData)
{
AsciiDataLogger.Debug($"{rxStringRecord}");
}
}
}
//byte record with preceding SYNC byte
else
{
//normally decoding is required
dataSyncMarkThisRun = SyncMarkRecord.DecodeEveryPackage;
//this is the record for decoding
var rxByteRecord = new List<Byte>();
//this is the informational record for logging on undetected SYNC byte
var rxRawRecord = new List<Byte>();
//each byte protocol has to start with a syncByte, this has to be detected first
Byte rxByte;
do
{
rxByte = (Byte)_serialPort.ReadByte();
//record raw data stream for output on missing sync-byte to investigate this issue
rxRawRecord.Add(rxByte);
} while (_serialPort.BytesToRead > 0 && PortSettingsForTransmitProtocol.ProtSyncByte != rxByte);
// if syncByte has not been detected skip read loop and wait for next incoming record
if (PortSettingsForTransmitProtocol.ProtSyncByte == rxByte)
{
//save received SYNC byte
rxByteRecord.Add(rxByte);
//to assemble the length it has to be extracted first from the record at given index,
//the payload length index cannot be 0 because at this position is always the SYNC byte,
//the overall length includes the SYNC byte!
var length = PortSettingsForTransmitProtocol.ProtLengthIndex + 1;
//if the protLengthIndex is null the protAddLength equals the entire record length
if (null == PortSettingsForTransmitProtocol.ProtLengthIndex)
{
length = PortSettingsForTransmitProtocol.ProtAddLength;
}
//read until length index to extract the length from the record
//the length index cannot be at position 0, because this is always the syncByte
do
{
rxByte = (Byte)_serialPort.ReadByte();
//read the next byte if doubled sync byte detected and required by transmit protocol settings
if (PortSettingsForTransmitProtocol.DoubleSyncByte)
{
if (rxByte.Equals(PortSettingsForTransmitProtocol.ProtSyncByte) &&
rxByteRecord[rxByteRecord.Count - 1]
.Equals(PortSettingsForTransmitProtocol.ProtSyncByte))
{
//skips this byte and read the next one
rxByte = (Byte)_serialPort.ReadByte();
}
}
//add byte to receive result buffer
rxByteRecord.Add(rxByte);
//capture the length at given index and readjust the record length
if (null != PortSettingsForTransmitProtocol.ProtLengthIndex
&& rxByteRecord.Count - 1 == PortSettingsForTransmitProtocol.ProtLengthIndex)
{
//if the received length indicates empty payload, then nothing is to decode
if (rxByte == 0)
{
//exit this loop
dataSyncMarkThisRun = SyncMarkRecord.SkipDecoding;
FlushBuffer();
}
else
{
//build the new length to continue this receive loop
length = (UInt16)(PortSettingsForTransmitProtocol.ProtAddLength + rxByte);
}
}
//wait a certain time to let the data stream coming in
if (length - rxByteRecord.Count - 1 > _serialPort.BytesToRead)
{
Thread.Sleep(2);
}
} while (rxByteRecord.Count < length && dataSyncMarkThisRun != SyncMarkRecord.SkipDecoding);
//all data received or skip decoding marked, skip decoding will clear the response timeout
//these protocols have always to be decoded because the base is a request protocol
OnRawRecordReceived?.Invoke(this, new ListBytePortDataEventArgs(rxByteRecord,
_receiveTimeStampPc, dataSyncMarkThisRun));
}
else
{
//output actual byte and recorded raw data stream to investigate missed sync-byte
_byteDataLogger.Warn($"{Ident} Response SYNC Byte missing. Raw Byte Received: " +
$"{BitConverter.ToString(rxRawRecord.ToArray())}");
}
}
}
catch (ThreadAbortException)
{
_byteDataLogger.Info($"{Ident} Thread abort exception fired!");
}
catch (TimeoutException)
{
//ATTENTION: This "_serialPort.DiscardInBuffer()" caused a lot of trouble as it flushes a few incoming
//bytes and therefore destroys the already started data stream. Here it had been left in to indicate
//this critical issue!
/*-------------------------------DO NOT ACTIVATE-----------------------------------------------------*/
//flush receive buffer at timeout to force task to enter JoinWaitSleep state in finally
//if (_serialPort.IsOpen)
//{
// _serialPort.DiscardInBuffer();
//}
/*---------------------------------------------------------------------------------------------------*/
_byteDataLogger.Debug($"{Ident} Read timeout({InterByteReadDelayMs}ms)");
//_byteDataLogger.Trace($"{Ident} Read timeout({PortSettingsForTransmitProtocol.ResponseTimeoutMs}ms)");
}
catch (Exception ex)
{
if (_receiveThread.ThreadState == ThreadState.Aborted
|| _receiveThread.ThreadState == ThreadState.AbortRequested)
{
_byteDataLogger.Debug( $"{Ident} ThreadState is Aborted or AbortRequested but an " +
"error occurred while reading records from serial port", ex);
}
else
{
_byteDataLogger.Error( $"{Ident} Error while reading records from serial port", ex);
}
}
finally
{
if (_serialPort.IsOpen && !_receiveToken.IsCancellationRequested)
{
//it makes no sense to put the thread to sleep if there is something to read
if (0 == _serialPort.BytesToRead)
{
//restore event delegate to activate handle for incoming records
_serialPort.DataReceived += PhysicalDataReceived;
//put this thread (receive thread) to JainWaitSleep
_onSyncReceiveThread.WaitOne();
}
}
}
} //while (!_tokenReadData.IsCancellationRequested)
} // over hole reading thread loop
catch (ThreadAbortException)
{
_byteDataLogger.Info($"{Ident} Thread abort exception fired!");
}
catch (Exception ex)
{
_byteDataLogger.Error($"{Ident} {ex.Message}");
}
//finally
//{
// //var retries = 5;
// //while (_serialPort != null && _serialPort.IsOpen && retries-- > 0)
// //{
// // _serialPort.Close();
// // if (_serialPort.IsOpen)
// // {
// // _byteDataLogger.Info($"{Ident} Port closing delay!");
// // Thread.Sleep(500);
// // }
// //}
// //if (_serialPort != null && _serialPort.IsOpen)
// // _byteDataLogger.Info($"{Ident} Port unable to close");
// //else
// // _byteDataLogger.Info($"{Ident} Port is closed");
//}
}
/// <inheritdoc />
public Boolean IsOpen()
{
return _serialPort != null && _serialPort.IsOpen;
}
/// <summary>
/// Serial port specific write routine
/// </summary>
/// <param name="txBuffer"></param>
/// <remarks date="2019-Apr-11" author="T.Wiedebusch">
/// - Hide data in logging for e.g. passwords
/// </remarks>
protected virtual void SpecificPortWrite(Byte[] txBuffer)
{
PhysicalWrite(txBuffer);
}
/// <summary>
/// Physically sending to the serial port
/// </summary>
/// <remarks date="2017" author="R.Drabesch">
/// - Initial
/// </remarks>
/// <remarks date="2018-Oct-23" author="T.Wiedebusch">
/// - Doubling of sync byte in protocol behind sync byte itself implemented
/// </remarks>
/// <remarks date="2022-Feb-25" author="T.Wiedebusch, R.Drabesch">
/// - Flush buffer as MOXA sometimes takes two messages and combines these to one!
/// As the Genesis needs a separated wake-up message with a following delay before the
/// real payload message, this causes a lot of trouble.
/// </remarks>
protected void PhysicalWrite(Byte[] txBuffer)
{
try
{
//don't double the sync byte itself
var txByteList = new List<Byte> { txBuffer[0] };
for (var byteCtr = 1; byteCtr < txBuffer.Length; byteCtr++)
{
txByteList.Add(txBuffer[byteCtr]);
//write the doubled sync byte again if required by transmit protocol settings
if (PortSettingsForTransmitProtocol.DoubleSyncByte)
{
if (txBuffer[byteCtr].Equals(PortSettingsForTransmitProtocol.ProtSyncByte))
{
txByteList.Add(txBuffer[byteCtr]);
}
}
}
_serialPort.Write(txByteList.ToArray(), 0, txByteList.Count);
// flush the buffer for MOXA, to avoid two subsequent communications assembled to one communication!
_serialPort.BaseStream.Flush();
OnRawRecordSendOut?.Invoke(this, new ListBytePortDataEventArgs(txByteList, DateTimeOffset.UtcNow));
}
catch (Exception ex)
{
_byteDataLogger.Error($"{Ident} {ex.Message}");
}
}
}
}
@@ -0,0 +1,40 @@
using System;
using System.IO.Ports;
using System.Threading;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.SerialPorts
{
/// <inheritdoc />
public class IrdaSerialPort : BaseSerialPort
{
/// <inheritdoc />
public IrdaSerialPort(String ident, SerialPort serialPort, TransmitPortSettings setting)
: base(ident, serialPort, setting)
{
}
/// <inheritdoc />
/// <remarks date="2018-Mar-15" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2019-Aug-07" author="T.Wiedebusch">
/// - wakeup burst pattern changed from 2 times 0xF0 to 4 times 0x01,
/// - required delay from 500µs to 1ms between wakeup burst and data.
/// </remarks>
/// <remarks date="2022-Jun-28" author="T.Wiedebusch">
/// - Removed comments and one delay (was at 2 x 1 ms).
/// </remarks>
protected override void SpecificPortWrite(Byte[] tx)
{
//send wake up burst
var wakeUpBurst = new Byte[] { 0x01, 0x01, 0x01, 0x01 };
PhysicalWrite(wakeUpBurst);
//delay at least 500 µs
Thread.Sleep(1);
//send the IrDA record
PhysicalWrite(tx);
}
}
}
@@ -0,0 +1,28 @@
using System;
using System.IO.Ports;
using log4net;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.SerialPorts
{
/// <inheritdoc />
public class LedSerialPort : BaseSerialPort
{
/// <inheritdoc />
public LedSerialPort(String ident, SerialPort serialPort, TransmitPortSettings setting) : base(ident, serialPort, setting)
{
}
/// <inheritdoc />
protected override void SpecificPortWrite(Byte[] tx)
{
throw new NotSupportedException("Led port does not support writing");
}
public ILog GetRawLogger()
{
return AsciiDataLogger;
}
}
}
@@ -0,0 +1,600 @@
using System;
using System.IO.Ports;
using System.Linq;
using System.Threading;
using log4net;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.SerialPorts
{
/// <inheritdoc />
public class RfidSerialPort : BaseSerialPort
{
/// <inheritdoc />
public override event EventHandler<BasePortDataEventArgs> OnRawRecordReceived;
private const Byte RfidFrameStartId = 0x01;
private const Byte RfidFrameTxLength = 0x12;
private const Byte RfidFrameRxLength = 0x0C;
private const Byte RfidFrameExpectedRxLength = 0x0A;
private const Byte RfidFramePayloadDataMarker = 0x7D;
private const Byte RfidRxModeMarker = 0x7E;
private const Byte RfidFramePollingByte = 0x03;
private const Int32 RfidRxStartSyncPosition = 0;
private const Int32 RfidRxLengthPosition = 1;
private const Int32 RfidRxModePosition = 4;
private const Int32 RfidRxDataPosition = 5;
private const Int32 RfidRxDataMarkerPosition = 11;
private const Int32 RfidRxCrcLowPosition = 12;
private const Int32 RfidRxCrcHighPosition = 13;
private const Int32 RfidRxBccPosition = 14;
private const Int32 RfidCommRetries = 4;
private const Int32 RfidStartPatternLength = 10;
//additional frame length for start, length and BCC added to frame length
//being in the length itself
private const Int32 RfidProtAddLength = 3;
private const Int32 RfidRxLength = RfidFrameRxLength + RfidProtAddLength;
private const Int32 RfidTxLength = RfidFrameTxLength + RfidProtAddLength;
//first byte is fixed to 0x7D (data mode) at the Tx or the last byte behind
//the payload at Rx, the others are from the device protocol (the payload),
//this byte needs to be included into the CRC calculation
private const Int32 RfidPayLoadLength = 7;
private const Int32 RfidRawPayLoadLength = 6;
//the raw payload to add to RFID transmit buffer, it has a start Id, a length position
//and some additional length needed to add to the raw Rx length
private Byte[] _rawTxPayLoad;
private Int32 _rawRxLength;
private Byte[] _rawRxPayLoad;
//create RFID payload
private Byte[] _rfidTxPayLoad;
private Int32 _payLoadTxCounterPosition;
private Int32 _payLoadRxCounterPosition;
//remind send state
private Boolean _txIsActive;
//marker for first protocol containing the start id and the length
private Boolean _waitRawStartSyncPattern;
//this is the RFID raw buffer being sent to the serial port
private Byte[] _rfidTxBuffer = new Byte[RfidTxLength];
private Byte[] _rfidRxBuffer = new Byte[RfidRxLength];
private Int32 _rfidCommRetryCounter = RfidCommRetries;
private RfidRxState _rfidRxState;
private static readonly ILog _logger = LogManager.GetLogger(typeof(RfidSerialPort));
//communication counter for debug for application layer
private Int32 _commCounter;
/// <inheritdoc />
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
public RfidSerialPort(String ident, SerialPort serialPort, TransmitPortSettings setting)
: base(ident, serialPort, setting)
{
}
/// <inheritdoc />
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Not needed and therefor deactivated by overwriting
/// </remarks>
public override void PhysicalDataReceived(Object sender, SerialDataReceivedEventArgs e)
{
//remove registration of delegate
GetBaseComport().DataReceived -= PhysicalDataReceived;
}
/// <inheritdoc />
/// <summary>
/// Overwritten routine, kicks off the first communication,
/// handles the communication state machine
/// </summary>
/// <param name="tx"></param>
/// <remarks date="2017-Dec-15" author="R.Drabesch">
/// - Not needed and therefor deactivated by overwriting
/// </remarks>
/// <remarks date="2017-Dec-17" author="T.Wiedebusch">
/// - communication scheduler removed, call directly the stats machine
/// </remarks>
/// <remarks date="2019-Apr-11" author="T.Wiedebusch">
/// - Hide data in logging for e.g. passwords
/// </remarks>
protected override void SpecificPortWrite(Byte[] tx)
{
//raw protocol before wrapped into RFID
SendDataViaRfid(tx);
RfidRxState rfidState;
//call state machine until ready
do
{
rfidState = RfidCommStateMachine();
} while (RfidRxState.CommunicationFinished != rfidState &&
RfidRxState.CommunicationFailed != rfidState);
}
/// <summary>
/// Send data via RFID
/// </summary>
/// <param name="data">data to be transferred via RFID</param>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2017-Dec-15" author="T.Wiedebusch">
/// - Receive size removed, will be automatically extracted from underlay-protocol (raw)
/// </remarks>
/// <remarks date="2017-Dec-17" author="T.Wiedebusch">
/// - Raw buffer increased to communication retries * raw packet size.
/// </remarks>
public void SendDataViaRfid(Byte[] data)
{
if (data.Length < RfidRawPayLoadLength) return;
//length is unknown before receiving the first raw data, setup to x packets size for
//receive routine
_rawRxLength = RfidRawPayLoadLength * _rfidCommRetryCounter;
_rawRxPayLoad = new Byte[_rawRxLength];
_waitRawStartSyncPattern = true;
//start with sending of data
_txIsActive = true;
//allow retries during communication
_rfidCommRetryCounter = RfidCommRetries;
//reset running counters
_payLoadTxCounterPosition = 0;
_payLoadRxCounterPosition = 0;
_commCounter = 0;
//copy part of the huge payload buffer (containing the entire data) to small
//6 byte chunks being able to transmit within one RFID communication
_rfidTxPayLoad = null;
_rfidTxPayLoad = new Byte[RfidRawPayLoadLength];
_rawTxPayLoad = new Byte[data.Length];
_rawTxPayLoad = data;
for (var i = 0; i < RfidRawPayLoadLength; i++)
{
_rfidTxPayLoad[i] = _rawTxPayLoad[_payLoadTxCounterPosition];
_payLoadTxCounterPosition++;
}
//assemble the RFID transmit buffer
PrepareRfidTxBuffer(ref _rfidTxBuffer, _rfidTxPayLoad);
//kick off first communication
_rfidRxState = RfidComm(ref _rfidRxBuffer, _rfidTxBuffer);
}
/// <summary>
/// RFID Tx buffer content
/// </summary>
/// <returns>array of RFID Tx buffer content</returns>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
public Byte[] GetRfidTxBuffer()
{
return _rfidTxBuffer;
}
/// <summary>
/// RFID Rx buffer content
/// </summary>
/// <returns>array of RFID Rx buffer content</returns>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
public Byte[] GetRfidRxBuffer()
{
return _rfidRxBuffer;
}
/// <summary>
/// read out the received data from RFID
/// </summary>
/// <returns></returns>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
public Byte[] GetDecodedDataFromRfid()
{
return _rawRxPayLoad;
}
/// <summary>
/// feedback of communication counter
/// </summary>
/// <returns>number of RFID communications</returns>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
public Int32 GetRfidComCounter()
{
return _commCounter;
}
/// <summary>
/// communication state machine
/// </summary>
/// <returns></returns>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2017-Dec-15" author="T.Wiedebusch">
/// - Extract length information from raw protocol
/// </remarks>
/// <remarks date="2017-Dec-17" author="T.Wiedebusch">
/// - Return of communication failed on missing start sync pattern of raw protocol
/// </remarks>
/// <remarks date="2017-Dec-19" author="T.Wiedebusch">
/// - New modes implemented to differ between sending / waiting for Rx start
/// and receiving
/// </remarks>
/// <remarks date="2018-Feb-21" author="T.Wiedebusch">
/// - Time stamp added
/// </remarks>
/// <remarks date="2019-Apr-12" author="T.Wiedebusch">
/// - Hide data in logging for e.g. passwords
/// </remarks>
public RfidRxState RfidCommStateMachine()
{
//check the receive status
switch (_rfidRxState)
{
case RfidRxState.EchoRxProtocolOk:
//clear payload buffer to force filling with polling pattern
_rfidTxPayLoad = null;
//send loop
if (_txIsActive)
{
if (_payLoadTxCounterPosition < _rawTxPayLoad.Length)
{
//assign new Tx buffer
Int32 arraySize;
if (_payLoadTxCounterPosition + RfidRawPayLoadLength <
_rawTxPayLoad.Length)
arraySize = RfidRawPayLoadLength;
else
arraySize = _rawTxPayLoad.Length - _payLoadTxCounterPosition;
_rfidTxPayLoad = new Byte[arraySize];
//fill payload buffer with remaining payload bytes
for (var i = 0; i < arraySize; i++)
{
if (_payLoadTxCounterPosition >= _rawTxPayLoad.Length) continue;
_rfidTxPayLoad[i] = _rawTxPayLoad[_payLoadTxCounterPosition];
_payLoadTxCounterPosition++;
}
}
else
{
//switch receive loop active
_txIsActive = false;
}
}
//reset retry counter
_rfidCommRetryCounter = RfidCommRetries;
//assemble new RFID transmit buffer
PrepareRfidTxBuffer(ref _rfidTxBuffer, _rfidTxPayLoad);
//start communication
_rfidRxState = RfidComm(ref _rfidRxBuffer, _rfidTxBuffer);
break;
case RfidRxState.DataRxProtocolOk:
//receive loop with polling pattern sending
for (var i = 0; i < RfidRawPayLoadLength; i++)
{
//avoid out of bounce access
if (_payLoadRxCounterPosition >= _rawRxLength) continue;
_rawRxPayLoad[_payLoadRxCounterPosition] =
_rfidRxBuffer[i + RfidRxDataPosition];
_payLoadRxCounterPosition++;
}
//check for entire message received
if (_payLoadRxCounterPosition >= _rawRxLength)
{
//assign time stamp of received record
var readDateTimePc = DateTimeOffset.UtcNow;
//communication finished, return data to caller
//RawRecordReceived(this, _rawRxPayLoad, readDateTimePc);
var byteList = _rawRxPayLoad.ToList();
OnRawRecordReceived?.Invoke(this, new ListBytePortDataEventArgs(byteList, readDateTimePc));
//log the RFID payload, RFID protocol is removed
//_logger.Trace($"{Ident} Read({BitConverter.ToString(_rawRxPayLoad)})");
return _waitRawStartSyncPattern ? RfidRxState.CommunicationFailed :
RfidRxState.CommunicationFinished;
}
//reset retry counter
_rfidCommRetryCounter = RfidCommRetries;
//start communication with polling pattern, has been assembled in last send run
_rfidRxState = RfidComm(ref _rfidRxBuffer, _rfidTxBuffer);
break;
case RfidRxState.Idle:
break;
default:
//RFID or device is not ready or protocol error, start retry
if (_rfidCommRetryCounter > 0)
{
_logger.Debug($"{Ident} Internal RFID retry");
_rfidCommRetryCounter--;
//send the same RFID message again
_rfidRxState = RfidComm(ref _rfidRxBuffer, _rfidTxBuffer);
}
else
{
//communication failed because of exceeded internal RFID retry counter
_rfidRxState = RfidRxState.CommunicationFailed;
_logger.Debug($"{Ident} Internal RFID communication failed, RFID retry counter exceeded");
}
break;
}
return _rfidRxState;
}
/// <summary>
/// send and receive
/// </summary>
/// <param name="rxBuffer"></param>
/// <param name="txBuffer"></param>
/// <returns>RfidRxState</returns>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2017-Dec-18" author="T.Wiedebusch">
/// - Communication timeout activated.
/// </remarks>
/// <remarks date="2019-Apr-12" author="T.Wiedebusch">
/// - Hide data in logging for e.g. passwords
/// </remarks>
private RfidRxState RfidComm(ref Byte[] rxBuffer, Byte[] txBuffer)
{
var rfidRxState = RfidRxState.CommPortError;
if (!IsOpen()) return rfidRxState;
Clear();
//_logger.Trace($"{Ident} RFID raw sent({BitConverter.ToString(txBuffer)})");
PhysicalWrite(txBuffer);
_commCounter++;
//set timeout for receive exit, take 5 records of 6 byte chunks as base
var commTimeoutMs = PortSettingsForTransmitProtocol.ResponseTimeoutMs / 5;
while (commTimeoutMs > 0 && BytesToRead() < RfidRxLength)
{
Thread.Sleep(1);
commTimeoutMs--;
}
var i = 0;
//test if something is in input buffer
if (RfidRxLength > BytesToRead())
{
_logger.Debug($"{Ident} Internal RFID read timeout({PortSettingsForTransmitProtocol.ResponseTimeoutMs}ms)");
return RfidRxState.RxTimeout;
}
while (BytesToRead() > 0 && i < RfidRxLength)
{
rxBuffer[i] = (Byte)ReadByte();
i++;
}
if (i != RfidRxLength) return rfidRxState;
//all expected bytes received
//_logger.Trace($"{Ident} RFID raw read({BitConverter.ToString(rxBuffer)})");
rfidRxState = CheckRfidRxBuffer(rxBuffer);
return rfidRxState;
}
/// <summary>
/// check the received buffer CRC, BCC and
/// </summary>
/// <param name="rfidRxBuffer"></param>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2017-Dec-19" author="T.Wiedebusch">
/// - New modes implemented to differ between sending / waiting for Rx start
/// and receiving
/// </remarks>
/// <returns>RfidRxState</returns>
private RfidRxState CheckRfidRxBuffer(Byte[] rfidRxBuffer)
{
//retry required by start sync error
if (RfidFrameStartId != rfidRxBuffer[RfidRxStartSyncPosition])
return RfidRxState.StartSyncError;
//retry required by length error
if (RfidFrameRxLength != rfidRxBuffer[RfidRxLengthPosition])
return RfidRxState.LengthError;
//data marked as useful data
if (RfidRxModeMarker != rfidRxBuffer[RfidRxModePosition] ||
RfidFramePayloadDataMarker != rfidRxBuffer[RfidRxDataMarkerPosition])
return RfidRxState.RetryRequired;
//BCC check transfer entire receive buffer, this will automatically handled
if (rfidRxBuffer[RfidRxBccPosition] != BuildRfidBcc(rfidRxBuffer))
return RfidRxState.BccError;
//extract the 6 data (payload) bytes and the data frame marker
var testBuffer = new Byte[RfidPayLoadLength];
for (var i = 0; i < RfidPayLoadLength; i++)
testBuffer[i] = rfidRxBuffer[i + RfidRxDataPosition];
//CRC with LSB first
var buildCrc = Crc16Ccitt.CalculateLsb0408(testBuffer);
UInt16 receivedCrc = rfidRxBuffer[RfidRxCrcHighPosition];
receivedCrc <<= 8;
receivedCrc &= 0xFF00;
receivedCrc += rfidRxBuffer[RfidRxCrcLowPosition];
if (receivedCrc != buildCrc) return RfidRxState.DataCrcError;
//send loop is active
if (_txIsActive) return RfidRxState.EchoRxProtocolOk;
//if the start sync byte has been detected the normal receive mode is active
if (!_waitRawStartSyncPattern) return RfidRxState.DataRxProtocolOk;
//search for raw protocol start sync Id to extract length information,
//if start Id hasn't been found the wait for Rx start mode is active
//forcing the retry loop being active (default switch)
if (PortSettingsForTransmitProtocol.ProtSyncByte != _rfidRxBuffer[RfidRxDataPosition])
return RfidRxState.WaitRxStartProtocolOk;
//here the Rx mode is going to be activated, first data received including
//start sync Id and length information
_waitRawStartSyncPattern = false;
_rawRxLength = _rfidRxBuffer[RfidRxDataPosition +
(PortSettingsForTransmitProtocol.ProtLengthIndex.HasValue ?
PortSettingsForTransmitProtocol.ProtLengthIndex.Value : 0)] +
PortSettingsForTransmitProtocol.ProtAddLength;
_rawRxPayLoad = new Byte[_rawRxLength];
return RfidRxState.DataRxProtocolOk;
}
/// <summary>
/// assemble the RFID transmit buffer as bytes
/// </summary>
/// <param name="rfidTxBuffer"></param>
/// <param name="payLoad"></param>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
private static void PrepareRfidTxBuffer(ref Byte[] rfidTxBuffer, Byte[] payLoad)
{
RfidTxProtocol rfidTxStruct;
rfidTxStruct.StartPattern = new Byte[]
{
RfidFrameStartId, //start identifier 0x01
RfidFrameTxLength, //length, start behind length without BCC
0xE8, 0x90, 0x00, //CMD 1, 2, 3
0x00, 0x32, 0x00, 0x11, //Power 1 and 2
0x48 //TX bits
};
//the default data is the polling pattern 0x03
rfidTxStruct.PayLoad = new[] { RfidFramePayloadDataMarker,
RfidFramePollingByte, RfidFramePollingByte, RfidFramePollingByte,
RfidFramePollingByte, RfidFramePollingByte, RfidFramePollingByte};
//fill the payload with real data
var i = 0;
if (payLoad != null)
{
//put payload behind RFID frame payload data marker
for (; i < payLoad.Length; i++)
rfidTxStruct.PayLoad[i + 1] = payLoad[i];
}
rfidTxStruct.ExpectedRxBytes = RfidFrameExpectedRxLength;
//fill transmit buffer with constant start pattern for RFID communication
i = 0;
for (; i < RfidStartPatternLength; i++)
{
rfidTxBuffer[i] = rfidTxStruct.StartPattern[i];
}
//add payload to buffer, first byte is constant 0x7D (data mode)
var c = 0;
for (; i < RfidStartPatternLength + RfidPayLoadLength; i++)
{
rfidTxBuffer[i] = rfidTxStruct.PayLoad[c];
c++;
}
//add data CRC, LSB first
rfidTxStruct.DataCrc = Crc16Ccitt.CalculateLsb0408(rfidTxStruct.PayLoad);
rfidTxBuffer[i] = (Byte)(rfidTxStruct.DataCrc & 0xFF);
i++;
rfidTxBuffer[i] = (Byte)((rfidTxStruct.DataCrc & 0xFF00) >> 8);
//add expected receive bytes
i++;
rfidTxBuffer[i] = rfidTxStruct.ExpectedRxBytes;
//add BCC
rfidTxStruct.Bcc = BuildRfidBcc(rfidTxBuffer);
i++;
rfidTxBuffer[i] = rfidTxStruct.Bcc;
}
/// <summary>
/// build simple byte by byte XOR'ed checksum called BCC for RFID
/// </summary>
/// <param name="rfidRawBuffer"></param>
/// <returns>BCC code</returns>
/// <remarks date="2017-Dec-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
private static Byte BuildRfidBcc(Byte[] rfidRawBuffer)
{
//remove the "start byte" and the BCC itself
var maxCounts = rfidRawBuffer.Length - 1;
//start behind the "start byte"
var i = 1;
Byte bcc = rfidRawBuffer[i];
//take the second value
i++;
for (; i < maxCounts; i++)
{
bcc ^= rfidRawBuffer[i];
}
return bcc;
}
private struct RfidTxProtocol
{
public Byte[] StartPattern;
public Byte[] PayLoad;
public UInt16 DataCrc;
public Byte ExpectedRxBytes;
public Byte Bcc;
}
}
public enum RfidRxState
{
/// <summary>
/// No communication is ongoing
/// </summary>
Idle,
/// <summary>
/// Echo of sent protocol is received successfully back
/// </summary>
EchoRxProtocolOk,
/// <summary>
/// Waiting for switch from transmitting to receiving
/// </summary>
WaitRxStartProtocolOk,
/// <summary>
/// Received data message is valid
/// </summary>
DataRxProtocolOk,
/// <summary>
/// Communication port assignment error
/// </summary>
CommPortError,
/// <summary>
/// Start of synchronization error
/// </summary>
StartSyncError,
/// <summary>
/// Length error
/// </summary>
LengthError,
/// <summary>
/// Retry required
/// </summary>
RetryRequired,
/// <summary>
/// CRC error of data in payload field
/// </summary>
DataCrcError,
/// <summary>
/// BCC (special checksum) error of RFID
/// </summary>
BccError,
/// <summary>
/// Receive timeout
/// </summary>
RxTimeout,
/// <summary>
/// Communication to RFID failed, record cannot be assembled
/// </summary>
CommunicationFailed,
/// <summary>
/// Communication to RFID successfully executed
/// </summary>
CommunicationFinished
}
}
@@ -0,0 +1,17 @@
using System;
using System.IO.Ports;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.SerialPorts
{
/// <inheritdoc />
public class UartSerialPort : BaseSerialPort
{
/// <inheritdoc />
public UartSerialPort(String ident, SerialPort serialPort, TransmitPortSettings setting) : base(ident, serialPort, setting)
{
}
}
}
@@ -0,0 +1,155 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol;
using BaseDataEventArgs = TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments.BaseDataEventArgs;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.ProtocolCore
{
/// <inheritdoc />
/// <summary>
/// Base for all protocol types
/// </summary>
public abstract class BaseProtocol : IProtocol
{
private readonly CancellationTokenSource _decodingToken = new CancellationTokenSource();
/// <inheritdoc />
public abstract event EventHandler<BaseDataEventArgs> OnRecordIsDecoded;
/// <inheritdoc />
public virtual event EventHandler<BasePortDataEventArgs> OnRecordReadyToSend;
private readonly ConcurrentQueue<IPortDataEventArgs> _decodingFifo = new ConcurrentQueue<IPortDataEventArgs>();
//initially do not signal event
private readonly AutoResetEvent _onSyncDecodingThread = new AutoResetEvent(false);
private readonly Thread _decodingThread;
/// <summary>
/// Ident is a combined string of Slot, Port, Protocol and Type
/// </summary>
protected readonly String Ident;
private ITransmitProtocol _transmitProtocol;
/// <summary>
/// Starting decoding thread
/// </summary>
/// <remarks date="2018-Feb-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
protected BaseProtocol(String ident)
{
Ident = ident;
//assign thread to loop
_decodingThread = new Thread(DecodingThreadLoop) { Name = $"{Ident} Decoding thread" };
//start DecodingThread
ThreadWatcher.Instance.Start(_decodingThread);
}
/// <inheritdoc />
/// <summary>
/// Kill the decoding thread
/// </summary>
/// <remarks date="2018-Feb-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Oct-25" author="T.Wiedebusch">
/// - try catch block.
/// </remarks>
public virtual void Dispose()
{
try
{
//Cancel receive tokens
_decodingToken.Cancel();
//Run thread again to notice CancellationToken has changed
_onSyncDecodingThread.Set();
//this timeout counter is being used for dispose only
var timeoutCounter = 100;
while (_decodingThread.ThreadState != ThreadState.Stopped && timeoutCounter > 0)
{
Thread.Sleep(1);
timeoutCounter -= 1;
//Run thread again to notice CancellationToken has changed
_onSyncDecodingThread.Set();
}
if (_decodingThread.ThreadState != ThreadState.Stopped)
{
//if thread still running, he stuck so try to abort
// try to avoid abort (takes age to run and is not safe)
_decodingThread.Abort();
}
}
catch (Exception ex)
{
throw new ApplicationException(ex.Message);
}
}
/// <summary>
/// Fill FIFO with received data
/// </summary>
/// <remarks date="2018-Feb-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
public void FillDecodingBuffer(IPortDataEventArgs data)
{
if (_decodingToken.IsCancellationRequested) return;
//put data to FIFO
_decodingFifo.Enqueue(data);
//put DecodingThread state from WaitSleepJoin to Running
_onSyncDecodingThread.Set();
}
/// <summary>
/// Decoding thread loop calling the individual decoding
/// </summary>
/// <remarks date="2018-Feb-14" author="T.Wiedebusch">
/// - Initial
/// </remarks>
private void DecodingThreadLoop()
{
try
{
while (!_decodingToken.IsCancellationRequested)
{
while (_decodingFifo.TryDequeue(out var receivedRecord))
{
DecodeRecord(receivedRecord);
}
//put DecodingThread to WaitSleepJoin until next data arrived
_onSyncDecodingThread.WaitOne();
}
}
catch (ThreadAbortException)
{ }
}
/// <summary>
/// The decoding routine
/// </summary>
/// <param name="data"></param>
protected abstract void DecodeRecord(IPortDataEventArgs data);
/// <inheritdoc />
public ITransmitProtocol GetTransmitProtocol()
{
return _transmitProtocol;
}
/// <inheritdoc />
public void SetTransmitProtocol(ITransmitProtocol transmitProtocol)
{
_transmitProtocol = transmitProtocol;
}
}
}
@@ -0,0 +1,33 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.ProtocolCore.EventArguments
{
/// <inheritdoc />
/// <summary>
/// abstract for set structure for BaseDataEventArgs
/// </summary>
public abstract class BaseDataEventArgs : EventArgs
{
/// <summary>
/// 'Base' get event record form real child.
/// </summary>
/// <returns></returns>
public Object GetData()
{
return GetEventData();
}
/// <summary>
/// get real Event record
/// </summary>
/// <returns></returns>
public abstract Object GetEventData();
/// <summary>
/// Holds the record before decoding, for logging
/// </summary>
public String RawData;
}
}
@@ -0,0 +1,42 @@
using System;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol;
using BaseDataEventArgs = TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments.BaseDataEventArgs;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.ProtocolCore
{
/// <inheritdoc />
/// <summary>
/// Protocol interface
/// </summary>
public interface IProtocol : IDisposable
{
/// <summary>
/// Fill the buffer with data ready for decoding
/// </summary>
/// <param name="rawData"></param>
void FillDecodingBuffer(IPortDataEventArgs rawData );
/// <summary>
/// Record is ready to send including all protocols
/// </summary>
event EventHandler<BasePortDataEventArgs> OnRecordReadyToSend;
/// <summary>
/// Record is successfully decoded and ready for further processing
/// </summary>
event EventHandler<BaseDataEventArgs> OnRecordIsDecoded;
/// <summary>
/// Return of transmit protocol settings
/// </summary>
/// <returns></returns>
ITransmitProtocol GetTransmitProtocol();
/// <summary>
/// Assignment of transmit protocol
/// </summary>
/// <param name="transmitProtocol"></param>
void SetTransmitProtocol(ITransmitProtocol transmitProtocol);
}
}
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using log4net;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol
{
public abstract class BaseTransmitProtocol : ITransmitProtocol
{
public static readonly ILog Logger = LogManager.GetLogger(typeof(BaseTransmitProtocol));
protected BaseTransmitProtocol(String port)
{
}
public abstract TransmitPortSettings GetTransmitPortSettings();
public abstract void SetResponseTimeout(Int32 responseTimeoutMs);
public abstract void SetDefaultResponseTimeout();
public abstract List<Byte> DecodeDataForPhysicalLayer(String ident, Byte command, Byte[] payload, Boolean hideDataInLog = false);
public abstract List<Byte> DecodeDataForLogicLayer(String ident, List<Byte> rawData, Boolean hideDataInLog = false);
public abstract List<Byte> DecodeDataForPhysicalLayerUI1236(String ident, Byte[] payload, Boolean hideDataInLog = false);
}
}
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol
{
public interface ITransmitProtocol
{
/// <summary>
/// Struct of settings being needed for transmit port
/// </summary>
/// <returns></returns>
TransmitPortSettings GetTransmitPortSettings();
/// <summary>
/// Overwrite the default response timeout
/// </summary>
void SetResponseTimeout(Int32 responseTimeoutMs);
/// <summary>
/// Set timeout to the default response time
/// </summary>
void SetDefaultResponseTimeout();
// ReSharper disable once InconsistentNaming UI1236 is a naming forced by the caller
List<Byte> DecodeDataForPhysicalLayerUI1236(String ident, Byte[] payload, Boolean hideDataInLog = false);
/// <summary>
/// Send out data (including transport protocol specific data like CRC) to physical port (like UART or IrDA)
/// </summary>
List<Byte> DecodeDataForPhysicalLayer(String ident, Byte command , Byte[] payload, Boolean hideDataInLog = false);
/// <summary>
/// Received data have to be checked and converted to request protocol
/// </summary>
List<Byte> DecodeDataForLogicLayer(String ident, List<Byte> rawData, Boolean hideDateInLog = false);
}
}
@@ -0,0 +1,213 @@
using System;
using System.Collections.Generic;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol
{
/// <summary>
/// IrDA transmission protocol
/// </summary>
public class IrdaTransmitProtocol : BaseTransmitProtocol
{
// Format of IrDA transmit protocol:
// IrdaSyncByte|IrdaSend/ReceiveHeader|IrdaPayLoadLength|IrdaCommand/Message|
// IrdaPayLoad|IrdaCrc LSB|IrdaCrc MSB
private const Byte IrdaSyncByte = 0x9B;
// Encoding of IrdaSendHeader (LAT: listen after talk, LAT 10b: 500ms)
// Bits: 0| 0| 10| 0| 000
// standard frame|adapter to register|LAT|reserved|optical command
private const Byte IrdaSendHeader = 0x20;
// Encoding of IrdaReceiveHeader (LAT: listen after talk, LAT 10b: 500ms)
// Bits: 0| 1| 10| 0| 001
// standard frame|register to adapter|LAT|reserved|optical message
private const Byte IrdaReceiveHeader = 0x61;
private const Byte IrdaWakeupHeader = 0x41;
// Mask out the LAT and the reserved bit for the IrdaReceiveHeader
private const Byte IrdaReceiveHeaderMask = 0xC7;
// data from adapter (software) to register (water meter) referred as optical command
private const Byte IrdaCommand = 0x02;
// data from register (water meter) to adapter (software) referred as optical message
private const Byte IrdaMessageId = 0x03;
private const Byte IrdaWakeupId = 0x04;
// IrdaSyncByte|IrdaReceiveHeader|IrdaPayLoadLength|IrdaMessageId|CRC LSB|CRC MSB
private const Int32 IrdaProtocolFrameLength = 6;
// Indexes of IrDA optical protocol
private const Int32 IrdaSyncByteIndex = 0;
private const Int32 IrdaHeaderIndex = 1;
private const Int32 IrdaPayLoadLengthIndex = 2;
private const Int32 IrdaMessageIndex = 3;
private const Int32 IrdaPayLoadIndex = 4;
//the IrDA length covers the payload length only
private const UInt16 ProtAddLength = IrdaProtocolFrameLength;
private const Int32 DefaultResponseTimeoutMs = 250;
//private const Int32 DefaultResponseTimeoutMs = 150;
private Int32 _responseTimeoutMs = DefaultResponseTimeoutMs;
private const UInt32 BaudRate = 115200;
private readonly UInt32? _receiveBufferFlushThreshold = 1;
/// <inheritdoc />
public override void SetResponseTimeout(Int32 responseTimeoutMs)
{
_responseTimeoutMs = responseTimeoutMs;
}
/// <inheritdoc />
public override void SetDefaultResponseTimeout()
{
_responseTimeoutMs = DefaultResponseTimeoutMs;
}
/// <inheritdoc />
public IrdaTransmitProtocol(String portName) : base(portName)
{
}
/// <inheritdoc />
public override TransmitPortSettings GetTransmitPortSettings()
{
return new TransmitPortSettings(IrdaSyncByte, IrdaPayLoadLengthIndex, ProtAddLength,
_responseTimeoutMs, BaudRate, _receiveBufferFlushThreshold);
}
/// <inheritdoc />
/// <remarks date="2018-Mar-15" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2020-Dec-19" author="T.Wiedebusch">
/// - Removed IrdaMessageId identifier check as it was observed receiving a 0x03 or a 0x04 in this field.
/// </remarks>
/// <remarks date="2022-Jul-19" author="T.Wiedebusch">
/// - Wakeup message detection reported to log-file,
/// - Message error text changed.
/// </remarks>
/// <remarks date="2023-Sep-01" author="T.Wiedebusch">
/// - Hide data in log introduced.
/// </remarks>
public override List<Byte> DecodeDataForLogicLayer(String ident, List<Byte> irdaRecord, Boolean hideDataInLog = false)
{
// avoid logging of passwords or other sensitive data
Logger.Info(hideDataInLog
? $"{ident} DecodeDataForLogicalLayer Cordonel->PC(*****)"
: $"{ident} DecodeDataForLogicalLayer Cordonel->PC({BitConverter.ToString(irdaRecord.ToArray())})");
// check the record length and start of frame information
if (irdaRecord.Count < IrdaProtocolFrameLength + irdaRecord[IrdaPayLoadLengthIndex]
|| irdaRecord[IrdaSyncByteIndex] != IrdaSyncByte
|| (((irdaRecord[IrdaHeaderIndex] & IrdaReceiveHeaderMask) != (IrdaReceiveHeader & IrdaReceiveHeaderMask)
&& (irdaRecord[IrdaHeaderIndex] != IrdaWakeupHeader))
|| ((irdaRecord[IrdaMessageIndex] != IrdaMessageId)
&& irdaRecord[IrdaMessageIndex] != IrdaWakeupId)))
{
var error = new ApplicationException($"{ident} Reply from IrDA is invalid.");
Logger.Error(error.Message, error);
return new List<Byte>();
}
// extract the received CRC first the MSB at last position
UInt16 receivedCrc = irdaRecord[irdaRecord.Count - 1];
receivedCrc <<= 8;
// add the LSB from before last position, the LSB of the CRC will be sent first
receivedCrc += irdaRecord[irdaRecord.Count - 2];
// the CRC is being built from IrdaReceiveHeader to end of IrdaPayload,
// subtract irdaSyncByteLength and irdaCrcLength
var irdaCrcInputBuffer = new List<Byte>();
irdaCrcInputBuffer.AddRange(irdaRecord.GetRange(IrdaHeaderIndex, irdaRecord.Count - 3));
var calculatedCrc = Crc16Ccitt.CalculateReversedLsb8408(irdaCrcInputBuffer.ToArray());
if (receivedCrc != calculatedCrc)
{
var error = new ApplicationException($"{ident} Reply CRC failure. Decoding of transmit layer failed.");
Logger.Error(error.Message, error);
return new List<Byte>();
}
// build the return value witch equals the IrDA payload
var irdaPayLoad = new List<Byte>();
irdaPayLoad.AddRange(irdaRecord.GetRange(IrdaPayLoadIndex, irdaRecord.Count - IrdaProtocolFrameLength));
return irdaPayLoad;
}
/// <inheritdoc />
/// <remarks date="2018-Mar-13" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2023-Mai-16" author="T.Wiedebusch">
/// - Hide data in log forwarded to DecodeDateForPhysicalLayer to hide passwords in log files.
/// </remarks>
public override List<Byte> DecodeDataForPhysicalLayer(String ident, Byte requestProtocolCommand, Byte[] requestProtocolPayload,
Boolean hideDataInLog = false)
{
var irdaCrcInputBuffer = new List<Byte>
{
// build CRC calculation buffer without sync byte
// IrdaSendHeader|IrdaPayLoadLength|IrdaCommand|requestProtocolCommand|requestProtocolPayload
IrdaSendHeader,
// IrdaPayloadLength is the requestProtocolCommandLength + requestProtocolPayloadLength
(Byte)(requestProtocolPayload.Length + 1),
IrdaCommand,
// add the IrdaPayload witch is the requestProtocolCommand + requestProtocolPayload
requestProtocolCommand
};
irdaCrcInputBuffer.AddRange(requestProtocolPayload);
var crcResult = Crc16Ccitt.CalculateReversedLsb8408(irdaCrcInputBuffer.ToArray());
// assemble result buffer
// IrdaSyncByte|crcInputBuffer|IrdaCrc LSB|IrdaCrc MSB
var irdaRecord = new List<Byte> { IrdaSyncByte };
irdaRecord.AddRange(irdaCrcInputBuffer);
irdaRecord.Add((Byte)(crcResult & 0xFF));
irdaRecord.Add((Byte)(crcResult >> 8));
// avoid logging of passwords or other sensitive data
Logger.Info(hideDataInLog
? $"{ident} DecodeDataForPhysicalLayer PC->Cordonel(*****)"
: $"{ident} DecodeDataForPhysicalLayer PC->Cordonel({BitConverter.ToString(irdaRecord.ToArray())})");
return irdaRecord;
}
/// <inheritdoc />
/// <remarks date="2022-Aug-24" author="R.Drabesch">
/// - Initial
/// </remarks>
public override List<Byte> DecodeDataForPhysicalLayerUI1236(String ident, Byte[] requestProtocolPayload,
Boolean hideDataInLog = false)
{
var collection = new List<Byte>
{
35,
(Byte) (requestProtocolPayload.Length / 2)
};
collection.AddRange(requestProtocolPayload);
var crcResult = Crc16Ccitt.CalculateReversedLsb8408(collection.ToArray());
var byteList = new List<Byte>
{
155
};
byteList.AddRange(collection);
byteList.Add((Byte)(crcResult & 0xFF));
byteList.Add((Byte)(crcResult >> 8));
// avoid logging of passwords or other sensitive data
Logger.Info(hideDataInLog
? $"{ident} DecodeDataForPhysicalLayerUI1236 PC->Cordonel(*****)"
: $"{ident} DecodeDataForPhysicalLayerUI1236 PC->Cordonel({BitConverter.ToString(byteList.ToArray())})");
return byteList;
}
}
}
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol
{
public class LedTransmitProtocol : BaseTransmitProtocol
{
private readonly Byte? _protSyncByte = null;
private const UInt16 ProtAddLength = 0;
private readonly UInt16? _protLengthIndex = null;
private const Int32 DefaultResponseTimeoutMs = 50;
private Int32 _responseTimeoutMs = DefaultResponseTimeoutMs;
private const UInt32 BaudRate = 115200;
private readonly UInt32? _receiveBufferFlushThreshold = 1000;
/// <inheritdoc />
public override void SetResponseTimeout(Int32 responseTimeoutMs)
{
_responseTimeoutMs = responseTimeoutMs;
}
/// <inheritdoc />
public override void SetDefaultResponseTimeout()
{
_responseTimeoutMs = DefaultResponseTimeoutMs;
}
public LedTransmitProtocol(String portName) : base (portName)
{
}
public override TransmitPortSettings GetTransmitPortSettings()
{
return new TransmitPortSettings(_protSyncByte, _protLengthIndex, ProtAddLength, _responseTimeoutMs, BaudRate,
_receiveBufferFlushThreshold);
}
public TransmitPortSettings GetTransmitPortSettings(UInt32 baudRate)
{
return new TransmitPortSettings(_protSyncByte, _protLengthIndex, ProtAddLength, _responseTimeoutMs, baudRate,
_receiveBufferFlushThreshold);
}
/// <inheritdoc />
public override List<Byte> DecodeDataForPhysicalLayer(String ident, Byte command, Byte[] payload, Boolean hideDataInLog = false)
{
throw new ApplicationException("Streaming port cannot send data");
}
/// <inheritdoc />
public override List<Byte> DecodeDataForLogicLayer(String ident, List<Byte> rawData, Boolean hideDataInLog = false)
{
return rawData;
}
public override List<Byte> DecodeDataForPhysicalLayerUI1236(String ident, Byte[] payload, Boolean hideDataInLog = false)
=> throw new ApplicationException("Streaming port cannot send data");
}
}
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol
{
public class RfidTransmitProtocol : UartTransmitProtocol
{
//use the UART transmit protocol sync byte wrapped to RFID protocol
private const Byte ProtSyncByte = 0x5B;
private const UInt16 ProtAddLength = 1;
private readonly UInt16? _protLengthIndex = 1;
//this will be used if RFID protocol has been extracted from RfidComPort
//private const Byte ProtSyncByte = 0x01;
//private const UInt16 ProtAddLength = 1;
//private readonly UInt16? _protLengthIndex = 3;
private const Int32 DefaultResponseTimeoutMs = 1550;
private Int32 _responseTimeoutMs = DefaultResponseTimeoutMs;
/// <inheritdoc />
public override void SetResponseTimeout(Int32 responseTimeoutMs)
{
_responseTimeoutMs = responseTimeoutMs;
}
/// <inheritdoc />
public override void SetDefaultResponseTimeout()
{
_responseTimeoutMs = DefaultResponseTimeoutMs;
}
private const UInt32 BaudRate = 9600;
private readonly UInt32? _receiveBufferFlushThreshold = null;
public RfidTransmitProtocol(String portName) : base (portName)
{
}
public override TransmitPortSettings GetTransmitPortSettings()
{
return new TransmitPortSettings(ProtSyncByte, _protLengthIndex, ProtAddLength,
_responseTimeoutMs, BaudRate, _receiveBufferFlushThreshold);
}
public override List<Byte> SpecificCrcCalc()
{
//returns an empty list of bytes to avoid filling of CRC in advance to the CRC calculation,
//because the RFID does not use the CRC filled up with 0x00, 0x00 to calculate itself
return new List<Byte>();
}
}
}
@@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using System.Linq;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.TransmitProtocol
{
public class UartTransmitProtocol : BaseTransmitProtocol
{
// Format of UART transmit protocol:
// UartSyncByte|UartLength LSB|UartLength MSB|requestProtocolCommand|
// NextRequestProtocolCommand|UartCrc LSB|UartCrc MSB|requestProtocolPayload
// UartLength MSB will NOT be used, it is always 0x00
// NextRequestProtocolCommand will NOT be used, it is always 0x00
//Indexes of UART transmit protocol
private const Int32 UartSyncByteIndex = 0;
private const Int32 UartLengthIndex = 1;
private const Int32 UartCommandIndex = 3;
private const Int32 UartCrcIndex = 5;
private const Int32 UartPayloadIndex = 7;
//length of UART transmit protocol fields
private const Int32 UartSyncByteLength = 1; //length of syncByte
private const Int32 UartHeaderLength = 4; //length excluding syncByte and CRC
private const Int32 UartCrcLength = 2;
private const Int32 UartProtocolFrameLength = UartSyncByteLength + UartHeaderLength + UartCrcLength;
private const Byte UartSyncByte = 0x5B;
//The UART length covers all bytes excluding the syncByte length
private const Int32 DefaultResponseTimeoutMs = 50;
private Int32 _responseTimeoutMs = DefaultResponseTimeoutMs;
private const UInt16 ProtAddLength = UartSyncByteLength;
private const UInt32 BaudRate = 9600;
private readonly UInt32? _receiveBufferFlushThreshold = null;
private const Boolean UartDoubleSyncByte = true;
/// <inheritdoc />
public override void SetResponseTimeout(Int32 responseTimeoutMs)
{
_responseTimeoutMs = responseTimeoutMs;
}
/// <inheritdoc />
public override void SetDefaultResponseTimeout()
{
_responseTimeoutMs = DefaultResponseTimeoutMs;
}
public UartTransmitProtocol(String portName) : base (portName)
{
}
public override TransmitPortSettings GetTransmitPortSettings()
{
return new TransmitPortSettings(UartSyncByte, UartLengthIndex, ProtAddLength,
_responseTimeoutMs, BaudRate, _receiveBufferFlushThreshold, UartDoubleSyncByte);
}
public override List<Byte> DecodeDataForLogicLayer(String ident, List<Byte> uartRecord, Boolean hideDataInLog = false)
{
// check the record length and start of frame information
if (uartRecord.Count < uartRecord[UartLengthIndex] + UartSyncByteLength
|| UartSyncByte != uartRecord[UartSyncByteIndex])
{
var error = new ApplicationException($"{ident} Reply from UART is invalid. Decoding of transmit layer failed.");
Logger.Error(error.Message, error);
return new List<Byte>();
}
//extract the header without syncByte and the payload, call CRC calculation
var calculatedCrc = CrcCalc(uartRecord.GetRange(UartLengthIndex, UartHeaderLength),
uartRecord.GetRange(UartPayloadIndex, uartRecord.Count - UartProtocolFrameLength));
//extract the received CRC, first the MSB
UInt16 receivedCrc = uartRecord[UartCrcIndex + 1];
receivedCrc <<= 8;
receivedCrc += uartRecord[UartCrcIndex];
if(receivedCrc != calculatedCrc)
{
var error = new ApplicationException($"{ident} Reply CRC failure. Decoding of transmit layer failed.");
Logger.Error(error.Message, error);
return new List<Byte>();
}
//the request protocol needs the requestProtocolCommand and the requestProtocolPayload
var ret = new List<Byte> {uartRecord[UartCommandIndex]};
//add the requestProtocolCommand
//add the request protocol payload
ret.AddRange(uartRecord.GetRange(UartPayloadIndex, uartRecord.Count - UartProtocolFrameLength));
return ret;
}
public virtual List<Byte> SpecificCrcCalc()
{
//for the pure UART transmit protocol the CRC fields will be set
//to 0x00 and used for the CRC calculation
return new List<Byte>() { 0x00, 0x00 };
}
public UInt16 CrcCalc(List<Byte> header, List<Byte> payload)
{
//the CRC calculation excludes the syncByte and optional includes the
//CRC fields filled up with 0x00
var tmpArr = new List<Byte>();
tmpArr.AddRange(header);
//this adds the optional CRC fields
tmpArr.AddRange(SpecificCrcCalc());
tmpArr.AddRange(payload);
return Crc16Ccitt.CalculateMsb1021(tmpArr.ToArray());
}
/// <inheritdoc />
public override List<Byte> DecodeDataForPhysicalLayer(String ident, Byte requestProtocolCommand,
Byte[] requestProtocolPayload, Boolean hideDataInLog = false)
{
var uartHeader = new List<Byte>();
var uartPayload = requestProtocolPayload.ToList();
var uartRecord = new List<Byte>();
//first assemble the list being used for CRC calculation,
//this list does NOT contain the syncByte and optional
//the CRC LSB and MSB if set in SpecificCrcCalc()
//the length of length LSB and MSB: 2
//add the length of the requestProtocolCommand: 1
//the nextRequestProtocolCommand length: 1
//the length of the CRC MSB and LSB: 2
uartHeader.Add((Byte)(requestProtocolPayload.Length + UartHeaderLength + UartCrcLength));
//add the length MSB, always 0x00
uartHeader.Add(0x00);
uartHeader.Add(requestProtocolCommand);
//add the nextRequestProtocolCommand, always 0x00
uartHeader.Add(0x00);
var crcResult = CrcCalc(uartHeader, uartPayload);
//assemble the entire record for sending via the communication port
uartRecord.Add(UartSyncByte);
uartRecord.AddRange(uartHeader);
uartRecord.Add((Byte)(crcResult & 0xFF));
uartRecord.Add((Byte)(crcResult >> 8));
uartRecord.AddRange(uartPayload);
return uartRecord;
}
/// <inheritdoc />
/// <remarks date="2022-Aug-24" author="R.Drabesch">
/// - Initial
/// </remarks>
public override List<Byte> DecodeDataForPhysicalLayerUI1236(String ident, Byte[] requestProtocolPayload,
Boolean hideDataInLog = false)
{
var uartHeader = new List<Byte>();
var uartPayload = requestProtocolPayload.ToList();
var uartRecord = new List<Byte>();
uartHeader.Add((Byte)(requestProtocolPayload.Length + 4 + 2));
uartHeader.Add(0);
uartHeader.Add(0);
var crcResult = CrcCalc(uartHeader, uartPayload);
uartRecord.Add(91);
uartRecord.AddRange(uartHeader);
uartRecord.Add((Byte)(crcResult & 0xFF));
uartRecord.Add((Byte)(crcResult >> 8));
uartRecord.AddRange(uartPayload);
return uartRecord;
}
}
}
@@ -0,0 +1,53 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis
{
public static class ProgramConfig
{
/// <summary>
/// Name of register definition file
/// </summary>
public const String RegisterDefinitionFileName = "configuration.json";
/// <summary>
/// Name of serial configuration file
/// </summary>
public const String FM2014ConfigFileName = "FM2014Config.json";
/// <summary>
/// Name of serial configuration file
/// </summary>
public const String SerialConfigFileName = "SerialConfig.json";
/// <summary>
/// Name for NLog config
/// </summary>
public const String NlogConfig = "NlogConfig.xml";
/// <summary>
/// Name for meter config
/// </summary>
public const String MeterConfigFileName = "MeterConfig.json";
/// <summary>
/// Name for SIRT config
/// </summary>
public const String SirtConfigFileName = "SirtConfig.json";
/// <summary>
/// Name for offline information e.g. Passwords
/// </summary>
public const String OfflineInfoFile = "OfflineFile.json";
/// <summary>
/// Path for data logger.
/// </summary>
public const String BaseLoggingPath = "C:\\GenesisLog\\";
/// <summary>
/// Sub folder for genesis contents.
/// </summary>
public const String GenesisBaseFolder = "Genesis\\";
}
}
@@ -0,0 +1,125 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Consts
{
/// <summary>
/// Commands that the genesis meter support over the request protocol.
/// Functional Specification Breeze Core and Applications Revision:3.03(12748DOC11 - functional spec ICOE472.pdf)
/// </summary>
public static class Commands
{
/// <summary>
/// 9.2.5
///Command 0x00 (NOP)
///This command will do nothing, and will have no response.
/// </summary>
public const Byte Nop = 0x00;
/// <summary>
/// 9.2.6
/// Command 0x01 (Query capabilities)
/// This command will be used by the external computer to discover the protocol parameters that may be varied.
/// These can then be compared with the external computers capabilities and the best match selected.
/// </summary>
public const Byte QueryCaps = 0x01;
/// <summary>
/// response on <see cref="QueryCaps"/>
/// </summary>
public const Byte QueryCapsReply = 0x02;
/// <summary>
/// 9.2.7
/// Command 0x03 (Set capabilities)
/// Used to finalize the baud rate and packet settings after negotiation.
/// The reply will be sent at the currently selected baud rate and packet length,
/// after which the settings will take effect
/// </summary>
public const Byte SetCaps = 0x03;
/// <summary>
/// response on <see cref="SetCaps"/>
/// </summary>
public const Byte SetCapsReply = 0x04;
/// <summary>
/// 9.2.8
/// Command 0x05 (Train)
///This command will perform target driven data training, that is, where the target is in control of the data flow.
///See also command 0x11.
/// </summary>
public const Byte Train = 0x05;
/// <summary>
/// response on <see cref="Train"/>
/// </summary>
public const Byte TrainReply = 0x06;
/// <summary>
/// 9.2.9
/// Command 0x07 (Repeat last)
/// Used by the external computer to request the last response to be resent, for example if it was found to be corrupted.
/// Note that the response 0x08 will never be sent, the reply to command 0x07 will be a verbatim resend of the last response.
/// </summary>
public const Byte RepeatLast = 0x07;
/// <summary>
/// response on <see cref="RepeatLast"/>
/// </summary>
public const Byte RepeatLastReply = 0x08;
/// <summary>
/// 9.2.10
///Command 0x09 (Read data)
///This command makes reads of any random selection of configuration registers, up to the maximum packet size negotiated.
/// </summary>
public const Byte ReadData = 0x09;
/// <summary>
/// response on <see cref="ReadData"/>
/// </summary>
public const Byte ReadDataReply = 0x0A;
/// <summary>
/// 9.2.11
/// Command 0x0B (Write data)
/// This command makes writes to any random selection of configuration registers, up to the maximum packet size negotiated.
/// </summary>
public const Byte WriteData = 0x0B;
/// <summary>
/// response on <see cref="WriteData"/>
/// </summary>
public const Byte WriteDataReply = 0x0C;
/// <summary>
/// 9.2.12
///Command 0x0D (Multiple read data)
///This command makes reads of one register multiple times, which will be more efficient than performing successive reads using command 0x09.
///This command will be available from protocol version 0.40, for earlier protocol versions command 0x09 should be used.
/// </summary>
public const Byte MultipleReadData = 0x0D;
/// <summary>
/// response on <see cref="MultipleReadData"/>
/// </summary>
public const Byte MultipleReadDataReply = 0x0E;
/// <summary>
/// 9.2.13
/// Command 0x0F (Multiple write data)
/// This command makes writes to one register multiple times, which will be more efficient than performing successive reads using command 0x0B.
/// This command will be available from protocol version 0.40, for earlier protocol versions command 0x0B should be used.
/// </summary>
public const Byte MultipleWriteData = 0x0F;
/// <summary>
/// response on <see cref="MultipleWriteData"/>
/// </summary>
public const Byte MultipleWriteDataReply = 0x10;
/// <summary>
/// 9.2.14
/// Command 0x11 (Set level)
/// This command will perform external driven data training, that is, where the external computer is in control of the data flow.
/// This command will be available from protocol version 0.42, for earlier protocol versions command 0x05 should be used.
/// </summary>
public const Byte SetLevel = 0x11;
/// <summary>
/// response on <see cref="SetLevel"/>
/// </summary>
public const Byte SetLevelReply = 0x12;
//todo remove?
//public const byte SetBitrateLsb = 0x03;
//public const byte SetBitrateMsb = 0x05;
//public const byte SetPacketSizesLsb = 0x04;
//public const byte SetPacketSizesMsb = 0x05;
//public const byte SetProtocolLsb = 0x02;
//public const byte SetProtocolMsb = 0x05;
}
}
@@ -0,0 +1,31 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Consts
{
/// <summary>
/// Config Exchange error codes.
/// </summary>
public enum ConfigExErrors
{
#pragma warning disable CS1591 //as a matter of course
Base = 0x04,
LockedOut = 0x00,
AuthenticationFail = 0x01,
AccessDenied = 0x02,
UnknownParameter = 0x03,
InUse = 0x04,
SizesDoesNotMatch = 0x05,
CantReadCfgFile = 0x06,
UserNotKnown = 0x07,
CantCreateCfgFile = 0x08,
StoringFailed = 0x09,
StoreCorrupt = 0x0A,
ExpectedWrite = 0x0B,
ExpectedRead = 0x0C,
StopCycling = 0x0D,
TooManyOpen = 0x0E,
NeverOpened = 0x0F,
FileProtected = 0x1F,
PartialRecall = 0x2F,
DefaultPasswordUsed = 0x3F,
#pragma warning restore
}
}
@@ -0,0 +1,25 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Consts
{
/// <summary>
/// Transport errors for request protocol
/// </summary>
public enum HighLevelErrors
{
#pragma warning disable CS1591 //as a matter of course
Base = 0x05,
PayloadCount = 0x00,
InvalidSubReason = 0x01,
InvalidBaudrate = 0x02,
InvalidBufferSize = 0x03,
CrcFailure = 0x04,
UnrecognizedCmd = 0x05,
Framing = 0x06,
Overflow = 0x07,
PacketTimeout = 0x08,
InvalidEscape = 0x09,
UnknownParameter = 0x0A,
TrainingFailed = 0x0B,
NoBreak = 0x0C,
#pragma warning restore
}
}
@@ -0,0 +1,47 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Consts
{
/// <summary>
/// List of constance for error codes from genesis meter
/// </summary>
public static class LowLevelErrors
{
/// <summary>
/// everything is fine
/// </summary>
public const Int16 NoError = 0;
/// <summary>
/// Port is not open
/// </summary>
public const Int16 SerialPortClosed = -2;
/// <summary>
/// fails to write to serial port
/// </summary>
public const Int16 SerialPortWriteFailure = -3;
/// <summary>
/// fails to read from serial port
/// </summary>
public const Int16 SerialPortReadFailure = -4;
/// <summary>
/// Command is to long
/// </summary>
public const Int16 MaxDataLength = -5;
/// <summary>
/// deeper exception, check out log if this happen
/// </summary>
public const Int16 Exception = -6;
/// <summary>
/// wrong CRC
/// </summary>
public const Int16 CrcCalcError = -7;
/// <summary>
/// something strange
/// </summary>
public const Int16 Unknown = -8;
/// <summary>
/// Timeout occur
/// </summary>
public const Int16 Timeout = -9;
}
}
@@ -0,0 +1,48 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Consts
{
/// <summary>
/// Acknowledge feedback from meter after communication
/// </summary>
public enum RequestAcknowledgeState
{
/// <summary>
/// Acknowledge code for unassigned command
/// </summary>
CommandNotAssigned,
/// <summary>
/// Will be used initially as the record is not sent
/// </summary>
Unsent,
/// <summary>
/// Response missing
/// </summary>
NoResponse,
/// <summary>
/// Response command not match the required command
/// </summary>
CommandError,
/// <summary>
/// Lost connection (logged out from meter), a re-authorization is required
/// to access this command and/or register
/// </summary>
AuthorizationRequired,
/// <summary>
/// Meter error received, a retry may be useful
/// </summary>
MeterError,
/// <summary>
/// The response record couldn't be decoded
/// </summary>
DecodingError,
/// <summary>
/// The meter sent a wakeup message instead of the required data
/// </summary>
WakeupMessage,
/// <summary>
/// Valid meter response record
/// </summary>
Ok
}
}
@@ -0,0 +1,25 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.EventArguments
{
/// <inheritdoc />
/// <summary>
/// if session is gone and a re authorization is necessary
/// </summary>
public class AuthorizationRequiredEventArgs : EventArgs
{
/// <inheritdoc />
/// <summary>
/// Ctor
/// </summary>
/// <param name="command">this command will be resend after authorization</param>
public AuthorizationRequiredEventArgs(RequestRecord command = null)
{
Command = command;
}
/// <summary>
/// Command to resend
/// </summary>
public RequestRecord Command;
}
}
@@ -0,0 +1,59 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Exceptions
{
/// <summary>
/// Holds all errors can occur on the request protocol
/// </summary>
public class RequestProtocolException : Exception
{
/// <summary>
/// Exception occurred on this command
/// </summary>
public Object Command;
/// <summary>
/// Exception occurred while receiving this data
/// </summary>
public Object ReceivedData;
/// <summary>
/// Error is interpreted and has a define error code
/// if its not null is Genesis.Protocols.Request.Const.ConfigExErrors
/// </summary>
public Int32? ConfigExErrorCode;
/// <summary>
/// Error is interpreted and has a define error code
/// if its not null is Genesis.Protocols.Request.Const.HighLevelError
/// </summary>
public Int32? HighLevelErrorCode;
/// <summary>
/// Ctor with only message
/// </summary>
/// <param name="message">error message</param>
public RequestProtocolException(String message) : base(message)
{
}
/// <summary>
/// Ctor with message and <see cref="Command"/>
/// </summary>
/// <param name="message">error message</param>
/// <param name="command"><see cref="Command"/></param>
public RequestProtocolException(String message, Object command) : base(message)
{
Command = command;
ReceivedData = null;
}
/// <summary>
/// Ctor with message, <see cref="Command"/> and <see cref="ReceivedData"/>
/// </summary>
/// <param name="message">error message</param>
/// <param name="command"><see cref="Command"/></param>
/// <param name="receivedData"><see cref="ReceivedData"/> </param>
public RequestProtocolException(String message, Object command, Object receivedData) : base(message)
{
Command = command;
ReceivedData = receivedData;
}
}
}
@@ -0,0 +1,767 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using log4net;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.ProtocolCore;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Consts;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol
{
/// <summary>
/// a bidirectional protocol support read and write genesis meter registers
/// needed for
/// </summary>
public class RequestProtocol : BaseProtocol
{
//All read and write data sets (payload size) are organized in 4 byte chunks
//Format of RequestProtocol read data
//Request CMD | Register LSB | Register MSB | 0x00 | 0x00 | 0x00 | 0x00
//Response CMD | ErrCode Reason | ErrCode Base | DATA0 | DATA1 | DATA2 | DATA3
//Format of RequestProtocol write data
//Request CMD | Register LSB | Register MSB | DATA0 | DATA1 | DATA2 | DATA3
//Response CMD | ErrCode Reason | ErrCode Base | UNDEF0 | UNDEF1 | UNDEF2 | UNDEF3
//Format of RequestProtocol multiple read data
//Request CMD | Register LSB | Register MSB | RequestChunks LSB | RequestChunks MSB
//Response CMD | ErrCode Reason | ErrCode Base | ErrPosition LSB | ErrPosition MSB |
//DATA0 | DATA1 | DATA2 | DATA3.... (repeat for number of chunks)
//Format of RequestProtocol multiple read data
//Request CMD | Register LSB | Register MSB | WriteChunks LSB | WriteChunks MSB |
//DATA0 | DATA1 | DATA2 | DATA3.... (repeat for number of chunks)
//Response CMD | ErrCode Reason | ErrCode Base | ErrPosition LSB | ErrPosition MSB
//Indexes of the request protocol read/write data
//private const Int32 RequestCommandIndex = 0;
//private const Int32 RequestRegisterIndex = 1;
//private const Int32 RequestDataIndex = 3;
//Indexes for response protocol read/write data
private const Int32 ResponseCommandIndex = 0;
private const Int32 ResponseErrorCodeIndex = 1;
private const Int32 ResponseDataIndex = 3;
//Indexes of the request protocol multiple read/write data
//private const Int32 RequestChunksIndex = 3;
//private const Int32 RequestMultiDataIndex = 5;
//Indexes of the response protocol multiple read/write data
private const Int32 ResponseMultiErrorPosition = 3;
private const Int32 ResponseMultiDataIndex = 5;
//Indexes of error code
private const Int32 ErrorCodeReasonIndex = 0;
private const Int32 ErrorCodeBaseIndex = 1;
//Multiple response header size is 1 byte command 2 bytes error code and 2 bytes error position
private const Int32 ResponseMultiHeaderSize = 5;
//fill byte
private const Byte FillByte = 0x00;
private static readonly ILog _logger = LogManager.GetLogger(typeof(RequestProtocol));
// wakeup from register (water meter) to adapter
private static readonly Byte[] WakeupMessage = { 0x00, 0xFF, 0xFF };
/// <summary>
/// FIFO of records to be send next
/// </summary>
private readonly ConcurrentQueue<RequestRecord> _recordsSendFifo = new ConcurrentQueue<RequestRecord>();
/// <summary>
/// This is the actual record in the send loop
/// </summary>
private RequestRecord _recordInProcess;
/// <summary>
/// Last communication time for session refresh
/// </summary>
public DateTimeOffset? LastCommTime;
/// <inheritdoc />
public override event EventHandler<BasePortDataEventArgs> OnRecordReadyToSend;
/// <inheritdoc />
public override event EventHandler<BaseDataEventArgs> OnRecordIsDecoded;
/// <summary>
/// Occurs when a meter Response for write password is good
/// </summary>
public event EventHandler OnAuthorizationGrant;
/// <summary>
/// Event after a register entries changed
/// </summary>
public event EventHandler<RegisterUpdatedEventArgs> OnMeterRegisterUpdated;
/// <summary>
/// User adjustable additional retry timeout. This is 0 ms for standard operation.
/// </summary>
public Int32 AdditionalRetryTimeoutMs;
private Int32 _maxTimeOutMs;
private readonly String _ident;
/// <inheritdoc />
public RequestProtocol(String ident) : base(ident)
{
_ident = ident;
}
/// <summary>
/// Process all records needed to be sent, this routine has to be called
/// to kick-off the communication of all RequestRecords saved to the send
/// FIFO <see cref="AddRecordToSendFifo"/>
/// </summary>
/// <remarks date="2018-Dec-07" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2018-Dec-09" author="T.Wiedebusch">
/// - Logging of raw RequestProtocol at time of sending,
/// - Logging of retries.
/// </remarks>
/// <remarks date="2018-Dec-12" author="T.Wiedebusch">
/// - Added timeout from transmit protocol.
/// </remarks>
/// <remarks date="2019-Jan-02" author="Drabesch">
/// - Added reorder FIFO to bring login at first position
/// </remarks>
/// <remarks date="2019-Apr-11" author="T.Wiedebusch">
/// - Hide data in logging for e.g. passwords
/// </remarks>
/// <remarks date="2019-Jun-06" author="T.Wiedebusch">
/// - Retry counter reset if authorization required and this record will be put back into FIFO,
/// - Retry delay corrected for all errors.
/// </remarks>
/// <remarks date="2019-Jun-12" author="T.Wiedebusch">
/// - Dynamic retry delay: ResponseTimeoutMs * Retry counter.
/// </remarks>
/// <remarks date="2019-Jun-28" author="T.Wiedebusch">
/// - Skip retries on specific error mask to speed up communication on functional errors
/// or informational feed backs (e.g FW not installed 0x0004)
/// <see cref="RequestRecord.ResponseErrorCode"/>
/// </remarks>
/// <remarks date="2019-Aug-29" author="T.Wiedebusch">
/// - User adjustable additional retry timeout.
/// </remarks>
/// <remarks date="2020-Jan-17/18" author="T.Wiedebusch">
/// - Response timeout message output,
/// - Initialize acknowledge code before communication to NoResponse.
/// </remarks>
/// <remarks date="2020-Jan-22" author="T.Wiedebusch">
/// - Response timeout deviated from system time.
/// </remarks>
/// <remarks date="2021-Feb-02" author="T.Wiedebusch">
/// - Avoid enqueue of _recordInProcess if retry counter is 0.
/// </remarks>
/// <remarks date="2022-Nov-04" author="T.Wiedebusch">
/// - Additional DEBUG information included about FIFO and loops.
/// </remarks>
/// <remarks date="2022-Nov-07" author="T.Wiedebusch">
/// - Command not assigned response for recordInProcess == null.
/// </remarks>
/// <remarks date="2024-Apr-22" author="T.Wiedebusch">
/// - Initial request acknowledge state changed from NotDecoded to NoResponse.
/// </remarks>
public RequestAcknowledgeState ProcessRecordList()
{
_logger.Debug($"{_ident} Entry to ProcessRecordList, FIFO contains ({_recordsSendFifo.Count}) records");
//The inter record send delay has to be hold before trying to communicate again
const Int32 interRecordSendDelayMs = CommunicationConfig.InterRecordSendDelayMs;
//remind counter for FIFO and therefore execution loops
var loopCounter = 0;
//check if FIFO is empty and get the actual record to process out of it
while (_recordsSendFifo.TryDequeue(out var internalRecord))
{
// Starting with record number 1
loopCounter++;
_logger.Debug($"{_ident} Record({loopCounter}) - Dequeued from FIFO");
//pointer to new record
_recordInProcess = internalRecord;
if (_recordInProcess == null)
return RequestAcknowledgeState.CommandNotAssigned;
do
{
//mark record as answer outstanding
_recordInProcess.Acknowledge = RequestAcknowledgeState.NoResponse;
_logger.Debug($"{_ident} Record({loopCounter}) - Processing");
//log retries
if (_recordInProcess.RetryCtr > 0)
{
_logger.Debug($"{_ident} Retry({_recordInProcess.RetryCtr})");
_logger.Debug($"{_ident} Record({loopCounter}) - Retry({_recordInProcess.RetryCtr})");
}
//log request protocol content
_logger.Debug(_recordInProcess.HideDataInLog
? $"{_ident} SentData(*****)"
: $"{_ident} SentData({BitConverter.ToString(_recordInProcess.RequestProtocolData.ToArray())})");
//remind time for keep-session-active test to deny automatic logout of meter
LastCommTime = DateTimeOffset.UtcNow;
//set timeout for one communication trial adding the transmit protocol specific timeout and
//increase the timeout with each retry
_maxTimeOutMs = _recordInProcess.ResponseTimeoutMs + AdditionalRetryTimeoutMs +
CommunicationConfig.ResponseTimeoutMs * (_recordInProcess.RetryCtr + 1);
//start initial communication or retry
OnRecordReadyToSend?.Invoke(this, new ListBytePortDataEventArgs(_recordInProcess.EncodedRequestData));
//time reminder of request record
var requestTimeUtc = DateTimeOffset.UtcNow;
Int32 actualResponseWaitTimeMs;
//wait for communication acknowledge or until timeout, this also handles the inter record send delay
do
{
//minimum delay is the inter record send delay
Thread.Sleep(interRecordSendDelayMs);
var actualTimeUtc = DateTimeOffset.UtcNow;
//actual time difference from request to now
var timeSpan = actualTimeUtc - requestTimeUtc;
//avoid total milliseconds below zero at time overflow
if (timeSpan.TotalMilliseconds < 0)
{
requestTimeUtc = DateTimeOffset.UtcNow;
}
actualResponseWaitTimeMs = (Int32)timeSpan.TotalMilliseconds;
} while (_recordInProcess.Acknowledge != RequestAcknowledgeState.Ok &&
_recordInProcess.SkipRetryErrorCode != _recordInProcess.ResponseErrorCode &&
_maxTimeOutMs > actualResponseWaitTimeMs);
//if timeout value reaches zero, the response hasn't been received or the delay until
//next communication needed to be hold
if (_maxTimeOutMs <= actualResponseWaitTimeMs)
{
_logger.Error(_recordInProcess.Acknowledge == RequestAcknowledgeState.NoResponse
? $"{_ident} Response timeout({actualResponseWaitTimeMs}ms)"
: $"{_ident} Communication delay({actualResponseWaitTimeMs}ms)");
}
//skip loop to handle re-authorization
if (_recordInProcess.Acknowledge != RequestAcknowledgeState.AuthorizationRequired)
continue;
//reset retry counter for this record, it will be dispatched after re-authorization
_recordInProcess.RetryCtr = 0;
if (CommunicationConfig.RequestRetries > 0)
{
//add the recordInProcess to the top of the FIFO
_recordsSendFifo.Enqueue(_recordInProcess);
}
//call login
return _recordInProcess.Acknowledge;
} while (_recordInProcess.Acknowledge != RequestAcknowledgeState.Ok &&
_recordInProcess.RetryCtr++ < CommunicationConfig.RequestRetries &&
_recordInProcess.SkipRetryErrorCode != _recordInProcess.ResponseErrorCode);
}
return _recordInProcess.Acknowledge;
}
/// <summary>
/// Assemble record with transmit protocol and put it to record-send-FIFO,
/// backup the ready-to-send, which is the dataEncodedWithTransmitProtocol,
/// to the RequestRecord object for sending including the retry capability.
/// </summary>
/// <param name="payload">Data package with all details like CRC, etc</param>
/// <param name="cmd">Command identifier <see cref="Commands" /></param>
/// <param name="register"><see cref="RegisterDefinition" /> which is intend</param>
/// <param name="hideDataInLog">hiding data in log file to avoid spying of passwords</param>
/// <param name="skipRetryErrorCode">error mask to skip retries</param>
/// <returns>the assembled record for the send FIFO</returns>
/// <remarks date="2018-Dec-08" author="T.Wiedebusch">
/// - Initial
/// </remarks>
/// <remarks date="2018-Dec-09" author="T.Wiedebusch">
/// - Logging of raw data (RequestProtocol) moved to ProcessRecordList.
/// </remarks>
/// <remarks date="2018-Dec-12" author="T.Wiedebusch">
/// - Added timeout from transmit protocol.
/// </remarks>
/// <remarks date="2019-Apr-11" author="T.Wiedebusch">
/// - Hide data in logging for e.g. passwords
/// </remarks>
/// <remarks date="2019-Jun-28" author="T.Wiedebusch">
/// - Skip retries on specific error mask to speed up communication on functional errors
/// or informational feed backs (e.g FW not installed 0x0004)
/// <see cref="RequestRecord.ResponseErrorCode"/>
/// </remarks>
/// <remarks date="2020-Jan-18" author="T.Wiedebusch">
/// - Initial skip retry error code set to 0x0004 (e.g FW not installed 0x0004).
/// </remarks>
/// <remarks date="2023-Mai-16" author="T.Wiedebusch">
/// - Hide data in log forwarded to DecodeDateForPhysicalLayer to hide passwords in log files.
/// </remarks>
private RequestRecord AddRecordToSendFifo(Byte cmd, Byte[] payload, RegisterDefinition register = null,
Boolean hideDataInLog = false, UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode)
{
var requestProtocolData = new List<Byte> { cmd };
requestProtocolData.AddRange(payload);
//encode request protocol data with transmit protocol
var transmit = GetTransmitProtocol();
var dataEncodedWithTransmitProtocol = transmit.DecodeDataForPhysicalLayer(_ident, cmd, payload, hideDataInLog);
//remind port specific response timeout
var transmitPortSettings = transmit.GetTransmitPortSettings();
var recordForSendFifo = new RequestRecord(cmd, requestProtocolData, dataEncodedWithTransmitProtocol,
transmitPortSettings.ResponseTimeoutMs, register, hideDataInLog, skipRetryErrorCode);
_recordsSendFifo.Enqueue(recordForSendFifo);
return recordForSendFifo;
}
/// <summary>
/// Record dispatcher to send FIFO of UI1236 command
/// </summary>
/// <remarks date="2022-Aug-24" author="R.Drabesch">
/// - Initial
/// </remarks>
/// <param name="ident">string for logging of slot</param>
/// <param name="payload">Data package with all details like CRC, etc</param>
/// <param name="register"><see cref="RegisterDefinition" /> which is intend</param>
/// <param name="hideDataInLog">hiding data in log file to avoid spying of passwords</param>
/// <param name="skipRetryErrorCode">error mask to skip retries</param>
/// <returns>the assembled record for the send FIFO</returns>
// ReSharper disable once InconsistentNaming UI1236 is a naming forced by the caller
public RequestRecord AddRecordToSendFifoUI1236(String ident, Byte[] payload, RegisterDefinition register = null,
Boolean hideDataInLog = false, UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode)
{
const Byte maxValue = byte.MaxValue;
var requestProtocolData = new List<Byte>
{
maxValue
};
requestProtocolData.AddRange(payload);
var transmitProtocol = GetTransmitProtocol();
var encodedRequestData = transmitProtocol.DecodeDataForPhysicalLayerUI1236(ident, payload, hideDataInLog);
var transmitPortSettings = transmitProtocol.GetTransmitPortSettings();
var sendFifoUi1236 = new RequestRecord(maxValue, requestProtocolData, encodedRequestData,
transmitPortSettings.ResponseTimeoutMs, register, hideDataInLog, skipRetryErrorCode);
_recordsSendFifo.Enqueue(sendFifoUi1236);
return sendFifoUi1236;
}
/// <summary>
/// Called after successful response.
/// Decode and check record, handle Errors and dispatch result.
/// Invokes <see cref="OnRecordIsDecoded" />if somebody is listening
/// </summary>
/// <remarks date="2018-Mar-15" author="R.Drahbesch">
/// - Initial
/// </remarks>
/// <remarks date="2018-Dec-08" author="T.Wiedebusch">
/// - First part reworked to extract the response protocol information
/// </remarks>
/// <remarks date="2019-Apr-11" author="T.Wiedebusch">
/// - Hide data in logging for e.g. passwords
/// </remarks>
/// <remarks date="2019-Jun-28" author="T.Wiedebusch">
/// - Error code extraction changed for <see cref="RequestRecord.SkipRetryErrorCode"/>
/// </remarks>
/// <remarks date="2019-Dec-12/16" author="T.Wiedebusch">
/// - Wakeup-message will avoid further timeout (during FW update this is in the range
/// of 15000ms).
/// - Allow one additional retry on decoding error, which is often the wakeup-message.
/// </remarks>
/// <remarks date="2019-Jan-17" author="T.Wiedebusch">
/// - Send time reminder for timeout time calculation as output in log-file,
/// - OnRecordIsDecoded?.Invoke moved before return to assure that the Acknowledge status is set.
/// </remarks>
/// <remarks date="2019-Jan-22" author="T.Wiedebusch">
/// - Avoid activation of wakeup retry if record is meanwhile acknowledged.
/// </remarks>
/// <remarks date="2022-Jul-19" author="T.Wiedebusch">
/// - Wakeup message handling changed,
/// - Multiple replies on wakeup message allowed.
/// </remarks>
/// <remarks date="2022-Oct-02" author="T.Wiedebusch">
/// - On wakeup message 5 retires are allowed to avoid an infinite loop.
/// </remarks>
/// <remarks date="2022-Oct-04" author="T.Wiedebusch">
/// - On wakeup message exit this routine.
/// </remarks>
/// <remarks date="2023-Feb-13" author="T.Wiedebusch">
/// - Early exit on _recordInProcess == null,
/// - Wakeup message retries from 5 to 2,
/// - Removed error base from error code decision as error base is only the AppId
/// </remarks>
/// <remarks date="2023-Sep-01" author="T.Wiedebusch">
/// - HideDataInLog.
/// </remarks>
/// <remarks date="2024-Jan-24" author="T.Wiedebusch">
/// - Ignore wakeup if message already acknowledged (avoid to set
/// "_recordInProcess.Acknowledge = RequestAcknowledgeState.NotDecoded"),
/// - Avoid to overwrite "_recordInProcess.Acknowledge = RequestAcknowledgeState.Acknowledge" with
/// "RequestAcknowledgeState.WakeupMessage".
/// </remarks>
protected override void DecodeRecord(IPortDataEventArgs data)
{
if (_recordInProcess == null)
return;
//the request response record covers the entire request protocol
var responseRecord = GetTransmitProtocol().DecodeDataForLogicLayer(_ident, (List<Byte>)data.GetData(),
_recordInProcess.HideDataInLog);
//on protocol decoding failure
if (responseRecord.Count == 0)
{
_recordInProcess.Acknowledge = RequestAcknowledgeState.DecodingError;
_logger.Debug($"{_ident} Message decoding error.");
//reset timeout to a normal value if timeout is extremely high but response received
if (_maxTimeOutMs > CommunicationConfig.BusyTimeoutMs)
_maxTimeOutMs = CommunicationConfig.BusyTimeoutMs;
return;
}
if (responseRecord.Count == WakeupMessage.Length)
{
// check for wakeup message
var wakeUp = true;
for (var i = 0; i < WakeupMessage.Length; i++)
{
if (responseRecord[i] != WakeupMessage[i])
wakeUp = false;
}
if (wakeUp)
{
//avoid wakeup retry on acknowledged record
if (RequestAcknowledgeState.Ok != _recordInProcess.Acknowledge)
_recordInProcess.Acknowledge = RequestAcknowledgeState.WakeupMessage;
//initiate a single retry on wakeup message response
if (_recordInProcess.WakeupMessageRetryCtr < 2)
{
_recordInProcess.WakeupMessageRetryCtr++;
_logger.Debug(
$"{_ident} Wakeup message({_recordInProcess.WakeupMessageRetryCtr}) received");
}
}
else
{
//if it is not identified as valid wakeup message it is something unknown
_recordInProcess.Acknowledge = RequestAcknowledgeState.DecodingError;
_logger.Debug($"{_ident} Message decoding error.");
}
// reset retry counter to get all required retries
_recordInProcess.RetryCtr = -1;
//reset timeout to a normal value if timeout is extremely high but response received
if (_maxTimeOutMs > CommunicationConfig.BusyTimeoutMs)
_maxTimeOutMs = CommunicationConfig.BusyTimeoutMs;
return;
}
//log request protocol content
_logger.Debug(_recordInProcess.HideDataInLog
? $"{_ident} DecodedRecord(*****)"
: $"{_ident} DecodedRecord({BitConverter.ToString(responseRecord.ToArray())})");
//extract information command
var replyCmd = responseRecord[ResponseCommandIndex];
//extract position of error for multiple access
if (replyCmd == Commands.MultipleReadDataReply || replyCmd == Commands.MultipleWriteDataReply)
{
_recordInProcess.ResponseErrorPosition =
(UInt16)((responseRecord[ResponseMultiErrorPosition] & 0x00FF) |
(UInt16)((responseRecord[ResponseMultiErrorPosition + 1] << 8) & 0xFF00));
}
//extract the error codes from response record to _record in process
_recordInProcess.ResponseErrorBase = responseRecord[ResponseErrorCodeIndex + ErrorCodeBaseIndex];
_recordInProcess.ResponseErrorReason = responseRecord[ResponseErrorCodeIndex + ErrorCodeReasonIndex];
//combine error base and error reason
_recordInProcess.ResponseErrorCode = (UInt16)(_recordInProcess.ResponseErrorReason |
(_recordInProcess.ResponseErrorBase << 8));
_recordInProcess.ResponsePayload = new List<Byte>();
switch (replyCmd)
{
case Commands.MultipleReadDataReply:
_recordInProcess.ResponsePayload.AddRange(
responseRecord.GetRange(ResponseMultiDataIndex,
responseRecord.Count - ResponseMultiHeaderSize));
break;
case Commands.ReadDataReply:
_recordInProcess.ResponsePayload.AddRange(responseRecord.GetRange(ResponseDataIndex,
RegisterDefinition.ChunkSize));
break;
}
//Deny access if reply does not match
if (_recordInProcess.ResponseCommand != replyCmd)
{
_logger.Fatal($"{_ident} Wrong command received({replyCmd:X2})," +
$" expected command({_recordInProcess.ResponseCommand:X2})");
_recordInProcess.Acknowledge = RequestAcknowledgeState.CommandError;
OnRecordIsDecoded?.Invoke(this,
new RequestResponseDataEventArgs { RequestResponseData = responseRecord });
return;
}
//examine error code, if base (AppId) is not 0 the reason 0 may be an error!!!!
if (/*_recordInProcess.ResponseErrorBase != 0 ||*/ _recordInProcess.ResponseErrorReason != 0)
{
_logger.Warn($"{_ident} Error code received(0x{_recordInProcess.ResponseErrorBase:X2}" +
$"{_recordInProcess.ResponseErrorReason:X2})");
_recordInProcess.Acknowledge = RequestAcknowledgeState.MeterError;
CheckErrorCode();
OnRecordIsDecoded?.Invoke(this,
new RequestResponseDataEventArgs { RequestResponseData = responseRecord });
return;
}
//register password acknowledge
if (_recordInProcess.Register.GetIdent() == Register.Configexchange.Password)
{
OnAuthorizationGrant?.Invoke(null, null);
}
//dispatch data
if (_recordInProcess.ResponsePayload != null)
{
OnMeterRegisterUpdated?.Invoke(this, new RegisterUpdatedEventArgs(_recordInProcess.Register,
_recordInProcess.ResponsePayload.ToArray()));
}
_recordInProcess.Acknowledge = RequestAcknowledgeState.Ok;
OnRecordIsDecoded?.Invoke(this,
new RequestResponseDataEventArgs { RequestResponseData = responseRecord });
}
/// <summary>
/// Method to send basic Commands to Meter. Creates an integer of 4 bytes for the payload.
/// Supported Commands are: <see cref="Commands.ReadData" />,<see cref="Commands.MultipleReadData" />,
/// <see cref="Commands.WriteData" />, <see cref="Commands.MultipleWriteData" /> and <see cref="Commands.QueryCaps" />.
/// Calculate CRCs and Command length and check if command is valid.
/// Use <see cref="AddRecordToSendFifo" /> to push data to Port/Meter.
/// </summary>
/// <param name="command">
/// Supported <see cref="Commands.ReadData" />,<see cref="Commands.MultipleReadData" />,
/// <see cref="Commands.WriteData" />, <see cref="Commands.MultipleWriteData" /> and <see cref="Commands.QueryCaps" />.
/// </param>
/// <param name="meterRegister">Register to Read or Write, null for <see cref="Commands.QueryCaps" /></param>
/// <param name="payload">
/// Data to push into the <see cref="RegisterDefinition" />.
/// must be null on Read Commands ( <see cref="Commands.ReadData" />,<see cref="Commands.MultipleReadData" />)
/// and not null on WriteData (<see cref="Commands.WriteData" /> and <see cref="Commands.MultipleWriteData" />)
/// </param>
/// <param name="expectedLength"></param>
/// <param name="hideDataInLog">hiding data in log file to avoid spying of passwords</param>
/// <param name="skipRetryErrorCode">mask to skip reties on error</param>
/// <returns>an new command just send to port/meter </returns>
/// <remarks date="2018-Mar-15" author="R.Drahbesch">
/// - Initial
/// </remarks>
/// <remarks date="2018-Dec-08" author="T.Wiedebusch">
/// - Reworked to zero pad payload with chunks of 4 bytes, the caller needn't take care of the size
/// </remarks>
/// <remarks date="2018-Dec-09" author="T.Wiedebusch">
/// - Corrected payload content in request protocol
/// </remarks>
/// <remarks date="2019-Apr-11" author="T.Wiedebusch">
/// - Hide data in logging for e.g. passwords
/// </remarks>
/// <remarks date="2019-Jun-28" author="T.Wiedebusch">
/// - Skip retries on specific error mask to speed up communication on functional errors
/// or informational feed backs (e.g FW not installed 0x0004)
/// <see cref="RequestRecord.ResponseErrorCode"/>
/// </remarks>
/// <remarks date="2019-Aug-28" author="R.Drabesch">
/// - Multiple read for string split to simple reads.
/// </remarks>
/// <remarks date="2019-Jan-18" author="T.Wiedebusch">
/// - Default <see cref="CommunicationConfig.SkipRetryErrorCode"/> set.
/// </remarks>
/// <remarks date="2023-Sep-06" author="T.Wiedebusch">
/// - MultipleReadData based on data size.
/// </remarks>
/// <remarks date="2023-Nov-24" author="T.Wiedebusch">
/// - Exit multiple read date if retries exceeded.
/// </remarks>
/// <remarks date="2024-Apr-11" author="T.Wiedebusch">
/// - Exit multiple read date if retries exceeded increased to <see cref="CommunicationConfig.MaxRequestRetries"/>.
/// </remarks>
/// <remarks date="2024-Apr-11 Version 2" author="T.Wiedebusch">
/// - Rewound to version from 06.09.2023 14:44:33 before Commit 8c9d7704.
/// </remarks>
/// <remarks date="2024-Apr-15" author="T.Wiedebusch">
/// - Removed useless too short comments as every string is going to be delimited by a 0 and usually won't match
/// to a 4 byte chunk.
/// </remarks>
/// <remarks date="2026-Jan-06" author="T.Wiedebusch">
/// - Removed redundant 'return cRecord'.
/// </remarks>
public RequestRecord CommandToMeter(Byte command, RegisterDefinition meterRegister = null,
Byte[] payload = null, Int32? expectedLength = null, Boolean hideDataInLog = false,
UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode)
{
if (command == Commands.MultipleReadData && meterRegister != null && meterRegister.DataType == typeof(String))
{
var completeResponse = new List<Byte>();
while (true)
{
var cRecord = CommandToMeter(Commands.ReadData, meterRegister, payload, expectedLength,
hideDataInLog, skipRetryErrorCode);
var rest = ProcessRecordList();
if (rest == RequestAcknowledgeState.Ok && cRecord?.ResponsePayload != null)
{
completeResponse.AddRange(cRecord.ResponsePayload);
if (!cRecord.ResponsePayload.Contains(0)) continue;
cRecord.ResponsePayload = completeResponse;
OnMeterRegisterUpdated?.Invoke(this,
new RegisterUpdatedEventArgs(meterRegister, completeResponse.ToArray()));
}
return cRecord;
}
}
//the minimum payload is one 4 byte chunk
var chunkSize = RegisterDefinition.ChunkSize;
if (payload == null)
{
payload = new Byte[chunkSize];
for (var i = 0; i < chunkSize; i++)
{
payload[i] = FillByte;
}
}
//real payload based on 4 byte chunks
var requestPayload = new List<Byte>();
requestPayload.AddRange(payload);
//fill-byte padding
if (payload.Length % chunkSize != 0)
{
for (var i = 0; i < chunkSize - payload.Length % chunkSize; i++)
{
requestPayload.Add(FillByte);
}
}
//request protocol excluding the command
var requestProtocol = new List<Byte>();
if (meterRegister != null)
{
requestProtocol.Add(meterRegister.RegisterAddress[1]);
requestProtocol.Add(meterRegister.RegisterAddress[0]);
if (command == Commands.MultipleWriteData)
{
//set counter to inform meter writes are expected
//the payload size is already padded to 4 byte chunks
var numberOfChunks = (UInt16)(requestPayload.Count / chunkSize);
requestProtocol.Add((Byte)(numberOfChunks & 0xFF));
requestProtocol.Add((Byte)((numberOfChunks >> 8) & 0xFF));
}
if (command == Commands.MultipleReadData)
{
//TODO THW create expected size out of meterRegister.DataType
if (!expectedLength.HasValue)
{
if ((meterRegister.DataType == typeof(UInt64) || meterRegister.DataType == typeof(Int64)))
{
expectedLength = 8;
}
else
{
expectedLength = 24;
}
}
var numberOfChunks = (UInt16)(expectedLength.Value / chunkSize);
if (expectedLength % chunkSize != 0)
numberOfChunks++;
requestProtocol.Add((Byte)(numberOfChunks & 0xFF));
requestProtocol.Add((Byte)((numberOfChunks >> 8) & 0xFF));
}
//add always the payload to the request protocol
requestProtocol.AddRange(requestPayload);
}
return AddRecordToSendFifo(command, requestProtocol.ToArray(), meterRegister,
hideDataInLog, skipRetryErrorCode);
}
/// <summary>
/// Analyzes the error code
/// </summary>
private void CheckErrorCode()
{
if (_recordInProcess.ResponseErrorBase == (Byte)ConfigExErrors.Base)
{
if (_recordInProcess.ResponseErrorReason == (Byte)ConfigExErrors.AuthenticationFail ||
_recordInProcess.ResponseErrorReason == (Byte)ConfigExErrors.AccessDenied ||
_recordInProcess.ResponseErrorReason == (Byte)ConfigExErrors.LockedOut)
{
_recordInProcess.Acknowledge = RequestAcknowledgeState.AuthorizationRequired;
}
}
}
/// <summary>
/// Reorders the record list so that first entry is the defined topRegister
/// </summary>
/// <param name="topRegister"></param>
public void ReorderRecordList(String topRegister)
{
_recordsSendFifo.Enqueue(_recordInProcess);
//reorder FIFO to bring login at first position
for (var i = 0; i < _recordsSendFifo.Count; i++)
{
_recordsSendFifo.TryPeek(out var tmpRecord);
if (tmpRecord == null || tmpRecord.Register.GetIdent() == topRegister)
{
break;
}
_recordsSendFifo.TryDequeue(out tmpRecord);
_recordsSendFifo.Enqueue(tmpRecord);
}
}
/// <summary>
/// Checks the active register to access
/// </summary>
/// <param name="registerName"></param>
/// <returns></returns>
public Boolean ContainsRegisterIdent(String registerName)
{
return _recordsSendFifo.Any(a => string.Equals(a.Register.GetIdent(),
registerName, StringComparison.CurrentCultureIgnoreCase));
}
}
}
@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol.Consts;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers;
using CommunicationConfig = TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.GenesisConfig.CommunicationConfig;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.RequestProtocol
{
/// <summary>
/// Holds request commands with detail parameters to see processing state
/// </summary>
public class RequestRecord
{
/// <summary>
/// Indicates the base command <see cref="Commands" />
/// </summary>
public readonly Byte ResponseCommand;
/// <summary>
/// the register the command refers to.
/// needed to set Register dictionary to link response with dictionary key
/// </summary>
public readonly RegisterDefinition Register;
/// <summary>
/// Command acknowledged
/// </summary>
public RequestAcknowledgeState Acknowledge = RequestAcknowledgeState.Unsent;
/// <summary>
/// Retry counter
/// </summary>
public Int32 RetryCtr = 0;
/// <summary>
/// Wakeup-message retry counter
/// </summary>
public Int32 WakeupMessageRetryCtr = 0;
/// <summary>
/// indicates error base on <see cref="RequestRecord"/>
/// </summary>
public Byte ResponseErrorBase = 0xFF;
/// <summary>
/// indicates error reason on <see cref="RequestRecord"/>
/// </summary>
public Byte ResponseErrorReason = 0xFF;
/// <summary>
/// Combined error code of base and reason
/// </summary>
public UInt16 ResponseErrorCode = 0xFFFF;
/// <summary>
/// Chunk position of error on multiple read/write access
/// </summary>
public UInt16 ResponseErrorPosition = 0;
/// <summary>
/// Data containing the request protocol
/// </summary>
public readonly List<Byte> RequestProtocolData;
/// <summary>
/// Encoded with transmit protocol, ready to stream to port as is
/// </summary>
public List<Byte> EncodedRequestData { get; }
/// <summary>
/// Extracted payload of response
/// </summary>
public List<Byte> ResponsePayload;
/// <summary>
/// Extracted payload of response
/// </summary>
public readonly Int32 ResponseTimeoutMs;
/// <summary>
/// avoid logging for e.g. password
/// </summary>
public readonly Boolean HideDataInLog;
/// <summary>
/// error mask to skip retries for functional errors <see cref="ResponseErrorCode"/>
/// </summary>
public readonly UInt16 SkipRetryErrorCode;
/// <summary>
/// Ctor for an base command <see cref="Commands" />
/// </summary>
/// <param name="command"><see cref="Commands" /> as an byte</param>
/// <param name="requestProtocolData">Request protocol data for logging</param>
/// <param name="encodedRequestData"> Ready to send data encoded with transmit protocol retries</param>
/// <param name="responseTimeoutMs">Timeout for response</param>
/// <param name="register"><see cref="RegisterDefinition"/> to do command with</param>
/// <param name="hideDataInLog">hiding data in log file to avoid spying of passwords</param>
/// <param name="skipRetryErrorCode">error mask to skip retries <see cref="ResponseErrorCode"/></param>
public RequestRecord(Byte command, List<Byte> requestProtocolData = null, List<Byte> encodedRequestData = null,
Int32 responseTimeoutMs = 100, RegisterDefinition register = null, Boolean hideDataInLog = false,
UInt16 skipRetryErrorCode = CommunicationConfig.SkipRetryErrorCode)
{
ResponseCommand = (Byte)(command + 1);
Register = register;
RequestProtocolData = requestProtocolData;
EncodedRequestData = encodedRequestData;
ResponseTimeoutMs = responseTimeoutMs;
HideDataInLog = hideDataInLog;
SkipRetryErrorCode = skipRetryErrorCode;
}
}
}
@@ -0,0 +1,547 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.MeasurementRecords;
// ReSharper disable UnusedMember.Local
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.StreamingProtocol
{
/// <summary>
/// Data fields and definitions for GENESIS streaming protocol
/// </summary>
public class StreamingDecoder
{
private const Double MilliLitersToCmFactor = 1.0E-6;
private const Double CpuTimeToSecondsFactor = 1.0 / 0x10000;
public const Double CpuTimeOverflowS = 0x100000000 * CpuTimeToSecondsFactor;
private const Double LitersPerSecondToCmPerHourFactor = 3600.0 / 1000.0;
private const Double DefaultVolumeScaleRawPerMl = 1024.0;
private const Double DefaultVolumeFactorRawToCm = MilliLitersToCmFactor / DefaultVolumeScaleRawPerMl;
private const Double MaxGenesisAccuVolumeRaw = UInt32.MaxValue; //0x100000000; //2^32
public const Double DefaultAccuDutOverflowVolumeCm = MaxGenesisAccuVolumeRaw * DefaultVolumeFactorRawToCm;
private const Double DisplayMlSetupDutOverflowVolumeCm = 1000.0; //overflow of LCD if set to ml
private const Double TofToSecondsFactor38Bit = 1.0 / 0x4000000000; // 2^38
private const Double AmplitudeToVoltFactor = 1.0 / 0x400000 / 1000.0; // 2^22 100 0000 0000 0000 0000 0000b
private const Double PulseWidthToRelFactor = 1.0 / 0x100; // 2^8
/// <summary>
/// Default data for bend detection tests of Genesis
/// </summary>
private readonly BendDetectionRecord _bendDetectionDefault = new BendDetectionRecord
{
StatusBendU0 = BendDetectionRecord.StatusBendU0Enum.OKAY,
InstallationType = BendDetectionRecord.InstallationTypeEnum.INSTALLATION_UNDISTURBED,
CorrectionFactor_percent = 0.0,
PreCorrectionVolumeRaw = 0.0,
PostCorrectionVolumeRaw = 0.0,
TimeS = 0.0,
OverflowTimeS = CpuTimeOverflowS,
Crc = 0xFFFF,
IsValid = false
};
/// <summary>
/// Default data for flow tests of Genesis
/// </summary>
private readonly FlowTestRecord _dataDefault = new FlowTestRecord
{
VolumeCm = 0.0,
OverflowVolumeCm = DisplayMlSetupDutOverflowVolumeCm,
TimeS = 0.0,
OverflowTimeS = CpuTimeOverflowS,
Crc = 0xFFFF,
IsValid = false
};
/// <summary>
/// Default data for calibration of Genesis
/// </summary>
private readonly CalibrationRecord _rawDataDefault = new CalibrationRecord
{
Channel = 0,
Validation = 0xFFFF,
TotalTimeOfFlightS = 0.0,
DeltaTimeOfFlightS = 0.0,
VolumeScaleRawPerMl = DefaultVolumeScaleRawPerMl,
VolumeFactorRawToQm = DefaultVolumeFactorRawToCm,
DeltaVolumeRaw = 0.0,
DeltaVolumeQm = 0.0,
AccuVolumeRaw = 0.0,
VolumeCm = 0.0,
OverflowVolumeCm = DefaultAccuDutOverflowVolumeCm,
SampleIntervalS = 0.0,
AmplitudeUpV = 0.0,
AmplitudeDownV = 0.0,
PulseWidthRatioUp = 0.0,
PulseWidthRatioDown = 0.0,
TemperatureRaw = 20.0,
TemperaturePowFactor = 1.0,
TemperatureDegC = 20.0,
TimeS = 0.0,
OverflowTimeS = CpuTimeOverflowS,
Crc = 0xFFFF,
IsValid = false
};
private CalibrationRecord _dataCalibRec;
private FlowTestRecord _dataFlowTestRec;
private BendDetectionRecord _dataBendDetectRec;
private readonly Boolean _ignoreCorruptedData;
/// <summary>
/// Constructor initializes all decoded members with default values
/// </summary>
public StreamingDecoder(Boolean ignoreCorruptedData = true)
{
_dataFlowTestRec = _dataDefault;
_dataCalibRec = _rawDataDefault;
_dataBendDetectRec = _bendDetectionDefault;
_ignoreCorruptedData = ignoreCorruptedData;
}
/// <summary>
/// Calibration data
/// </summary>
public CalibrationRecord DataCalib
{
get; private set;
}
/// <summary>
/// Flow test data
/// </summary>
public FlowTestRecord DataFlowTest
{
get; private set;
}
/// <summary>
/// Bend detection test data
/// </summary>
public BendDetectionRecord DataBendDetectTest
{
get; private set;
}
/// <summary>
/// Decoding the raw message
/// </summary>
/// <param name="rawMsg">message received as one line delimited with LF</param>
/// <returns>true if decoding was successful and data has been validated</returns>
/// <remarks date="2023-Mar-09" author="T.Wiedebusch">
/// - Modified using common CRC check before branching to the protocol specific decoder.
/// </remarks>
/// <remarks date="2023-Dec-06" author="T.Wiedebusch">
/// - Introduced protocol 'm' for bending detection.
/// </remarks>
public Boolean DecodeMsg(String rawMsg)
{
var rawRecordIsValid = false;
try
{
// save the raw message for CRC calculation before separation to fields
//_rawMsgForCrc = rawMsg;
// extract message and split it to fields
//DN50
//2022-07-21 07:22:06.9871 | @f 8497D 062E4216 9B2A
//2022-07-21 07:22:06.9871 | @h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331
//2022-07-21 07:22:07.0171 | @h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD
//rawMsg = "@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E";
//DN80
//2022-04-28 15:19:54.9167 | @f AA754B 4D0CEE78 5D89
//2022-04-28 15:19:54.9337 | @h 1 0 0EF4A130 0002AAB8 000DAC8C 02B98FBD 00000200 00000FFC 643BCF30 63C9CFF6 00015096 0C 4D0CF3CC 3E87
//2022-04-28 15:19:54.9497 | @h 2 0 0EFA3000 000283E7 000CE580 E3D5EFFA 00000200 00001000 6DC0A84E 6CD462C2 00015096 0C 4D0CF922 1646
//2022-04-28 15:19:54.9627 | @h 3 0 0EFF7F91 0002AC39 000DB43D FE1C0254 00000200 00000FFE 6C932DE6 6D20005D 00015096 0C 4D0CFE76 868A
//2022-04-28 15:19:54.9787 | @f AA7C01 4D0CFE76 B08F
// rawMsg = "@h 3 0 0EFF7F91 0002AC39 000DB43D FE1C0254 00000200 00000FFE 6C932DE6 6D20005D 00015096 0C 4D0CFE76 868A ";
// The received message is a string with a line delimiter.
rawMsg = rawMsg.Replace('\n', ' ');
// The raw message fields are the separated values from the received string with a blank as field separator
var rawMsgFields = rawMsg.Split(' ');
// The last element is the CRC, the CRC can be separated, calculated and validated before trying to decode the content
var rawRecordForCrc = "";
// get all fields excluding the CRC (length - 1)
for (var x = 0; x < rawMsgFields.Length - 1; x++)
{
rawRecordForCrc += rawMsgFields[x];
// add the field delimiter from raw data
rawRecordForCrc += " ";
}
// extract bytes of raw message for CRC calculation each character, CRC field is already removed
var byteArraySize = rawRecordForCrc.Length;
var byteArray = new Byte[byteArraySize];
for (var i = 0; i < byteArraySize; i++)
{
byteArray[i] = (Byte)rawRecordForCrc[i];
}
// calculate the CRC from the received data
var calculatedCrc = Crc16Ccitt.CalculateMsb1021(byteArray);
// extract received CRC
var receivedCrc = UInt16.Parse(rawMsgFields[rawMsgFields.Length - 1], NumberStyles.HexNumber);
// compare received with calculated CRC and remind valid decoding
rawRecordIsValid = calculatedCrc == receivedCrc;
switch (rawMsgFields[0])
{
case "@m":
_dataBendDetectRec.IsValid = rawRecordIsValid;
if (rawRecordIsValid || !_ignoreCorruptedData)
{
DecodeProtocolM(ref _dataBendDetectRec, rawMsgFields);
DataBendDetectTest = _dataBendDetectRec;
}
break;
case "@f":
_dataFlowTestRec.IsValid = rawRecordIsValid;
if (rawRecordIsValid || !_ignoreCorruptedData)
{
DecodeProtocolF(ref _dataFlowTestRec, rawMsgFields);
DataFlowTest = _dataFlowTestRec;
}
break;
case "@g":
_dataCalibRec.IsValid = rawRecordIsValid;
if (rawRecordIsValid || !_ignoreCorruptedData)
{
DecodeProtocolG(ref _dataCalibRec, rawMsgFields);
DataCalib = _dataCalibRec;
}
break;
case "@h":
_dataCalibRec.IsValid = rawRecordIsValid;
if (rawRecordIsValid || !_ignoreCorruptedData)
{
DecodeProtocolH(ref _dataCalibRec, rawMsgFields);
DataCalib = _dataCalibRec;
}
break;
}
}
catch (Exception)
{
// ignored
}
return rawRecordIsValid;
}
/// <summary>
/// Extracting message from string fields for protocol 'm'
/// </summary>
/// <param name="dataBendTestRec">reference to bend detection test record</param>
/// <param name="fields">Separated fields containing the measurement as string</param>
/// <returns></returns>
/// <remarks date="2023-Mar-09" author="T.Wiedebusch">
/// - Modified using common CRC check in advance.
/// </remarks>
private static void DecodeProtocolM(ref BendDetectionRecord dataBendTestRec, IList<String> fields)
{
// time stamp attachment
dataBendTestRec.DecodedTime = DateTimeOffset.UtcNow;
// extract received CRC
dataBendTestRec.Crc = ushort.Parse(fields[(Int32)ProtMsubString.Crc],
NumberStyles.HexNumber);
// extract the status
dataBendTestRec.StatusBendU0 =
(BendDetectionRecord.StatusBendU0Enum)UInt32.Parse(fields[(Int32)ProtMsubString.Status],
NumberStyles.AllowHexSpecifier);
// extract the installation type
dataBendTestRec.InstallationType =
(BendDetectionRecord.InstallationTypeEnum)UInt32.Parse(fields[(Int32)ProtMsubString.Installation],
NumberStyles.AllowHexSpecifier);
// extract the correction factor in percent
dataBendTestRec.CorrectionFactor_percent =
UInt32.Parse(fields[(Int32)ProtMsubString.Factor],
NumberStyles.AllowHexSpecifier) * BendDetectionRecord.CorrectionFactorScale;
// build result values, the volume before and after correction can be positive or negative!
dataBendTestRec.PreCorrectionVolumeRaw = Int32.Parse(fields[(Int32)ProtMsubString.PreVolume],
NumberStyles.AllowHexSpecifier);
// build result values, the volume before and after correction can be positive or negative!
dataBendTestRec.PostCorrectionVolumeRaw = Int32.Parse(fields[(Int32)ProtMsubString.PostVolume],
NumberStyles.AllowHexSpecifier);
}
/// <summary>
/// Extracting message from string fields for protocol 'f'
/// </summary>
/// <param name="dataFlowTestRec">reference to flow test record</param>
/// <param name="fields">Separated fields containing the measurement as string</param>
/// <returns></returns>
/// <remarks date="2023-Mar-09" author="T.Wiedebusch">
/// - Modified using common CRC check in advance.
/// </remarks>
private static void DecodeProtocolF(ref FlowTestRecord dataFlowTestRec, IList<String> fields)
{
// time stamp attachment
dataFlowTestRec.DecodedTime = DateTimeOffset.UtcNow;
// extract received CRC
dataFlowTestRec.Crc = ushort.Parse(fields[(Int32)ProtFsubString.Crc],
NumberStyles.HexNumber);
// build result values, the display volume can be positive or negative!
dataFlowTestRec.VolumeCm = int.Parse(fields[(Int32)ProtFsubString.DisplayVolume],
NumberStyles.AllowHexSpecifier) * MilliLitersToCmFactor;
dataFlowTestRec.TimeS = uint.Parse(fields[(Int32)ProtFsubString.CpuTime],
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
}
/// <summary>
/// Extracting message from string fields to individual raw channel for protocol 'g'
/// </summary>
/// <param name="dataProtGRec">Reference to result structure for raw data for one channel</param>
/// <param name="fields">Separated fields containing the measurement as string</param>
/// <returns>true if protocol is valid</returns>
/// <remarks date="2018-Mar-22" author="T.Wiedebusch">
/// - Usage of VolumeFactorRawToQm and calculation of AccuDutOverflowVolumeCm
/// </remarks>
/// <remarks date="2023-Mar-09" author="T.Wiedebusch">
/// - Modified using common CRC check in advance.
/// </remarks>
private static void DecodeProtocolG(ref CalibrationRecord dataProtGRec, IList<String> fields)
{
// time stamp attachment
dataProtGRec.DecodedTime = DateTimeOffset.UtcNow;
// extract received CRC
dataProtGRec.Crc = ushort.Parse(fields[(Int32)ProtGsubString.Crc],
NumberStyles.HexNumber);
dataProtGRec.Channel = ushort.Parse(fields[(Int32)ProtGsubString.ChanNo],
NumberStyles.HexNumber);
dataProtGRec.Validation = ushort.Parse(fields[(Int32)ProtGsubString.Validation],
NumberStyles.HexNumber);
// Delta time of flight
dataProtGRec.DeltaTimeOfFlightS = int.Parse(fields[(Int32)ProtGsubString.Dtof],
NumberStyles.AllowHexSpecifier) * TofToSecondsFactor38Bit;
// Delta raw volume between last sample
dataProtGRec.DeltaVolumeRaw = uint.Parse(fields[(Int32)ProtGsubString.RawDVolume],
NumberStyles.AllowHexSpecifier);
// volume scaling
var volumeRawScale = uint.Parse(fields[(Int32)ProtGsubString.VolumeScale],
NumberStyles.AllowHexSpecifier);
dataProtGRec.VolumeScaleRawPerMl = volumeRawScale != 0 ? volumeRawScale : DefaultVolumeScaleRawPerMl;
dataProtGRec.VolumeFactorRawToQm = MilliLitersToCmFactor / dataProtGRec.VolumeScaleRawPerMl;
dataProtGRec.OverflowVolumeCm = MaxGenesisAccuVolumeRaw * dataProtGRec.VolumeFactorRawToQm;
// Calculate volume in cubic meters out of the raw volume
dataProtGRec.DeltaVolumeQm = dataProtGRec.DeltaVolumeRaw * dataProtGRec.VolumeFactorRawToQm;
// accumulated volume for each channel received from water meter scaled with volumeScale
// the volume can just be positive
dataProtGRec.AccuVolumeRaw = uint.Parse(fields[(Int32)ProtGsubString.AccuVolume],
NumberStyles.AllowHexSpecifier);
dataProtGRec.VolumeCm = dataProtGRec.AccuVolumeRaw * dataProtGRec.VolumeFactorRawToQm;
// Sample interval
dataProtGRec.SampleIntervalS = uint.Parse(fields[(Int32)ProtGsubString.SampleInterval],
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
// amplitude for high threshold in V
dataProtGRec.AmplitudeUpV = uint.Parse(fields[(Int32)ProtGsubString.AmplitudeUp],
NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor;
// amplitude for low threshold in V
dataProtGRec.AmplitudeDownV = uint.Parse(fields[(Int32)ProtGsubString.AmplitudeDown],
NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor;
// pulse width ratio high threshold
dataProtGRec.PulseWidthRatioUp = uint.Parse(fields[(Int32)ProtGsubString.PulseWidthRatioUp],
NumberStyles.AllowHexSpecifier) * PulseWidthToRelFactor;
// pulse width ratio low threshold
dataProtGRec.PulseWidthRatioDown = uint.Parse(fields[(Int32)ProtGsubString.PulseWidthRatioDown],
NumberStyles.AllowHexSpecifier) * PulseWidthToRelFactor;
// raw temperature
dataProtGRec.TemperatureRaw = int.Parse(fields[(Int32)ProtGsubString.RawTemperature],
NumberStyles.AllowHexSpecifier);
// temperature scaling
dataProtGRec.TemperaturePowFactor = uint.Parse(fields[(Int32)ProtGsubString.TemperatureScale],
NumberStyles.AllowHexSpecifier);
// Calculate temperature
dataProtGRec.TemperatureDegC = dataProtGRec.TemperatureRaw /
Math.Pow(2.0, dataProtGRec.TemperaturePowFactor);
// absolute CPU time, started at LED mode 3 activation
dataProtGRec.TimeS = uint.Parse(fields[(Int32)ProtGsubString.CpuTime],
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
}
/// <summary>
/// Extracting message from string fields to individual raw channel for protocol 'g'
/// </summary>
/// <param name="dataProtHRec">Reference to result structure for raw data for one channel</param>
/// <param name="fields">Separated fields containing the measurement as string</param>
/// <returns>true if protocol is valid</returns>
/// <remarks date="2018-Mar-22" author="T.Wiedebusch">
/// - Usage of VolumeFactorRawToQm and calculation of AccuDutOverflowVolumeCm
/// </remarks>
/// <remarks date="2023-Mar-09" author="T.Wiedebusch">
/// - Modified using common CRC check in advance.
/// </remarks>
private static void DecodeProtocolH(ref CalibrationRecord dataProtHRec, IList<String> fields)
{
// time stamp attachment
dataProtHRec.DecodedTime = DateTimeOffset.UtcNow;
// extract received CRC
dataProtHRec.Crc = ushort.Parse(fields[(Int32)ProtHsubString.Crc],
NumberStyles.HexNumber);
dataProtHRec.Channel = ushort.Parse(fields[(Int32)ProtHsubString.ChanNo],
NumberStyles.HexNumber);
dataProtHRec.Validation = ushort.Parse(fields[(Int32)ProtHsubString.Validation],
NumberStyles.HexNumber);
// Total time of flight
dataProtHRec.TotalTimeOfFlightS = int.Parse(fields[(Int32)ProtHsubString.Ttof],
NumberStyles.AllowHexSpecifier) * TofToSecondsFactor38Bit;
// Delta time of flight
dataProtHRec.DeltaTimeOfFlightS = int.Parse(fields[(Int32)ProtHsubString.Dtof],
NumberStyles.AllowHexSpecifier) * TofToSecondsFactor38Bit;
dataProtHRec.RawTotalTimeOfFlight =
int.Parse(fields[(Int32)ProtHsubString.Ttof], NumberStyles.AllowHexSpecifier);
dataProtHRec.RawDeltaTimeOfFlight = int.Parse(fields[(Int32)ProtHsubString.Dtof], NumberStyles.AllowHexSpecifier);
// Delta raw volume between two samples
dataProtHRec.DeltaVolumeRaw = uint.Parse(fields[(Int32)ProtHsubString.RawDVolume],
NumberStyles.AllowHexSpecifier);
// volume scaling
var volumeRawScale = uint.Parse(fields[(Int32)ProtHsubString.VolumeScale],
NumberStyles.AllowHexSpecifier);
dataProtHRec.VolumeScaleRawPerMl = volumeRawScale != 0 ? volumeRawScale : DefaultVolumeScaleRawPerMl;
dataProtHRec.VolumeFactorRawToQm = MilliLitersToCmFactor / dataProtHRec.VolumeScaleRawPerMl;
dataProtHRec.OverflowVolumeCm = MaxGenesisAccuVolumeRaw * dataProtHRec.VolumeFactorRawToQm;
// Calculate volume in cubic meters out of the raw volume
dataProtHRec.DeltaVolumeQm = dataProtHRec.DeltaVolumeRaw * dataProtHRec.VolumeFactorRawToQm;
// accumulated volume for each channel received from water meter scaled with volumeScale
// the volume can just be positive
dataProtHRec.AccuVolumeRaw = uint.Parse(fields[(Int32)ProtHsubString.AccuVolume],
NumberStyles.AllowHexSpecifier);
dataProtHRec.VolumeCm = dataProtHRec.AccuVolumeRaw * dataProtHRec.VolumeFactorRawToQm;
// Sample interval
dataProtHRec.SampleIntervalS = uint.Parse(fields[(Int32)ProtHsubString.SampleInterval],
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
// amplitude for high threshold in V
dataProtHRec.AmplitudeUpV = uint.Parse(fields[(Int32)ProtHsubString.AmplitudeUp],
NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor;
// amplitude for low threshold in V
dataProtHRec.AmplitudeDownV = uint.Parse(fields[(Int32)ProtHsubString.AmplitudeDown],
NumberStyles.AllowHexSpecifier) * AmplitudeToVoltFactor;
// raw temperature
dataProtHRec.TemperatureRaw = int.Parse(fields[(Int32)ProtHsubString.RawTemperature],
NumberStyles.AllowHexSpecifier);
// temperature scaling
dataProtHRec.TemperaturePowFactor = uint.Parse(fields[(Int32)ProtHsubString.TemperatureScale],
NumberStyles.AllowHexSpecifier);
// Calculate temperature
dataProtHRec.TemperatureDegC = dataProtHRec.TemperatureRaw / (
Math.Pow(2.0, dataProtHRec.TemperaturePowFactor));
// absolute CPU time, started at LED mode 3 activation
dataProtHRec.TimeS = uint.Parse(fields[(Int32)ProtHsubString.CpuTime],
NumberStyles.AllowHexSpecifier) * CpuTimeToSecondsFactor;
}
/// field position in protocol 'f'
private enum ProtFsubString
{
//do not remove this needed for position in record
ProtType,
DisplayVolume,
CpuTime,
Crc
}
/// field position in protocol 'm'
private enum ProtMsubString
{
//do not remove this needed for position in record
ProtType,
Status,
Installation,
Factor,
PreVolume,
PostVolume,
Crc
}
/// field position in protocol 'g'
private enum ProtGsubString
{
//do not remove this needed for position in record
ProtType,
ChanNo,
Validation,
Dtof,
RawDVolume,
AccuVolume,
VolumeScale,
SampleInterval,
AmplitudeUp,
AmplitudeDown,
PulseWidthRatioUp,
PulseWidthRatioDown,
RawTemperature,
TemperatureScale,
CpuTime,
Crc
}
/// field position in protocol 'h'
private enum ProtHsubString
{
//do not remove this needed for position in record
ProtType,
ChanNo,
Validation,
Ttof,
Dtof,
RawDVolume,
AccuVolume,
VolumeScale,
SampleInterval,
AmplitudeUp,
AmplitudeDown,
RawTemperature,
TemperatureScale,
CpuTime,
Crc
}
}
}
@@ -0,0 +1,107 @@
using System;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.DataPackages.EventArguments;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.ProtocolCore;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Protocols.StreamingProtocol
{
/// <inheritdoc />
/// <summary>
/// Layer between <see cref="StreamingDecoder" /> CRC16CCITT handler and Events
/// block trashy telegrams
/// </summary>
public class StreamingProtocol : BaseProtocol
{
/// <summary>
/// Decode data and hold results
/// </summary>
private StreamingDecoder _streamingDecode;
private readonly Boolean _ignoreCorruptedData;
/// <inheritdoc />
public override event EventHandler<BaseDataEventArgs> OnRecordIsDecoded;
/// <inheritdoc />
protected override void DecodeRecord(IPortDataEventArgs dataArgs)
{
var data = (String)dataArgs.GetData();
//Data decoding
_streamingDecode = new StreamingDecoder(_ignoreCorruptedData);
_streamingDecode.DecodeMsg(data);
//Event for new data
if (_streamingDecode.DataFlowTest != null)
{
_streamingDecode.DataFlowTest.SyncMarkRecord = dataArgs.GetSyncMarkRecord();
_streamingDecode.DataFlowTest.ReceivedTime = dataArgs.GetReceivedTime();
if (_streamingDecode.DataFlowTest.IsValid)
{
OnRecordIsDecoded?.Invoke(this,
new FlowDataEventArgs { NewData = _streamingDecode.DataFlowTest, RawData = data });
}
else if (!_ignoreCorruptedData)
{
OnRecordIsDecoded?.Invoke(this,
new FlowDataEventArgs { NewData = _streamingDecode.DataFlowTest, RawData = data });
}
}
else if (_streamingDecode.DataCalib != null)
{
_streamingDecode.DataCalib.SyncMarkRecord = dataArgs.GetSyncMarkRecord();
_streamingDecode.DataCalib.ReceivedTime = dataArgs.GetReceivedTime();
if (_streamingDecode.DataCalib.IsValid)
{
OnRecordIsDecoded?.Invoke(this, new CalibDataEventArgs
{
CalibChl = _streamingDecode.DataCalib,
RawData = data
});
}
else if (!_ignoreCorruptedData)
{
OnRecordIsDecoded?.Invoke(this, new CalibDataEventArgs
{
CalibChl = _streamingDecode.DataCalib,
RawData = data
});
}
}
else if (_streamingDecode.DataBendDetectTest != null)
{
_streamingDecode.DataBendDetectTest.SyncMarkRecord = dataArgs.GetSyncMarkRecord();
_streamingDecode.DataBendDetectTest.ReceivedTime = dataArgs.GetReceivedTime();
if (_streamingDecode.DataBendDetectTest.IsValid)
{
OnRecordIsDecoded?.Invoke(this, new BendDetectDataEventArgs
{
NewData = _streamingDecode.DataBendDetectTest,
RawData = data
});
}
else if (!_ignoreCorruptedData)
{
OnRecordIsDecoded?.Invoke(this, new BendDetectDataEventArgs
{
NewData = _streamingDecode.DataBendDetectTest,
RawData = data
});
}
}
//toDo: handle trashy telegrams
}
/// <inheritdoc />
public StreamingProtocol(String ident, Boolean ignoreCorruptedData = true) : base(ident)
{
_ignoreCorruptedData = ignoreCorruptedData;
}
}
}
@@ -0,0 +1,29 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers
{
/// <summary>
/// Enumeration for all known access level
/// </summary>
public enum Access
{
/// <summary>
/// Not set, means no valid input/information
/// </summary>
NS,
/// <summary>
/// No Access
/// </summary>
NA,
/// <summary>
/// read only
/// </summary>
RO,
/// <summary>
/// Write only
/// </summary>
WO,
/// <summary>
/// Read and Write
/// </summary>
RW
}
}
@@ -0,0 +1,6 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
public struct ByteArray
{
}
}
@@ -0,0 +1,8 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
// internal struct Enum8
public struct Enum8
{
//todo find out struct definition and implement it
}
}
@@ -0,0 +1,7 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
public struct Rpc
{
//todo find out struct definition and implement it
}
}
@@ -0,0 +1,67 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
/// <summary>
/// Static type to define restore capability
/// </summary>
public struct StaticType
{
/// <summary>
/// The value of the static type qualifier
/// </summary>
public String StaticTypeValue;
/// <summary>
/// Field identifier in configuration.json
/// </summary>
public const String StaticTypeFieldIdentifier = "statictype";
/// <summary>
/// Unknown to define restore capability
/// </summary>
// ReSharper disable once UnusedMember.Local
private const String UnknownAccess = null;
/// <summary>
/// Static type to define restore capability
/// </summary>
public const String RestoreRequired = "static";
/// <summary>
/// Static type approximate informs that the read back value may be not identical
/// to the written value and is approximated.
/// </summary>
public const String ReadBackApproximated = "approximate";
/// <summary>
/// Denied restore capability
/// </summary>
// ReSharper disable once UnusedMember.Local
public const String RestoreDenied = "dynamic";
/// <summary>
/// Unpredictable restore capability
/// </summary>
// ReSharper disable once UnusedMember.Local
public const String RestoreUnpredictable = "infrequentlyupdated";
/// <summary>
/// Check restore capability
/// </summary>
/// <returns>true if restore required</returns>
public Boolean CheckRestoreCapability()
{
return StaticTypeValue == RestoreRequired || StaticTypeValue == ReadBackApproximated;
}
/// <summary>
/// Ctor
/// </summary>
/// <param name="staticType">input of initial type</param>
public StaticType(String staticType)
{
StaticTypeValue = staticType;
}
}
}
@@ -0,0 +1,7 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
public struct StatusT
{
//todo find out struct definition and implement it
}
}
@@ -0,0 +1,117 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
/// <summary>
/// Class for Genesis TimeT
/// </summary>
public class TimeT
{
/// <summary>
/// Ctor:
/// - Takes actual date time as preset value
/// </summary>
public TimeT()
{
DateTimeUtc = DateTime.UtcNow;
}
/// <summary>
/// 1. Get seconds since 01. Jan 2000 00:00:00 UTC previously set by any call by
/// <see cref="UtcNowToSecondsSince2000"/> or <see cref="UtcAnyToSecondsSince2000"/>
/// or directly by this routine delivering the seconds e.g. read from meter,
/// 2. Set UTC seconds to analyze the corresponding time in UTC and use the ToString to
/// read the converted result.
/// </summary>
/// <remarks date="2023-Jul-19" author="Thomas Wiedebusch">
/// - Used <see cref="TimeT"/> to calculate time in UTC based on 01. Jan 2000.
/// </remarks>
/// <remarks date="2024-May-02" author="Thomas Wiedebusch">
/// - Setting of seconds and conversion to corresponding UTC sine 2000 or return seconds
/// from set DataTimeUtc.
/// </remarks>
public Int32 SecondsSince2000
{
set
{
var secondsToTimeSpan = TimeSpan.FromSeconds(value);
DateTimeUtc = _fixedDateTime2000.Add(secondsToTimeSpan);
}
// DateTimeUtc has to be set in advance with seconds or by the call
// of UtcNowToSecondsSince2000 or UtcAnyToSecondsSince2000
get => UtcToSecondsSince2000();
}
/// <summary>
/// Get actual seconds (UTC now) since 01. Jan 2000 00:00:00 UTC.
/// </summary>
/// <remarks date="2024-May-02" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
public Int32 UtcNowToSecondsSince2000
{
get
{
DateTimeUtc = DateTime.UtcNow;
return UtcToSecondsSince2000();
}
}
/// <summary>
/// Common routine to convert given UTC timestamp to seconds since 01. Jan 2000 00:00:00 UTC.
/// </summary>
/// <remarks date="2024-May-02" author="Thomas Wiedebusch">
/// - Initial.
/// </remarks>
private Int32 UtcToSecondsSince2000()
{
var timeSince2000Utc = DateTimeUtc - _fixedDateTime2000;
var secondsSince2000 = Convert.ToInt32(timeSince2000Utc.TotalSeconds);
return secondsSince2000;
}
/// <summary>
/// Builds the seconds for a given timestamp based on 01.Jan.2000 00:00:00 UTC
/// </summary>
/// <param name="dateTimeUtc">time stamp dateTime for conversion</param>
/// <remarks date="2023-Jul-19" author="Thomas Wiedebusch">
/// - Optional input of dateTime to convert this to the time in UTC based on 01. Jan 2000.
/// </remarks>
public Int32 UtcAnyToSecondsSince2000(DateTime dateTimeUtc)
{
try
{
// limit dateTime to 01. Jan 2000 00:00:00 UTC
DateTimeUtc = dateTimeUtc < _fixedDateTime2000 ? _fixedDateTime2000 : dateTimeUtc;
return UtcToSecondsSince2000();
}
catch (Exception)
{
return 0;
}
}
/// <summary>
/// The date and time of 01.Jan 2000 00:00:00 UTC
/// </summary>
private static readonly DateTime _fixedDateTime2000 = new DateTime(2000, 1, 1, 0, 0, 0);
/// <summary>
/// Converted date and time to UTC using the FixedDateTime2000
/// and the SecondsSince2000
/// </summary>
public DateTime DateTimeUtc
{
private set;
get;
}
/// <summary>
/// Get string of UTC date and time universal language with 24 hours format
/// </summary>
public override String ToString()
{
return $@"{DateTimeUtc:yyyy-MM-dd HH:mm:ss} UTC";
}
}
}
@@ -0,0 +1,6 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
public struct UInt672
{
}
}
@@ -0,0 +1,8 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
// ReSharper disable once InconsistentNaming
internal class st_radio_dewa
{
//todo find out struct definition and implement it
}
}
@@ -0,0 +1,8 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.DataTypes
{
// ReSharper disable once InconsistentNaming
internal class st_radio_tfx
{
//todo find out struct definition and implement it
}
}
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json
{
public class AppSection
{
public UInt16 Id { get; set; }
public FwVersion Version { get; set; }
public Dictionary<String, List<Build>> Builds { get; set; }
public IDictionary<String, Register> Registers { get; set; }
public IDictionary<String, Status> Status { get; set; }
}
}
@@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json
{
public class AppsDictionary : Dictionary<string, AppSection>
{
}
}
@@ -0,0 +1,11 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json
{
public class Build
{
public Int32 Id { get; set; }
public String FW { get; set; }
}
}
@@ -0,0 +1,19 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json
{
public class Details
{
public String Type { get; set; }
public Privilege Privilege { get; set; }
public String Description { get; set; }
public FwVersion Version { get; set; }
public String StaticType { get; set; }
public Value Values { get; set; }
}
}
@@ -0,0 +1,13 @@
using System;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json
{
public class FwVersion
{
public Int32? First { get; set; }
public Int32? Last { get; set; }
public Int32[] Exclude { get; set; }
}
}
@@ -0,0 +1,32 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json
{
public class JsonVersionsConverter : JsonConverter<FwVersion>
{
public override FwVersion ReadJson(JsonReader reader, Type objectType, FwVersion existingValue, Boolean hasExistingValue, JsonSerializer serializer)
{
var jobject = JToken.Load(reader);
if (jobject.Type != JTokenType.Object)
{
if (int.TryParse($"{jobject}", out var version))
{
return new FwVersion
{
First = version
};
}
}
return jobject.ToObject<FwVersion>();
}
public override void WriteJson(JsonWriter writer, FwVersion value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
@@ -0,0 +1,48 @@
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json
{
/// <summary>
/// Representing the 8 access levels meter register can have
/// </summary>
public class Privilege
{
/// <summary>
/// Value for first level
/// </summary>
public Access Lvl1 { get; set; }
/// <summary>
/// Value for second level
/// </summary>
public Access Lvl2 { get; set; }
/// <summary>
/// Value for third level
/// </summary>
public Access Lvl3 { get; set; }
/// <summary>
/// Value for fourth level
/// </summary>
public Access Lvl4 { get; set; }
/// <summary>
/// Value for fifth level
/// </summary>
public Access Lvl5 { get; set; }
/// <summary>
/// Value for sixth level
/// </summary>
public Access Lvl6 { get; set; }
/// <summary>
/// Value for seventh level
/// </summary>
public Access Lvl7 { get; set; }
/// <summary>
/// Value for eighth level
/// </summary>
public Access Lvl8 { get; set; }
}
}
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json
{
public class Register
{
public Byte Id { get; set; }
public IEnumerable<Details> Details { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show More