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.
This commit is contained in:
@@ -29,5 +29,5 @@ using System.Runtime.InteropServices;
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
[assembly: AssemblyVersion("3.9.3001.1")]
|
||||
[assembly: AssemblyFileVersion("3.9.3001.1")]
|
||||
[assembly: AssemblyVersion("3.9.3004.1")]
|
||||
[assembly: AssemblyFileVersion("3.9.3004.1")]
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using TBF.Rig.Generic;
|
||||
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 SmartReader(); }
|
||||
|
||||
public IComponent GetComponent(IComponentCfg cfg, IList<IComponent> components) { return new SmartReader(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,70 @@
|
||||
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
|
||||
|
||||
/// <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 = "iPerl";
|
||||
ParentName = string.Empty;
|
||||
OptoComPortNr = 10;
|
||||
RfidComPortNr = 0; /// = use mux. board
|
||||
MuxBoardNr = 1;
|
||||
ProcParams = CreateProcParamsProvider() as ProcParams;
|
||||
CommunicationInterface = CommunicationInterface.RFID;
|
||||
HeadCommunicationComPortNr = 0;
|
||||
}
|
||||
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
///
|
||||
/// 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();
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
///
|
||||
/// 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.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.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.groupBox2 = new System.Windows.Forms.GroupBox();
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
this.headPortNrTextBox = new System.Windows.Forms.TextBox();
|
||||
this.tabControl1.SuspendLayout();
|
||||
this.tabPage1.SuspendLayout();
|
||||
this.groupBox1.SuspendLayout();
|
||||
this.optoDataGroupBox.SuspendLayout();
|
||||
this.groupBox2.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// tabControl1
|
||||
//
|
||||
this.tabControl1.Controls.Add(this.tabPage1);
|
||||
this.tabControl1.Controls.Add(this.tabPage2);
|
||||
this.tabControl1.Location = new System.Drawing.Point(3, 3);
|
||||
this.tabControl1.Name = "tabControl1";
|
||||
this.tabControl1.SelectedIndex = 0;
|
||||
this.tabControl1.Size = new System.Drawing.Size(611, 432);
|
||||
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, 25);
|
||||
this.tabPage1.Name = "tabPage1";
|
||||
this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabPage1.Size = new System.Drawing.Size(603, 403);
|
||||
this.tabPage1.TabIndex = 0;
|
||||
this.tabPage1.Text = "Config";
|
||||
this.tabPage1.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// label4
|
||||
//
|
||||
this.label4.AutoSize = true;
|
||||
this.label4.Location = new System.Drawing.Point(208, 101);
|
||||
this.label4.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.label4.Name = "label4";
|
||||
this.label4.Size = new System.Drawing.Size(40, 16);
|
||||
this.label4.TabIndex = 25;
|
||||
this.label4.Text = "1 .. 10";
|
||||
//
|
||||
// label3
|
||||
//
|
||||
this.label3.AutoSize = true;
|
||||
this.label3.Location = new System.Drawing.Point(208, 72);
|
||||
this.label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.label3.Name = "label3";
|
||||
this.label3.Size = new System.Drawing.Size(33, 16);
|
||||
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(10, 259);
|
||||
this.groupBox1.Margin = new System.Windows.Forms.Padding(4);
|
||||
this.groupBox1.Name = "groupBox1";
|
||||
this.groupBox1.Padding = new System.Windows.Forms.Padding(4);
|
||||
this.groupBox1.Size = new System.Drawing.Size(552, 68);
|
||||
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(201, 27);
|
||||
this.comboBoxCommunicationInterface.Name = "comboBoxCommunicationInterface";
|
||||
this.comboBoxCommunicationInterface.Size = new System.Drawing.Size(71, 24);
|
||||
this.comboBoxCommunicationInterface.TabIndex = 9;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Location = new System.Drawing.Point(41, 30);
|
||||
this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(153, 16);
|
||||
this.label1.TabIndex = 8;
|
||||
this.label1.Text = "Communication Interface";
|
||||
//
|
||||
// rfidPortNrTextBox
|
||||
//
|
||||
this.rfidPortNrTextBox.Enabled = false;
|
||||
this.rfidPortNrTextBox.Location = new System.Drawing.Point(439, 26);
|
||||
this.rfidPortNrTextBox.Margin = new System.Windows.Forms.Padding(4);
|
||||
this.rfidPortNrTextBox.Name = "rfidPortNrTextBox";
|
||||
this.rfidPortNrTextBox.Size = new System.Drawing.Size(44, 22);
|
||||
this.rfidPortNrTextBox.TabIndex = 7;
|
||||
//
|
||||
// rfidSerialPortNrLabel
|
||||
//
|
||||
this.rfidSerialPortNrLabel.AutoSize = true;
|
||||
this.rfidSerialPortNrLabel.Location = new System.Drawing.Point(321, 30);
|
||||
this.rfidSerialPortNrLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.rfidSerialPortNrLabel.Name = "rfidSerialPortNrLabel";
|
||||
this.rfidSerialPortNrLabel.Size = new System.Drawing.Size(88, 16);
|
||||
this.rfidSerialPortNrLabel.TabIndex = 6;
|
||||
this.rfidSerialPortNrLabel.Text = "Serial port nr.:";
|
||||
//
|
||||
// optoDataGroupBox
|
||||
//
|
||||
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(10, 131);
|
||||
this.optoDataGroupBox.Margin = new System.Windows.Forms.Padding(4);
|
||||
this.optoDataGroupBox.Name = "optoDataGroupBox";
|
||||
this.optoDataGroupBox.Padding = new System.Windows.Forms.Padding(4);
|
||||
this.optoDataGroupBox.Size = new System.Drawing.Size(552, 119);
|
||||
this.optoDataGroupBox.TabIndex = 18;
|
||||
this.optoDataGroupBox.TabStop = false;
|
||||
this.optoDataGroupBox.Text = "Opto-data";
|
||||
//
|
||||
// tcpipPortLabel
|
||||
//
|
||||
this.tcpipPortLabel.AutoSize = true;
|
||||
this.tcpipPortLabel.Location = new System.Drawing.Point(41, 87);
|
||||
this.tcpipPortLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.tcpipPortLabel.Name = "tcpipPortLabel";
|
||||
this.tcpipPortLabel.Size = new System.Drawing.Size(54, 16);
|
||||
this.tcpipPortLabel.TabIndex = 4;
|
||||
this.tcpipPortLabel.Text = "Port nr..:";
|
||||
//
|
||||
// tcpipPortTextBox
|
||||
//
|
||||
this.tcpipPortTextBox.Enabled = false;
|
||||
this.tcpipPortTextBox.Location = new System.Drawing.Point(143, 84);
|
||||
this.tcpipPortTextBox.Margin = new System.Windows.Forms.Padding(4);
|
||||
this.tcpipPortTextBox.Name = "tcpipPortTextBox";
|
||||
this.tcpipPortTextBox.Size = new System.Drawing.Size(51, 22);
|
||||
this.tcpipPortTextBox.TabIndex = 5;
|
||||
//
|
||||
// ipAddressLabel
|
||||
//
|
||||
this.ipAddressLabel.AutoSize = true;
|
||||
this.ipAddressLabel.Location = new System.Drawing.Point(41, 59);
|
||||
this.ipAddressLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.ipAddressLabel.Name = "ipAddressLabel";
|
||||
this.ipAddressLabel.Size = new System.Drawing.Size(78, 16);
|
||||
this.ipAddressLabel.TabIndex = 2;
|
||||
this.ipAddressLabel.Text = "IP address.:";
|
||||
//
|
||||
// ipAddressTextBox
|
||||
//
|
||||
this.ipAddressTextBox.Enabled = false;
|
||||
this.ipAddressTextBox.Location = new System.Drawing.Point(143, 55);
|
||||
this.ipAddressTextBox.Margin = new System.Windows.Forms.Padding(4);
|
||||
this.ipAddressTextBox.Name = "ipAddressTextBox";
|
||||
this.ipAddressTextBox.Size = new System.Drawing.Size(129, 22);
|
||||
this.ipAddressTextBox.TabIndex = 3;
|
||||
//
|
||||
// radioButton1
|
||||
//
|
||||
this.radioButton1.AutoSize = true;
|
||||
this.radioButton1.Checked = true;
|
||||
this.radioButton1.Enabled = false;
|
||||
this.radioButton1.Location = new System.Drawing.Point(29, 23);
|
||||
this.radioButton1.Margin = new System.Windows.Forms.Padding(4);
|
||||
this.radioButton1.Name = "radioButton1";
|
||||
this.radioButton1.Size = new System.Drawing.Size(99, 20);
|
||||
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(312, 23);
|
||||
this.radioButton2.Margin = new System.Windows.Forms.Padding(4);
|
||||
this.radioButton2.Name = "radioButton2";
|
||||
this.radioButton2.Size = new System.Drawing.Size(115, 20);
|
||||
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(321, 55);
|
||||
this.optoSerialPortLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.optoSerialPortLabel.Name = "optoSerialPortLabel";
|
||||
this.optoSerialPortLabel.Size = new System.Drawing.Size(88, 16);
|
||||
this.optoSerialPortLabel.TabIndex = 6;
|
||||
this.optoSerialPortLabel.Text = "Serial port nr.:";
|
||||
//
|
||||
// optoSerialPortTextBox
|
||||
//
|
||||
this.optoSerialPortTextBox.Enabled = false;
|
||||
this.optoSerialPortTextBox.Location = new System.Drawing.Point(439, 52);
|
||||
this.optoSerialPortTextBox.Margin = new System.Windows.Forms.Padding(4);
|
||||
this.optoSerialPortTextBox.Name = "optoSerialPortTextBox";
|
||||
this.optoSerialPortTextBox.Size = new System.Drawing.Size(44, 22);
|
||||
this.optoSerialPortTextBox.TabIndex = 7;
|
||||
//
|
||||
// groupTextBox
|
||||
//
|
||||
this.groupTextBox.Enabled = false;
|
||||
this.groupTextBox.Location = new System.Drawing.Point(153, 97);
|
||||
this.groupTextBox.Margin = new System.Windows.Forms.Padding(4);
|
||||
this.groupTextBox.Name = "groupTextBox";
|
||||
this.groupTextBox.Size = new System.Drawing.Size(44, 22);
|
||||
this.groupTextBox.TabIndex = 22;
|
||||
//
|
||||
// groupLabel
|
||||
//
|
||||
this.groupLabel.AutoSize = true;
|
||||
this.groupLabel.Location = new System.Drawing.Point(6, 101);
|
||||
this.groupLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.groupLabel.Name = "groupLabel";
|
||||
this.groupLabel.Size = new System.Drawing.Size(54, 16);
|
||||
this.groupLabel.TabIndex = 21;
|
||||
this.groupLabel.Text = "Group 2";
|
||||
//
|
||||
// muxBoardNrTextBox
|
||||
//
|
||||
this.muxBoardNrTextBox.Enabled = false;
|
||||
this.muxBoardNrTextBox.Location = new System.Drawing.Point(153, 69);
|
||||
this.muxBoardNrTextBox.Margin = new System.Windows.Forms.Padding(4);
|
||||
this.muxBoardNrTextBox.Name = "muxBoardNrTextBox";
|
||||
this.muxBoardNrTextBox.Size = new System.Drawing.Size(44, 22);
|
||||
this.muxBoardNrTextBox.TabIndex = 20;
|
||||
//
|
||||
// muxBoardNrLabel
|
||||
//
|
||||
this.muxBoardNrLabel.AutoSize = true;
|
||||
this.muxBoardNrLabel.Location = new System.Drawing.Point(6, 72);
|
||||
this.muxBoardNrLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.muxBoardNrLabel.Name = "muxBoardNrLabel";
|
||||
this.muxBoardNrLabel.Size = new System.Drawing.Size(131, 16);
|
||||
this.muxBoardNrLabel.TabIndex = 19;
|
||||
this.muxBoardNrLabel.Text = "Group 1 (mux. board)";
|
||||
//
|
||||
// nameTextBox
|
||||
//
|
||||
this.nameTextBox.Enabled = false;
|
||||
this.nameTextBox.Location = new System.Drawing.Point(153, 40);
|
||||
this.nameTextBox.Margin = new System.Windows.Forms.Padding(4);
|
||||
this.nameTextBox.Name = "nameTextBox";
|
||||
this.nameTextBox.Size = new System.Drawing.Size(160, 22);
|
||||
this.nameTextBox.TabIndex = 17;
|
||||
//
|
||||
// nameLabel
|
||||
//
|
||||
this.nameLabel.AutoSize = true;
|
||||
this.nameLabel.Location = new System.Drawing.Point(6, 44);
|
||||
this.nameLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.nameLabel.Name = "nameLabel";
|
||||
this.nameLabel.Size = new System.Drawing.Size(44, 16);
|
||||
this.nameLabel.TabIndex = 16;
|
||||
this.nameLabel.Text = "Name";
|
||||
//
|
||||
// classNameLabel
|
||||
//
|
||||
this.classNameLabel.AutoSize = true;
|
||||
this.classNameLabel.Location = new System.Drawing.Point(149, 11);
|
||||
this.classNameLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.classNameLabel.Name = "classNameLabel";
|
||||
this.classNameLabel.Size = new System.Drawing.Size(78, 16);
|
||||
this.classNameLabel.TabIndex = 15;
|
||||
this.classNameLabel.Text = "ClassName";
|
||||
//
|
||||
// tabPage2
|
||||
//
|
||||
this.tabPage2.Location = new System.Drawing.Point(4, 25);
|
||||
this.tabPage2.Name = "tabPage2";
|
||||
this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tabPage2.Size = new System.Drawing.Size(603, 403);
|
||||
this.tabPage2.TabIndex = 1;
|
||||
this.tabPage2.Text = "Test";
|
||||
this.tabPage2.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// groupBox2
|
||||
//
|
||||
this.groupBox2.Controls.Add(this.headPortNrTextBox);
|
||||
this.groupBox2.Controls.Add(this.label2);
|
||||
this.groupBox2.Location = new System.Drawing.Point(10, 335);
|
||||
this.groupBox2.Name = "groupBox2";
|
||||
this.groupBox2.Size = new System.Drawing.Size(552, 50);
|
||||
this.groupBox2.TabIndex = 26;
|
||||
this.groupBox2.TabStop = false;
|
||||
this.groupBox2.Text = "Head Communication";
|
||||
//
|
||||
// label2
|
||||
//
|
||||
this.label2.AutoSize = true;
|
||||
this.label2.Location = new System.Drawing.Point(321, 18);
|
||||
this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.label2.Name = "label2";
|
||||
this.label2.Size = new System.Drawing.Size(88, 16);
|
||||
this.label2.TabIndex = 7;
|
||||
this.label2.Text = "Serial port nr.:";
|
||||
//
|
||||
// headPortNrTextBox
|
||||
//
|
||||
this.headPortNrTextBox.Enabled = false;
|
||||
this.headPortNrTextBox.Location = new System.Drawing.Point(439, 15);
|
||||
this.headPortNrTextBox.Margin = new System.Windows.Forms.Padding(4);
|
||||
this.headPortNrTextBox.Name = "headPortNrTextBox";
|
||||
this.headPortNrTextBox.Size = new System.Drawing.Size(44, 22);
|
||||
this.headPortNrTextBox.TabIndex = 8;
|
||||
//
|
||||
// IperlHeadCfgCtrl
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.Controls.Add(this.tabControl1);
|
||||
this.Margin = new System.Windows.Forms.Padding(4);
|
||||
this.Name = "GenesisCfgCtrl";
|
||||
this.Size = new System.Drawing.Size(617, 438);
|
||||
this.Load += new System.EventHandler(this.WaterMeterCfgCtrl_Load);
|
||||
this.tabControl1.ResumeLayout(false);
|
||||
this.tabPage1.ResumeLayout(false);
|
||||
this.tabPage1.PerformLayout();
|
||||
this.groupBox1.ResumeLayout(false);
|
||||
this.groupBox1.PerformLayout();
|
||||
this.optoDataGroupBox.ResumeLayout(false);
|
||||
this.optoDataGroupBox.PerformLayout();
|
||||
this.groupBox2.ResumeLayout(false);
|
||||
this.groupBox2.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#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 0–9 -> 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,353 @@
|
||||
///
|
||||
/// Copyright (c) 2015-2021 Sensus Metering Systems
|
||||
///
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
|
||||
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;
|
||||
|
||||
|
||||
///
|
||||
/// 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;
|
||||
|
||||
///
|
||||
/// 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>
|
||||
/// Parses optical telegram and returns OptoTelegramRaw object
|
||||
/// </summary>
|
||||
/// <description>
|
||||
/// Create a configuration structure from a complete byte array
|
||||
///
|
||||
/// Telegram description:
|
||||
///
|
||||
/// AAAAAA[tab]BBBB[tab]CCCC[tab]DDDDDD[tab]EEEE[tab]FFFFFFFF[tab]GG[cr][lf] (42 bytes)
|
||||
///
|
||||
/// Data Comment Type Calculate to decimal
|
||||
/// ----------------------------------------------------------------
|
||||
/// AAAAAA EMF Int24 Value * 0.000000333
|
||||
/// BBBB Magnetic field Int16 Value
|
||||
/// CCCC Flow Int16 Value * 0.225 * Scalig factor
|
||||
/// DDDDDD Volume Int24 Value / 16000 * Scaling factor
|
||||
/// EEEE Impedance Int16 Value
|
||||
/// FFFFFFFF Timestamp Uint32 Value / 8192
|
||||
/// GG Checksum Byte
|
||||
/// ----------------------------------------------------------------
|
||||
///
|
||||
/// Example:
|
||||
/// FFFFFE 51EA 0000 65324E 0087 F6319DFF 86
|
||||
/// FFDD3A 51F9 0000 65324E 0088 F631A60B 45
|
||||
/// ...
|
||||
/// </description>
|
||||
/// <param name="data">A complete byte array data</param>
|
||||
/// <returns>true = telegram OK, false = telegram NOK</returns>
|
||||
// public bool UpdateFromString(string telegram, int counter, float refFlow, ref Int64 volumeRawExtLast, ref Int64 timestampExtLast, bool isLog = false)
|
||||
// {
|
||||
// DateTime = DateTime.Now;
|
||||
// Counter = counter;
|
||||
// RefFlow = refFlow;
|
||||
//
|
||||
// if ((telegram == null) || (telegram.Length < Length) ||
|
||||
// (telegram[6] != '\t') || (telegram[11] != '\t') || (telegram[16] != '\t') ||
|
||||
// (telegram[23] != '\t') || (telegram[28] != '\t') || (telegram[37] != '\t') ||
|
||||
// (!isLog && (telegram[40] != '\r' || telegram[41] != '\n')))
|
||||
// {
|
||||
// Flags = OptoTelegramFlags.InvalidTelegram;
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// UInt32 uEmfRaw;
|
||||
// bool f1 = UInt32.TryParse(telegram.Substring(0, 6), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out uEmfRaw);
|
||||
// EmfRaw = (uEmfRaw > 0x7FFFFF) ? ((int)uEmfRaw - 0x1000000) : (int)uEmfRaw;
|
||||
//
|
||||
// bool f2 = Int16.TryParse(telegram.Substring(7, 4), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out MagneticFieldRaw);
|
||||
// bool f3 = Int16.TryParse(telegram.Substring(12, 4), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out FlowRaw);
|
||||
// bool f4 = UInt32.TryParse(telegram.Substring(17, 6), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out VolumeRaw);
|
||||
// bool f5 = Int16.TryParse(telegram.Substring(24, 4), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out Impedance);
|
||||
// bool f6 = UInt32.TryParse(telegram.Substring(29, 8), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out Timestamp);
|
||||
// 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 = f1 && f2 && f3 && f4 && f5 && f6 && f7 && (calculatedCheckSum == CheckSum);
|
||||
//
|
||||
// if (allOk)
|
||||
// {
|
||||
// ///
|
||||
// /// Cope with 'VolumeRaw' overflow
|
||||
// ///
|
||||
// Int64 uncorrected = (Int64)(((UInt64)volumeRawExtLast & 0xFFFFFFFFFF000000UL) | VolumeRaw);
|
||||
// if (Math.Abs(uncorrected - volumeRawExtLast) <= 0x800000L)
|
||||
// {
|
||||
// VolumeRawExt = volumeRawExtLast = uncorrected;
|
||||
// }
|
||||
// else if (Math.Abs(uncorrected + 0x1000000L - volumeRawExtLast) <= 0x800000L)
|
||||
// {
|
||||
// VolumeRawExt = volumeRawExtLast = uncorrected + 0x1000000L;
|
||||
// }
|
||||
// else if (Math.Abs(uncorrected - 0x1000000L - volumeRawExtLast) <= 0x800000L)
|
||||
// {
|
||||
// VolumeRawExt = volumeRawExtLast = uncorrected - 0x1000000L;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// VolumeRawExt = volumeRawExtLast = uncorrected;
|
||||
// }
|
||||
//
|
||||
// ///
|
||||
// /// Cope with 'Timestamp' overflow
|
||||
// ///
|
||||
// uncorrected = (Int64)(((UInt64)timestampExtLast & 0xFFFFFFFF00000000UL) | Timestamp);
|
||||
// if (Math.Abs(uncorrected - timestampExtLast) <= 0x80000000L)
|
||||
// {
|
||||
// TimestampExt = timestampExtLast = uncorrected;
|
||||
// }
|
||||
// else if (Math.Abs(uncorrected + 0x100000000L - timestampExtLast) <= 0x80000000L)
|
||||
// {
|
||||
// TimestampExt = timestampExtLast = uncorrected + 0x100000000L;
|
||||
// }
|
||||
// else if (Math.Abs(uncorrected - 0x100000000L - timestampExtLast) <= 0x80000000L)
|
||||
// {
|
||||
// TimestampExt = timestampExtLast = uncorrected - 0x100000000L;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// TimestampExt = timestampExtLast = uncorrected;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Flags = allOk ? OptoTelegramFlags.OK : OptoTelegramFlags.InvalidTelegram;
|
||||
//
|
||||
// return allOk;
|
||||
// }
|
||||
|
||||
|
||||
// -------- TIMESTAMP (seconds) --------
|
||||
// bbbbbbbb – unsigned 32 bit ASIC time stamp in 8192 ticks per second– rolls over after 2^32
|
||||
private const double TS_TICKS_PER_SEC = 8192.0;
|
||||
private const double TS_RANGE = 4294967296.0 / TS_TICKS_PER_SEC; // 2^32 / 8192 = 524288 sec
|
||||
|
||||
// -------- VOLUME (liters) --------
|
||||
// vvvvvv is unsigned 24-bit, 1 tick = 1/4 ml = 0.00025 L
|
||||
private const double VOL_LITERS_PER_TICK = 0.00025; // liters per tick
|
||||
private const double VOL_RANGE = 16777216.0 * VOL_LITERS_PER_TICK; // 2^24 * 0.00025 = 4194.304 L
|
||||
|
||||
// -------- VOLUME (liters) --------
|
||||
private const double GAL_TO_LITER = 3.785411784;
|
||||
|
||||
public void UpdateFromSmart(
|
||||
Object data,
|
||||
int counter,
|
||||
float refFlow,
|
||||
ref double volumeRawExtLast,
|
||||
ref double timestampExtLast)
|
||||
{
|
||||
DateTime = DateTime.Now;
|
||||
Counter = counter;
|
||||
RefFlow = refFlow;
|
||||
|
||||
throw new NotImplementedException();
|
||||
|
||||
// FlowRaw = data.RawFlow;
|
||||
// VolumeRaw = data.RawVolume;
|
||||
//
|
||||
// // ---- TIMESTAMP RAW (seconds, modulo TS_RANGE) ----
|
||||
// // If upstream conversion ever produced negative values, normalize them.
|
||||
// double ts = data.AsicTimestamp; // already in seconds, but wraps every TS_RANGE
|
||||
// ts = ts % TS_RANGE;
|
||||
// if (ts < 0) ts += TS_RANGE;
|
||||
//
|
||||
// Timestamp = ts;
|
||||
//
|
||||
// // ---------- VOLUME UNWRAP ----------
|
||||
// double v = VolumeRaw;
|
||||
//
|
||||
// if (double.IsNaN(volumeRawExtLast))
|
||||
// {
|
||||
// VolumeRawExt = volumeRawExtLast = v;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // nearest-lap unwrap
|
||||
// //double k = Math.Round(volumeRawExtLast - v) / VOL_RANGE);
|
||||
// if (v < volumeRawExtLast)
|
||||
// {
|
||||
// VolumeRawExt = volumeRawExtLast = v + VOL_RANGE;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// VolumeRawExt = volumeRawExtLast = v;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // ---------- TIMESTAMP UNWRAP (seconds) ----------
|
||||
// if (double.IsNaN(timestampExtLast))
|
||||
// {
|
||||
// TimestampExt = timestampExtLast = ts;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// // robust unwrap: choose the smallest jump across the modulo boundary
|
||||
// double lastMod = timestampExtLast % TS_RANGE;
|
||||
// if (lastMod < 0) lastMod += TS_RANGE;
|
||||
//
|
||||
// double delta = ts - lastMod;
|
||||
//
|
||||
// if (delta < -TS_RANGE / 2.0) delta += TS_RANGE;
|
||||
// else if (delta > TS_RANGE / 2.0) delta -= TS_RANGE;
|
||||
//
|
||||
// TimestampExt = timestampExtLast = timestampExtLast + delta;
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <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"),
|
||||
VolumeRaw.ToString("X6"),
|
||||
Impedance.ToString("X4"),
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Xylem.Common.Hardware.WaterMeter.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Xylem.Common.Hardware.WaterMeter.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Xylem.Common.Hardware.WaterMeter.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
+23
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Xylem.Common.Metrology.Measurements;
|
||||
|
||||
namespace Xylem.Common.Hardware.WaterMeter.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Xylem.Common.Metrology.Measurements;
|
||||
|
||||
namespace Xylem.Common.Hardware.WaterMeter.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Xylem.Common.Metrology.Measurements;
|
||||
|
||||
namespace Xylem.Common.Hardware.WaterMeter.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
|
||||
namespace Xylem.Common.Hardware.WaterMeter.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; }
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace Xylem.Common.Hardware.WaterMeter.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;
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace Xylem.Common.Hardware.WaterMeter.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; }
|
||||
}
|
||||
}
|
||||
+88
@@ -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;
|
||||
}
|
||||
}
|
||||
+24
@@ -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;
|
||||
}
|
||||
}
|
||||
+86
@@ -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; }
|
||||
|
||||
}
|
||||
}
|
||||
+114
@@ -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;
|
||||
}
|
||||
}
|
||||
+111
@@ -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; }
|
||||
}
|
||||
}
|
||||
+96
@@ -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; }
|
||||
|
||||
}
|
||||
}
|
||||
+46
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -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();
|
||||
}
|
||||
}
|
||||
+33
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
+63
@@ -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();
|
||||
}
|
||||
}
|
||||
+63
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -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; }
|
||||
}
|
||||
}
|
||||
+17
@@ -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,
|
||||
}
|
||||
}
|
||||
+109
@@ -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;
|
||||
}
|
||||
}
|
||||
+702
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+600
@@ -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
|
||||
}
|
||||
}
|
||||
+17
@@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
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.ProtocolCore.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Ports.PortCore.EventArguments;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Interfaces.Protocols.ProtocolCore.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);
|
||||
}
|
||||
}
|
||||
+31
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -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);
|
||||
}
|
||||
}
|
||||
+213
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -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");
|
||||
}
|
||||
}
|
||||
+52
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+168
@@ -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\\";
|
||||
|
||||
}
|
||||
}
|
||||
+125
@@ -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 computer’s 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;
|
||||
}
|
||||
}
|
||||
+31
@@ -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
|
||||
}
|
||||
}
|
||||
+25
@@ -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
|
||||
}
|
||||
}
|
||||
+47
@@ -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;
|
||||
}
|
||||
}
|
||||
+48
@@ -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
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
+25
@@ -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;
|
||||
}
|
||||
}
|
||||
+59
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+767
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
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 Xylem.Common.Hardware.WaterMeter.Genesis.GenesisConfig;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+547
@@ -0,0 +1,547 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Xylem.Common.Hardware.WaterMeter.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;
|
||||
private 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
|
||||
private 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
|
||||
}
|
||||
}
|
||||
}
|
||||
+107
@@ -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
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
|
||||
{
|
||||
public struct ByteArray
|
||||
{
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
|
||||
{
|
||||
// internal struct Enum8
|
||||
public struct Enum8
|
||||
{
|
||||
//todo find out struct definition and implement it
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
|
||||
{
|
||||
public struct Rpc
|
||||
{
|
||||
//todo find out struct definition and implement it
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
|
||||
namespace Xylem.Common.Hardware.WaterMeter.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
|
||||
{
|
||||
public struct StatusT
|
||||
{
|
||||
//todo find out struct definition and implement it
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
|
||||
namespace Xylem.Common.Hardware.WaterMeter.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";
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
|
||||
{
|
||||
public struct UInt672
|
||||
{
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
internal class st_radio_dewa
|
||||
{
|
||||
//todo find out struct definition and implement it
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
internal class st_radio_tfx
|
||||
{
|
||||
//todo find out struct definition and implement it
|
||||
}
|
||||
}
|
||||
+18
@@ -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; }
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json
|
||||
{
|
||||
public class AppsDictionary : Dictionary<string, AppSection>
|
||||
{
|
||||
}
|
||||
}
|
||||
+11
@@ -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; }
|
||||
}
|
||||
}
|
||||
+19
@@ -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; }
|
||||
}
|
||||
}
|
||||
+13
@@ -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; }
|
||||
}
|
||||
}
|
||||
+32
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -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; }
|
||||
}
|
||||
}
|
||||
+12
@@ -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; }
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json
|
||||
{
|
||||
public class Status
|
||||
{
|
||||
public Byte Id { get; set; }
|
||||
|
||||
public String Action { get; set; }
|
||||
|
||||
public String Description { get; set; }
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json
|
||||
{
|
||||
public class Value
|
||||
{
|
||||
public Object Minimum { get; set; }
|
||||
|
||||
public Object Maximum { get; set; }
|
||||
|
||||
public Object Default { get; set; }
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xylem.Common.Hardware.WaterMeter.WaterMeterRegisters;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers
|
||||
{
|
||||
/// <summary>
|
||||
/// Representing all registers from one meter.
|
||||
/// create empty list on construction.
|
||||
/// updated after read register
|
||||
/// </summary>
|
||||
public class MeterRegisters : IEnumerable
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictionary of meter registers
|
||||
/// </summary>
|
||||
public readonly ConcurrentDictionary<RegisterDefinition, Byte[]> MeterRegisterDic;
|
||||
|
||||
/// <summary>
|
||||
/// create empty list on construction.
|
||||
/// </summary>
|
||||
public MeterRegisters()
|
||||
{
|
||||
MeterRegisterDic = new ConcurrentDictionary<RegisterDefinition, Byte[]>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// add empty registers
|
||||
/// </summary>
|
||||
/// <param name="adds"></param>
|
||||
public void AddRegistersDefinitions(List<IRegister> adds)
|
||||
{
|
||||
foreach (var add in adds)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (add is RegisterDefinition addGenesisRegister)
|
||||
{
|
||||
MeterRegisterDic.TryAdd(addGenesisRegister, null);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search register referenced by name as combination of application name and register name
|
||||
/// Example: GENSISFLOW_LedMode
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public RegisterDefinition GetRegisterDefinitionByName(String name)
|
||||
{
|
||||
if (MeterRegisterDic == null || !MeterRegisterDic.Any())
|
||||
{
|
||||
throw new ApplicationException($"Register {name} is not available. Registers are not loaded!");
|
||||
}
|
||||
|
||||
var regDef = MeterRegisterDic.Where(f => f.Key.GetIdent().ToLower() == name.ToLower()).ToList();
|
||||
|
||||
if (regDef.Count == 1)
|
||||
{
|
||||
return regDef.First().Key;
|
||||
}
|
||||
var r = regDef.Count >= 1 ? regDef.Last().Key : new RegisterDefinition();
|
||||
|
||||
if (!string.IsNullOrEmpty(r.AppName))
|
||||
{
|
||||
return r;
|
||||
}
|
||||
// if no register is available throw ex
|
||||
throw new ApplicationException($"Register {name} is not available. Check the configuration.json for latest version!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get current value of register in program.
|
||||
/// ATTENTION: this is not the current meter register!
|
||||
/// for meter register use read register in advance
|
||||
/// </summary>
|
||||
/// <param name="register">register to get</param>
|
||||
/// <returns>current value</returns>
|
||||
public Byte[] Get(String register)
|
||||
{
|
||||
var reg = GetRegisterDefinitionByName(register);
|
||||
if (!MeterRegisterDic.ContainsKey(reg))
|
||||
return null;
|
||||
if (MeterRegisterDic.Count(f => f.Key.GetIdent() == register) > 1)
|
||||
{
|
||||
if (MeterRegisterDic.Any(f => f.Key.GetIdent() == register && f.Value != null))
|
||||
{
|
||||
var regDs = MeterRegisterDic.Last(f => f.Key.GetIdent() == register && f.Value != null);
|
||||
return regDs.Value;
|
||||
|
||||
}
|
||||
}
|
||||
var regD = MeterRegisterDic.First(f => f.Key.GetIdent() == register);
|
||||
return regD.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set register in program
|
||||
/// ATTENTION: this is not stored to the meter!
|
||||
/// to store to meter register use write register!
|
||||
/// </summary>
|
||||
/// <param name="register">Register to set</param>
|
||||
/// <param name="value">Value to set</param>
|
||||
public void Set(RegisterDefinition register, Byte[] value)
|
||||
{
|
||||
if (MeterRegisterDic.ContainsKey(register))
|
||||
{
|
||||
MeterRegisterDic[register] = value;
|
||||
}
|
||||
else if (register.DataType != null && register.RegisterDetail != null)
|
||||
{
|
||||
throw new ApplicationException("unknown register");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unimplemented
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public IEnumerator GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <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.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to ERROR: Register data type unknown.
|
||||
/// </summary>
|
||||
internal static string StrRegisterDataTypeUnknown {
|
||||
get {
|
||||
return ResourceManager.GetString("StrRegisterDataTypeUnknown", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to ERROR: Register value is out of range.
|
||||
/// </summary>
|
||||
internal static string StrRegisterValueOutOfRange {
|
||||
get {
|
||||
return ResourceManager.GetString("StrRegisterValueOutOfRange", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to WARNING: Register value is set to default.
|
||||
/// </summary>
|
||||
internal static string StrSetRegisterToDefault {
|
||||
get {
|
||||
return ResourceManager.GetString("StrSetRegisterToDefault", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 1.3
|
||||
|
||||
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">1.3</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1">this is my long string</data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
[base64 mime encoded serialized .NET Framework object]
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
[base64 mime encoded string representing a byte array form of the .NET Framework object]
|
||||
</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.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:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<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" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</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>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="StrSetRegisterToDefault" xml:space="preserve">
|
||||
<value>WARNUNG: Registerwert auf Standardwert gesetzt</value>
|
||||
</data>
|
||||
<data name="StrRegisterValueOutOfRange" xml:space="preserve">
|
||||
<value>FEHLER: Registerwert außerhalb des zulässigen Bereiches</value>
|
||||
</data>
|
||||
<data name="StrRegisterDataTypeUnknown" xml:space="preserve">
|
||||
<value>FEHLER: Registerdatentyp ist unbekannt!</value>
|
||||
</data>
|
||||
</root>
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
<?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>
|
||||
<data name="StrSetRegisterToDefault" xml:space="preserve">
|
||||
<value>WARNING: Register value is set to default</value>
|
||||
</data>
|
||||
<data name="StrRegisterValueOutOfRange" xml:space="preserve">
|
||||
<value>ERROR: Register value is out of range</value>
|
||||
</data>
|
||||
<data name="StrRegisterDataTypeUnknown" xml:space="preserve">
|
||||
<value>ERROR: Register data type unknown</value>
|
||||
</data>
|
||||
</root>
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers
|
||||
{
|
||||
/// <summary>
|
||||
/// Recovery of registers in the field
|
||||
/// </summary>
|
||||
public class RecoveryRegisterItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Ctor
|
||||
/// </summary>
|
||||
public RecoveryRegisterItem()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ctor for write access parameter setup
|
||||
/// </summary>
|
||||
public RecoveryRegisterItem(String registerIdent, Byte[] writeValue, Byte[] readBackValue = null)
|
||||
{
|
||||
RegisterIdent = registerIdent;
|
||||
WriteValue = writeValue;
|
||||
ReadBackValue = readBackValue;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Register name, use the latest configuration.json to make sure that the address will be correct
|
||||
/// </summary>
|
||||
public String RegisterIdent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content to write
|
||||
/// </summary>
|
||||
public Byte[] WriteValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// check if the written value is sett like this
|
||||
/// Null or not set if a read back is not needed
|
||||
/// </summary>
|
||||
public Byte[] ReadBackValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Mark Update as failed if write was not successful
|
||||
/// </summary>
|
||||
public Boolean IsCritical { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Condition when the register will be overridden
|
||||
/// </summary>
|
||||
public RegisterRecoveryAccess Access { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers;
|
||||
|
||||
namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class RecoverySettings
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Null if check in not necessary if is an number check it against the pcbid from meter
|
||||
/// </summary>
|
||||
public String PcbId;
|
||||
|
||||
/// <summary>
|
||||
/// Radio frequency in MHz referenced a SENSUSRADIO AppId 0x10, Register FrequencyIndicator Id 0x05
|
||||
/// values: default 433, minimum 433, maximum 868 or null if region is "NA" or if for all frequencies
|
||||
/// </summary>
|
||||
public Int32? RadioFrequencyMhz;
|
||||
|
||||
/// <summary>
|
||||
/// Null till meter handles regions
|
||||
/// </summary>
|
||||
public String Region;
|
||||
|
||||
/// <summary>
|
||||
/// Release string which is the FLEXNETVERSION
|
||||
/// </summary>
|
||||
public String Release;
|
||||
|
||||
/// <summary>
|
||||
/// User name
|
||||
/// </summary>
|
||||
public String ApproverName;
|
||||
|
||||
/// <summary>
|
||||
/// Null means not approved
|
||||
/// </summary>
|
||||
public DateTimeOffset? ApprovalDate;
|
||||
|
||||
/// <summary>
|
||||
/// List of registers to recover
|
||||
/// </summary>
|
||||
public List<RecoveryRegisterItem> RecoveryRegisters;
|
||||
|
||||
/// <summary>
|
||||
/// File name for this recovery register set
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public String GenerateFileName()
|
||||
{
|
||||
return $"{PcbId}_{Region}_{Release}.recovery";
|
||||
}
|
||||
}
|
||||
}
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
using System;
|
||||
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
|
||||
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers
|
||||
{
|
||||
/// <summary>
|
||||
/// Check the register value for Out Of Boundaries (min, max) and optional if set to Default .
|
||||
/// </summary>
|
||||
public static class RegisterCheck
|
||||
{
|
||||
private enum RangeCheck
|
||||
{
|
||||
MinimumExceeded,
|
||||
MaximumExceeded,
|
||||
DefaultValue,
|
||||
NotConvertible
|
||||
};
|
||||
private static String OutOfRangeMsg(RangeCheck rangeCheck, String value, String range, String name)
|
||||
{
|
||||
var msg = "";
|
||||
switch (rangeCheck)
|
||||
{
|
||||
case RangeCheck.DefaultValue:
|
||||
msg = $"{Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Properties.Resources.StrSetRegisterToDefault} {name}: ({value})";
|
||||
break;
|
||||
case RangeCheck.MaximumExceeded:
|
||||
msg = $"{Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Properties.Resources.StrRegisterValueOutOfRange} {name}: ({value}) - Maximum ({range})";
|
||||
break;
|
||||
case RangeCheck.MinimumExceeded:
|
||||
msg = $"{Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Properties.Resources.StrRegisterValueOutOfRange} {name}: ({value}) - Minimum ({range})";
|
||||
break;
|
||||
case RangeCheck.NotConvertible:
|
||||
msg = $"{Xylem.Common.Hardware.WaterMeter.Genesis.Registers.Properties.Resources.StrRegisterDataTypeUnknown} {name}";
|
||||
break;
|
||||
}
|
||||
return msg;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check the registers for ot of range (below minimum or above maximum) and return empty string
|
||||
/// if impossible to check (unset limits) or in range.
|
||||
/// </summary>
|
||||
/// <param name="regDef"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="msg">feedback message</param>
|
||||
/// <param name="checkIfValueIsDefault">forces a warning if value is set to default</param>
|
||||
/// <returns>
|
||||
/// <see cref="StatusReturn.Warning"/> if default and check for this is required
|
||||
/// <see cref="StatusReturn.Okay"/>if the value is in range or has no range defined
|
||||
/// <see cref="StatusReturn.Failed"/>if value exceeds the limits
|
||||
/// </returns>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Init.
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Mar-21" author="Thomas Wiedebusch">
|
||||
/// - Modified with try catch block.
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Sep-25" author="Thomas Wiedebusch">
|
||||
/// - Returns warning on default, else error or okay.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Feb-13" author="Thomas Wiedebusch">
|
||||
/// - Ignore TimeT.
|
||||
/// </remarks>
|
||||
public static StatusReturn CheckRange(RegisterDefinition regDef, Byte[] value, out String msg,
|
||||
Boolean checkIfValueIsDefault = false)
|
||||
{
|
||||
msg = "";
|
||||
// for all registers without min and max or if type is of TimeT
|
||||
if ((!regDef.Minimum.HasValue && !regDef.Maximum.HasValue) || regDef.DataType == typeof(TimeT))
|
||||
{
|
||||
return StatusReturn.Okay;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var type = regDef.DataType;
|
||||
if (type == typeof(UInt16))
|
||||
{
|
||||
var checkValue = RegisterConverter.ByteArrayToValue<UInt16>(value);
|
||||
if (regDef.Minimum.HasValue && checkValue < regDef.Minimum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(),
|
||||
regDef.Minimum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
|
||||
if (regDef.Maximum.HasValue && checkValue > regDef.Maximum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(),
|
||||
regDef.Maximum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
if (checkIfValueIsDefault && regDef.Default.HasValue && checkValue == regDef.Default.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(),
|
||||
regDef.Default.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Warning;
|
||||
}
|
||||
}
|
||||
else if (type == typeof(UInt32))
|
||||
{
|
||||
var checkValue = RegisterConverter.ByteArrayToValue<UInt32>(value);
|
||||
if (regDef.Minimum.HasValue && checkValue < regDef.Minimum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(),
|
||||
regDef.Minimum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
|
||||
if (regDef.Maximum.HasValue && checkValue > regDef.Maximum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(),
|
||||
regDef.Maximum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
if (checkIfValueIsDefault && regDef.Default.HasValue && checkValue == regDef.Default.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(),
|
||||
regDef.Default.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Warning;
|
||||
}
|
||||
}
|
||||
else if (type == typeof(UInt64))
|
||||
{
|
||||
var checkValue = RegisterConverter.ByteArrayToValue<UInt64>(value);
|
||||
if (regDef.Minimum.HasValue && checkValue < (UInt64)regDef.Minimum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(),
|
||||
regDef.Minimum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
|
||||
if (regDef.Maximum.HasValue && checkValue > (UInt64)regDef.Maximum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(),
|
||||
regDef.Maximum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
if (checkIfValueIsDefault && regDef.Default.HasValue && 0 == checkValue.CompareTo(regDef.Default.Value))
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(),
|
||||
regDef.Default.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Warning;
|
||||
}
|
||||
}
|
||||
else if (type == typeof(Int16))
|
||||
{
|
||||
var checkValue = RegisterConverter.ByteArrayToValue<Int16>(value);
|
||||
if (regDef.Minimum.HasValue && checkValue < regDef.Minimum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(),
|
||||
regDef.Minimum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
|
||||
if (regDef.Maximum.HasValue && checkValue > regDef.Maximum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(),
|
||||
regDef.Maximum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
if (checkIfValueIsDefault && regDef.Default.HasValue && checkValue == regDef.Default.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(),
|
||||
regDef.Default.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Warning;
|
||||
}
|
||||
}
|
||||
else if (type == typeof(Int32))
|
||||
{
|
||||
var checkValue = RegisterConverter.ByteArrayToValue<Int32>(value);
|
||||
if (regDef.Minimum.HasValue && checkValue < regDef.Minimum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(),
|
||||
regDef.Minimum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
|
||||
if (regDef.Maximum.HasValue && checkValue > regDef.Maximum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(),
|
||||
regDef.Maximum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
if (checkIfValueIsDefault && regDef.Default.HasValue && checkValue == regDef.Default.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(),
|
||||
regDef.Default.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Warning;
|
||||
}
|
||||
}
|
||||
else if (type == typeof(Int64))
|
||||
{
|
||||
var checkValue = RegisterConverter.ByteArrayToValue<Int64>(value);
|
||||
if (regDef.Minimum.HasValue && checkValue < regDef.Minimum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(),
|
||||
regDef.Minimum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
|
||||
if (regDef.Maximum.HasValue && checkValue > regDef.Maximum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(),
|
||||
regDef.Maximum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
if (checkIfValueIsDefault && regDef.Default.HasValue && checkValue == regDef.Default.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(),
|
||||
regDef.Default.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Warning;
|
||||
}
|
||||
}
|
||||
else if (type == typeof(Byte) || type == typeof(Enum8))
|
||||
{
|
||||
var checkValue = RegisterConverter.ByteArrayToValue<Byte>(value);
|
||||
if (regDef.Minimum.HasValue && checkValue < regDef.Minimum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(),
|
||||
regDef.Minimum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
|
||||
if (regDef.Maximum.HasValue && checkValue > regDef.Maximum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(),
|
||||
regDef.Maximum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
if (checkIfValueIsDefault && regDef.Default.HasValue && checkValue == regDef.Default.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(),
|
||||
regDef.Default.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Warning;
|
||||
}
|
||||
}
|
||||
else if (type == typeof(SByte))
|
||||
{
|
||||
var checkValue = RegisterConverter.ByteArrayToValue<SByte>(value);
|
||||
if (regDef.Minimum.HasValue && checkValue < regDef.Minimum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MinimumExceeded, checkValue.ToString(),
|
||||
regDef.Minimum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
|
||||
if (regDef.Maximum.HasValue && checkValue > regDef.Maximum.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.MaximumExceeded, checkValue.ToString(),
|
||||
regDef.Maximum.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
if (checkIfValueIsDefault && regDef.Default.HasValue && checkValue == regDef.Default.Value)
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.DefaultValue, checkValue.ToString(),
|
||||
regDef.Default.Value.ToString(), regDef.GetIdent());
|
||||
return StatusReturn.Warning;
|
||||
}
|
||||
}
|
||||
else if (type == typeof(Boolean))
|
||||
{
|
||||
// call function to force exception if out of range
|
||||
RegisterConverter.ByteArrayToValue<Boolean>(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = OutOfRangeMsg(RangeCheck.NotConvertible, "", "", regDef.GetIdent());
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
msg = e.Message;
|
||||
return StatusReturn.Failed;
|
||||
}
|
||||
|
||||
// the value is in range and not on default
|
||||
return StatusReturn.Okay;
|
||||
}
|
||||
}
|
||||
}
|
||||
+488
@@ -0,0 +1,488 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
|
||||
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to convert genesis register values store in bytes to data types and back
|
||||
/// </summary>
|
||||
public static class RegisterConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Convert single register from Genesis and return a message of the result. The value input is a byte array
|
||||
/// with the LSB at the index 0 and the MSB at the highest index. A string is sorted with the first character
|
||||
/// at index 0, this does not need to be reversed as it is readable by default.
|
||||
/// The raw value is swapped byte-wise to get a human-readable value with MSB at leftmost and LSB at rightmost.
|
||||
/// The message contains the register name, the swapped raw value and on the converted value.
|
||||
/// </summary>
|
||||
/// <param name="registerDefinition"></param>
|
||||
/// <param name="regRawByteArray">the raw byte array is always % 4 0 padded for unused elements, the LSB is
|
||||
/// on index 0 (little endian, LSB first)</param>
|
||||
/// <returns>message with register name, swapped raw value and converted value</returns>
|
||||
/// <remarks date="2023-Jul-19" author="Thomas Wiedebusch">
|
||||
/// - Initial.
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Sep-05" author="Thomas Wiedebusch">
|
||||
/// - Reworked.
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Sep-27" author="Thomas Wiedebusch">
|
||||
/// - Avoid logging of encryption key.
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Mar-20" author="Thomas Wiedebusch">
|
||||
/// - Hash password and encryption key with SHA256.
|
||||
/// </remarks>
|
||||
public static String GetRegisterContentText(RegisterDefinition registerDefinition, Byte[] regRawByteArray)
|
||||
{
|
||||
String msg;
|
||||
String registerName = "?";
|
||||
try
|
||||
{
|
||||
registerName = registerDefinition.GetIdent();
|
||||
// getting a result value as text of the data type e.g. UInt32
|
||||
var strValue = ConvertToText(regRawByteArray, registerDefinition.DataType);
|
||||
// remove non-printable char of the converted result
|
||||
var strValueResult = Regex.Replace(strValue, @"\p{C}+", string.Empty);
|
||||
|
||||
// use hashed output being able to compare written and read back values
|
||||
String strRawResult;
|
||||
if (registerName.Contains("EncryptionKey") || registerName.Contains("Password"))
|
||||
{
|
||||
strRawResult = GetRegisterRawText(registerDefinition, regRawByteArray, encryptData: true);
|
||||
msg = $"{registerName}: ({strRawResult})h";
|
||||
}
|
||||
else
|
||||
{
|
||||
strRawResult = GetRegisterRawText(registerDefinition, regRawByteArray);
|
||||
msg = $"{registerName}: ({strRawResult})h ({strValueResult})";
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ApplicationException($"Cannot convert register {registerName}");
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert single register from Genesis and return a message of the result. The value input is a byte array
|
||||
/// with the LSB at the index 0 and the MSB at the highest index. A string is sorted with the first character
|
||||
/// at index 0, this does not need to be reversed as it is readable by default.
|
||||
/// The raw value is swapped byte-wise to get a human-readable value with MSB at leftmost and LSB at rightmost.
|
||||
/// The message contains the swapped raw value.
|
||||
/// </summary>
|
||||
/// <param name="registerDefinition"></param>
|
||||
/// <param name="regRawByteArray">the raw byte array is always % 4 0 padded for unused elements, the LSB is
|
||||
/// on index 0 (little endian, LSB first)</param>
|
||||
/// <param name="encryptData">data encryption for data required SHA256 hash instead of clear text</param>
|
||||
/// <returns>message with swapped raw value</returns>
|
||||
/// <remarks date="2023-Sep-05" author="Thomas Wiedebusch">
|
||||
/// - Initial.
|
||||
/// </remarks>
|
||||
/// <remarks date="2024-Mar-20" author="Thomas Wiedebusch">
|
||||
/// - Hash password and encryption key with SHA256.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Oct-10" author="Thomas Wiedebusch">
|
||||
/// - Data size introduced including the new 'ByteArray' type and size to get rid of new defined 'uintxx_t'
|
||||
/// data types.
|
||||
/// </remarks>
|
||||
public static String GetRegisterRawText(RegisterDefinition registerDefinition, Byte[] regRawByteArray,
|
||||
Boolean encryptData = false)
|
||||
{
|
||||
String msg;
|
||||
var strRawResult = "?";
|
||||
try
|
||||
{
|
||||
// inverse the raw value to get a readable byte order MSB left and LSB last right position
|
||||
var tmpList = regRawByteArray.ToList();
|
||||
|
||||
// reverse the list if it is not a string or the UInt48, UInt72, UInt88,UInt96 or UInt128 which
|
||||
// will be used as placeholder for a string
|
||||
if (registerDefinition.DataType != typeof(String) &&
|
||||
registerDefinition.DataType != typeof(ByteArray))
|
||||
{
|
||||
tmpList.Reverse();
|
||||
}
|
||||
|
||||
// use hashed output being able to compare written and read back values
|
||||
if (encryptData)
|
||||
{
|
||||
var hash = SHA256.Create().ComputeHash(regRawByteArray);
|
||||
strRawResult = BitConverter.ToString(hash.ToArray());
|
||||
msg = $"SHA256 - {strRawResult}";
|
||||
}
|
||||
else
|
||||
{
|
||||
strRawResult = BitConverter.ToString(tmpList.ToArray());
|
||||
msg = $"{strRawResult}";
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
msg = $"{strRawResult}";
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Preset the raw byte array with the default value.
|
||||
/// </summary>
|
||||
/// <param name="registerDefinition"></param>
|
||||
/// <param name="regRawByteArray"></param>
|
||||
/// <returns>true if value could be set</returns>
|
||||
/// <remarks date="2023-Sep-05" author="Thomas Wiedebusch">
|
||||
/// - Initial.
|
||||
/// </remarks>
|
||||
public static Boolean SetToDefault(RegisterDefinition registerDefinition, out Byte[] regRawByteArray)
|
||||
{
|
||||
if (registerDefinition.Default.HasValue)
|
||||
{
|
||||
// will always return an Int64 value Byte[8] !
|
||||
regRawByteArray = ValueToByteArray(registerDefinition.Default);
|
||||
return true;
|
||||
}
|
||||
|
||||
regRawByteArray = new Byte[] { 0 };
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a value (T) into byte[]
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to convert from</typeparam>
|
||||
/// <param name="value">value to convert</param>
|
||||
/// <returns>byte[] like it store in genesis meter</returns>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Init.
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jul-19" author="Thomas Wiedebusch">
|
||||
/// - Used <see cref="TimeT"/> to calculate time in UTC based on 01. Jan 2000
|
||||
/// and the given offset in seconds.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Oct-10" author="Thomas Wiedebusch">
|
||||
/// - Data size introduced including the new 'ByteArray' type and size to get rid of new defined 'uintxx_t'
|
||||
/// data types.
|
||||
/// </remarks>
|
||||
public static Byte[] ValueToByteArray<T>(T value)
|
||||
{
|
||||
var converted = new Byte[] { 0 };
|
||||
if (value == null)
|
||||
return converted;
|
||||
|
||||
if (value.GetType() == typeof(TimeT))
|
||||
{
|
||||
var teaTime = value as TimeT;
|
||||
if (teaTime != null)
|
||||
{
|
||||
// the value contains always the seconds since 01.Jan 2000
|
||||
var intValue = Convert.ToInt32(teaTime.SecondsSince2000);
|
||||
return BitConverter.GetBytes(intValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (value is Boolean)
|
||||
{
|
||||
var tmp = Convert.ToBoolean(value);
|
||||
return BitConverter.GetBytes(tmp);
|
||||
}
|
||||
|
||||
if (value is UInt16)
|
||||
{
|
||||
var tmp = Convert.ToUInt16(value);
|
||||
return BitConverter.GetBytes(tmp);
|
||||
}
|
||||
|
||||
if (value is UInt32)
|
||||
{
|
||||
var tmp = Convert.ToUInt32(value);
|
||||
return BitConverter.GetBytes(tmp);
|
||||
}
|
||||
|
||||
if (value is UInt64)
|
||||
{
|
||||
var tmp = Convert.ToUInt64(value);
|
||||
return BitConverter.GetBytes(tmp);
|
||||
}
|
||||
|
||||
if (value is Int16)
|
||||
{
|
||||
var intValue = Convert.ToInt16(value);
|
||||
return BitConverter.GetBytes(intValue);
|
||||
}
|
||||
|
||||
if (value is Int32)
|
||||
{
|
||||
var intValue = Convert.ToInt32(value);
|
||||
return BitConverter.GetBytes(intValue);
|
||||
}
|
||||
|
||||
if (value is Int64)
|
||||
{
|
||||
var tmp = Convert.ToInt64(value);
|
||||
return BitConverter.GetBytes(tmp);
|
||||
}
|
||||
|
||||
if (value is String)
|
||||
{
|
||||
return Encoding.ASCII.GetBytes(value.ToString());
|
||||
}
|
||||
|
||||
if (value is Byte || value is Enum8)
|
||||
{
|
||||
converted[0] = Convert.ToByte(value);
|
||||
}
|
||||
else if (value is SByte)
|
||||
{
|
||||
return BitConverter.GetBytes(Convert.ToSByte(value));
|
||||
}
|
||||
|
||||
else if (value is Byte[] || value is ByteArray)
|
||||
{
|
||||
return (Byte[])Convert.ChangeType(value, typeof(Byte[]));
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
throw new ApplicationException($"Data type {typeof(T)} is unknown!");
|
||||
}
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build the result of the array as text.
|
||||
/// </summary>
|
||||
/// <param name="rawByteArray">byte value for conversion</param>
|
||||
/// <param name="type">type of the result</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Init.
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jul-19" author="Thomas Wiedebusch">
|
||||
/// - Used <see cref="TimeT"/> to calculate time in UTC based on 01. Jan 2000
|
||||
/// and the given offset in seconds.
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Oct-10" author="Thomas Wiedebusch">
|
||||
/// - Data size introduced including the new 'ByteArray' type and size to get rid of new defined 'uintxx_t'
|
||||
/// data types.
|
||||
/// </remarks>
|
||||
public static String ConvertToText(Byte[] rawByteArray, Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (type == typeof(Boolean))
|
||||
{
|
||||
return ByteArrayToValue<Boolean>(rawByteArray).ToString();
|
||||
}
|
||||
|
||||
if (type == typeof(TimeT))
|
||||
{
|
||||
var teaTime = ByteArrayToValue<TimeT>(rawByteArray);
|
||||
return teaTime.ToString();
|
||||
}
|
||||
if (type == typeof(UInt16))
|
||||
{
|
||||
return ByteArrayToValue<UInt16>(rawByteArray).ToString();
|
||||
}
|
||||
if (type == typeof(UInt32))
|
||||
{
|
||||
return ByteArrayToValue<UInt32>(rawByteArray).ToString();
|
||||
}
|
||||
if (type == typeof(UInt64))
|
||||
{
|
||||
return ByteArrayToValue<UInt64>(rawByteArray).ToString();
|
||||
}
|
||||
if (type == typeof(Int16))
|
||||
{
|
||||
return ByteArrayToValue<Int16>(rawByteArray).ToString();
|
||||
}
|
||||
if (type == typeof(Int32))
|
||||
{
|
||||
return ByteArrayToValue<Int32>(rawByteArray).ToString();
|
||||
}
|
||||
if (type == typeof(Int64))
|
||||
{
|
||||
return ByteArrayToValue<Int64>(rawByteArray).ToString();
|
||||
}
|
||||
if (type == typeof(Byte) || type == typeof(Enum8))
|
||||
{
|
||||
return ByteArrayToValue<Byte>(rawByteArray).ToString();
|
||||
}
|
||||
if (type == typeof(ByteArray) || type == typeof(Byte[]))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
if (type == typeof(SByte))
|
||||
{
|
||||
return ByteArrayToValue<SByte>(rawByteArray).ToString();
|
||||
}
|
||||
|
||||
return ByteArrayToValue<String>(rawByteArray);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// convert form byte (meter) to data type
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to cast in</typeparam>
|
||||
/// <param name="rawByteArray">byte value for conversion</param>
|
||||
/// <returns>converted value</returns>
|
||||
/// <remarks date="????" author="Roland Drabesch">
|
||||
/// - Init.
|
||||
/// </remarks>
|
||||
/// <remarks date="2023-Jul-19" author="Thomas Wiedebusch">
|
||||
/// - Used <see cref="TimeT"/> to calculate time in UTC based on 01. Jan 2000
|
||||
/// and the given offset in seconds.
|
||||
/// - String removed from first zero in string until the end to avoid ghost signs!
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Mar-31" author="Thomas Wiedebusch/Roland Drabesch">
|
||||
/// - Return default (T) on input == null;
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Jun-16" author="Thomas Wiedebusch/Roland Drabesch">
|
||||
/// - Padding bytes if input doesn't fit the required size;
|
||||
/// </remarks>
|
||||
/// <remarks date="2025-Oct-10" author="Thomas Wiedebusch">
|
||||
/// - Data size introduced including the new 'ByteArray' type and size to get rid of new defined 'uintxx_t'
|
||||
/// data types.
|
||||
/// </remarks>
|
||||
public static T ByteArrayToValue<T>(Byte[] rawByteArray)
|
||||
{
|
||||
if (rawByteArray == null)
|
||||
return default(T);
|
||||
|
||||
try
|
||||
{
|
||||
Object convertedObj = null;
|
||||
var type = typeof(T);
|
||||
|
||||
if (type == typeof(Byte) || type == typeof(Enum8))
|
||||
{
|
||||
convertedObj = rawByteArray[0];
|
||||
}
|
||||
else if (type == typeof(Boolean))
|
||||
{
|
||||
convertedObj = BitConverter.ToBoolean(rawByteArray, 0);
|
||||
}
|
||||
else if (type == typeof(SByte))
|
||||
{
|
||||
// set all others to 0 as only the LSB is of interest
|
||||
for (var idx = 1; idx < rawByteArray.Length; idx++)
|
||||
rawByteArray[idx] = 0;
|
||||
convertedObj = Convert.ToSByte((SByte)rawByteArray[0]);
|
||||
}
|
||||
else if (type == typeof(Boolean))
|
||||
{
|
||||
convertedObj = Convert.ToBoolean(rawByteArray[0]);
|
||||
}
|
||||
else if (type == typeof(String))
|
||||
{
|
||||
// the value may contain zeros at the beginning of the value-record, these have to be removed
|
||||
var zeroPaddingCounter = 0;
|
||||
while (rawByteArray.Length > zeroPaddingCounter && rawByteArray[zeroPaddingCounter] == 0)
|
||||
zeroPaddingCounter += 1;
|
||||
var trimmedValue = new Byte[rawByteArray.Length - zeroPaddingCounter];
|
||||
Int32 i;
|
||||
for (i = 0; i < rawByteArray.Length - zeroPaddingCounter; i++)
|
||||
trimmedValue[i] = rawByteArray[i + zeroPaddingCounter];
|
||||
|
||||
// as a string is terminated with a zero all following elements after including the initial
|
||||
// zero have to be removed. This is caused due to the 4 byte chunks which will be sent.
|
||||
var validCharCtr = 0;
|
||||
// find the first zero in the trimmed value array
|
||||
while (trimmedValue.Length > validCharCtr && trimmedValue[validCharCtr] != 0)
|
||||
validCharCtr += 1;
|
||||
// reserve space for valid chr plus the trailing 0 which will be added if
|
||||
var rawResult = new Byte[validCharCtr + 1];
|
||||
for (i = 0; i < validCharCtr; i++)
|
||||
rawResult[i] = trimmedValue[i];
|
||||
if (validCharCtr < trimmedValue.Length)
|
||||
{
|
||||
rawResult[i] = 0;
|
||||
convertedObj = Encoding.ASCII.GetString(rawResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
convertedObj = Encoding.ASCII.GetString(trimmedValue);
|
||||
}
|
||||
}
|
||||
else if (type == typeof(TimeT))
|
||||
{
|
||||
// the value contains always the seconds since 01.Jan 2000
|
||||
convertedObj = new TimeT { SecondsSince2000 = ByteArrayToValue<Int32>(rawByteArray) };
|
||||
}
|
||||
|
||||
if (convertedObj != null)
|
||||
{
|
||||
return (T)Convert.ChangeType(convertedObj, type);
|
||||
}
|
||||
|
||||
// Padding byte array to required byte size will be used for numerical values
|
||||
var requiredSize = Marshal.SizeOf(typeof(T));
|
||||
var paddedRawByteArray = new Byte[requiredSize];
|
||||
var inputArraySize = rawByteArray.Length;
|
||||
for (var ctr = 0; ctr < requiredSize; ctr++)
|
||||
{
|
||||
if (inputArraySize > 0)
|
||||
{
|
||||
paddedRawByteArray[ctr] = rawByteArray[ctr];
|
||||
inputArraySize--;
|
||||
}
|
||||
else
|
||||
{
|
||||
paddedRawByteArray[ctr] = 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
// For all numerical values the padded raw byte array will be used
|
||||
if (type == typeof(UInt16))
|
||||
{
|
||||
convertedObj = BitConverter.ToUInt16(paddedRawByteArray, 0);
|
||||
}
|
||||
else if (type == typeof(UInt32))
|
||||
{
|
||||
convertedObj = BitConverter.ToUInt32(paddedRawByteArray, 0);
|
||||
}
|
||||
else if (type == typeof(UInt64))
|
||||
{
|
||||
convertedObj = BitConverter.ToUInt64(paddedRawByteArray, 0);
|
||||
}
|
||||
else if (type == typeof(Int16))
|
||||
{
|
||||
convertedObj = BitConverter.ToInt16(paddedRawByteArray, 0);
|
||||
}
|
||||
else if (type == typeof(Int32))
|
||||
{
|
||||
convertedObj = BitConverter.ToInt32(paddedRawByteArray, 0);
|
||||
}
|
||||
else if (type == typeof(Int64))
|
||||
{
|
||||
convertedObj = BitConverter.ToInt64(paddedRawByteArray, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ApplicationException($"Data type {type} not implemented");
|
||||
}
|
||||
|
||||
return (T)Convert.ChangeType(convertedObj, type);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ApplicationException($"Data type {typeof(T)} not implemented. Message: {ex.Message}");
|
||||
//return default(T);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers.Json;
|
||||
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers.DataTypes;
|
||||
using Xylem.Common.Hardware.WaterMeter.WaterMeterRegisters;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers
|
||||
{
|
||||
/// <summary>
|
||||
/// describe a genesis register.
|
||||
/// need for read and write
|
||||
/// all request protocols uses RegisterDefinition
|
||||
/// </summary>
|
||||
public class RegisterDefinition : IRegister
|
||||
{
|
||||
/// <summary>
|
||||
/// name of the application which uses the register
|
||||
/// </summary>
|
||||
public String AppName;
|
||||
|
||||
/// <summary>
|
||||
/// address of application which is the base address for the register
|
||||
/// </summary>
|
||||
/// <remarks date="2025-Aug-05" author="Thomas Wiedebusch">
|
||||
/// - AppAddress from Byte to UInt16 including typecast for Byte[] return. To external, it will be used as Byte
|
||||
/// as before this change. But being able to parse the configuration.json with OPTICALINTERFACE using the
|
||||
/// AppAddress 256, which exceeds the byte range as this isn't a real application, but will be used to identify
|
||||
/// the interface (configuration.json) version.
|
||||
/// </remarks>
|
||||
public UInt16 AppAddress;
|
||||
|
||||
/// <summary>
|
||||
/// address of the register in this application
|
||||
/// </summary>
|
||||
public Byte RegAddressInApp;
|
||||
|
||||
/// <summary>
|
||||
/// data type of register
|
||||
/// </summary>
|
||||
public Type DataType;
|
||||
|
||||
/// <summary>
|
||||
/// size of the data to support the SizeOf implementation
|
||||
/// </summary>
|
||||
public Int32 DataSize;
|
||||
|
||||
/// <summary>
|
||||
/// length of one chunk during communication
|
||||
/// </summary>
|
||||
/// <returns>returns the expected length of the data from register</returns>
|
||||
public const Int32 ChunkSize = 4;
|
||||
|
||||
/// <summary>
|
||||
/// name of the register
|
||||
/// </summary>
|
||||
public String RegisterName;
|
||||
|
||||
/// <summary>
|
||||
/// Indicated if the register is a for this meter
|
||||
/// </summary>
|
||||
public Boolean IsAvailable;
|
||||
|
||||
/// <inheritdoc />
|
||||
public String GetIdent()
|
||||
{
|
||||
return $"{AppName}_{RegisterName}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// address combined of application and register in this application
|
||||
/// </summary>
|
||||
/// <remarks date="2025-Aug-05" author="Thomas Wiedebusch">
|
||||
/// - AppAddress from Byte to UInt16 including typecast for Byte[] return. To external, it will be used as Byte
|
||||
/// as before this change. But being able to parse the configuration.json with OPTICALINTERFACE using the
|
||||
/// AppAddress 256, which exceeds the byte range as this isn't a real application, but will be used to identify
|
||||
/// the interface (configuration.json) version.
|
||||
/// </remarks>
|
||||
public Byte[] RegisterAddress
|
||||
{
|
||||
get { return new[] { (Byte)AppAddress, RegAddressInApp }; }
|
||||
set
|
||||
{
|
||||
AppAddress = value[0];
|
||||
RegAddressInApp = value[1];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// describe the accessibility for different login levels
|
||||
/// </summary>
|
||||
public Details RegisterDetail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// default value
|
||||
/// </summary>
|
||||
public Int64? Default { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// minimum value
|
||||
/// </summary>
|
||||
public Int64? Minimum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// maximum value
|
||||
/// </summary>
|
||||
public Int64? Maximum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Read restore capability for firmware updates
|
||||
/// </summary>
|
||||
public StaticType RestoreCapability;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Int32 CompareTo(Object obj)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers
|
||||
{
|
||||
/// <summary>
|
||||
/// Recovery rules
|
||||
/// </summary>
|
||||
public enum RegisterRecoveryAccess
|
||||
{
|
||||
/// <summary>
|
||||
/// Set this register always
|
||||
/// </summary>
|
||||
SetAlways,
|
||||
/// <summary>
|
||||
/// Set register if it has the default value
|
||||
/// </summary>
|
||||
SetIfDefault,
|
||||
/// <summary>
|
||||
/// Set the register if it is zero
|
||||
/// </summary>
|
||||
SetIfZero
|
||||
|
||||
}
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis.Registers
|
||||
{
|
||||
public static class Register
|
||||
{
|
||||
public static class System
|
||||
{
|
||||
public static readonly String CheckFwPresence = "SYSTEM_CheckPresence";
|
||||
public static readonly String CheckFwCrc = "SYSTEM_CRC";
|
||||
public static readonly String TriggerFwUpgrade = "SYSTEM_TriggerUpgrade";
|
||||
public static readonly String CoreRevision = "SYSTEM_CoreRevision";
|
||||
public static readonly String MonotonicSeconds = "SYSTEM_MonotonicSeconds";
|
||||
public static readonly String MetrologyUpgradePermission = "SYSTEM_UpgradePermissions";
|
||||
}
|
||||
|
||||
public static class Customer
|
||||
{
|
||||
public static readonly String AlarmStatus0 = "CUSTOMER_AlarmStatus0";
|
||||
public static readonly String AlarmStatus1 = "CUSTOMER_AlarmStatus1";
|
||||
public static readonly String TriggerAlarmCancel = "CUSTOMER_TriggerAlarmCancel";
|
||||
public static readonly String AlarmStatus2 = "CUSTOMER_AlarmStatus2";
|
||||
public static readonly String AlarmStatus3 = "CUSTOMER_AlarmStatus3";
|
||||
public static readonly String AlarmStatus4 = "CUSTOMER_AlarmStatus4";
|
||||
public static readonly String AlarmStatus5 = "CUSTOMER_AlarmStatus5";
|
||||
public static readonly String AlarmStatus6 = "CUSTOMER_AlarmStatus6";
|
||||
public static readonly String AlarmStatus7 = "CUSTOMER_AlarmStatus7";
|
||||
public static readonly String StoreConfiguration = "CUSTOMER_StoreConfiguration";
|
||||
};
|
||||
|
||||
|
||||
public static class Configexchange
|
||||
{
|
||||
public static readonly String Privilege = "CONFIGEXCHANGE_Privilege";
|
||||
public static readonly String Password = "CONFIGEXCHANGE_Password";
|
||||
public static readonly String PcbSerialNumber = "CONFIGEXCHANGE_PCBSerialNumber";
|
||||
public static readonly String FileOpen = "CONFIGEXCHANGE_FOpen";
|
||||
public static readonly String FileClose = "CONFIGEXCHANGE_FClose";
|
||||
public static readonly String FileWrite = "CONFIGEXCHANGE_FWrite";
|
||||
public static readonly String FileRead = "CONFIGEXCHANGE_FRead";
|
||||
public static readonly String GetFilePointerOffset = "CONFIGEXCHANGE_FTell";
|
||||
public static readonly String SetFilePointerOffset = "CONFIGEXCHANGE_FSeek";
|
||||
public static readonly String FileRemove = "CONFIGEXCHANGE_Remove";
|
||||
public static readonly String Catalogue = "CONFIGEXCHANGE_Catalogue";
|
||||
};
|
||||
|
||||
public static class Genesisflow
|
||||
{
|
||||
public static readonly String SampleRate = "GENESISFLOW_SampleRate";
|
||||
public static readonly String MeterSize = "GENESISFLOW_MeterSize";
|
||||
public static readonly String CalFactor1 = "GENESISFLOW_CalFactor1";
|
||||
public static readonly String CalFactor2 = "GENESISFLOW_CalFactor2";
|
||||
public static readonly String CalFactor3 = "GENESISFLOW_CalFactor3";
|
||||
public static readonly String ZeroOffset1 = "GENESISFLOW_ZeroOffset1";
|
||||
public static readonly String ZeroOffset2 = "GENESISFLOW_ZeroOffset2";
|
||||
public static readonly String ZeroOffset3 = "GENESISFLOW_ZeroOffset3";
|
||||
public static readonly String ResetAccumulators = "GENESISFLOW_ResetAccumulators";
|
||||
public static readonly String ForwardArrow = "GENESISFLOW_ForwardArrow";
|
||||
public static readonly String LedMode = "GENESISFLOW_LedMode";
|
||||
public static readonly String StoreCalibration = "GENESISFLOW_StoreCalibration";
|
||||
public static readonly String TriggerActive = "GENESISFLOW_TriggerActive";
|
||||
public static readonly String TriggerIdle = "GENESISFLOW_TriggerIdle";
|
||||
public static readonly String FirstHitPercent1 = "GENESISFLOW_FirstHitPercent1";
|
||||
public static readonly String FirstHitPercent2 = "GENESISFLOW_FirstHitPercent2";
|
||||
public static readonly String FirstHitPercent3 = "GENESISFLOW_FirstHitPercent3";
|
||||
public static readonly String FirstHitShift = "GENESISFLOW_FirstHitShift";
|
||||
public static readonly String FirstHitUpdatePeriod = "GENESISFLOW_FirstHitUpdatePeriod";
|
||||
public static readonly String ToFTempOffset1 = "GENESISFLOW_ToFTempOffset1";
|
||||
public static readonly String ToFTempOffset2 = "GENESISFLOW_ToFTempOffset2";
|
||||
public static readonly String ToFTempOffset3 = "GENESISFLOW_ToFTempOffset3";
|
||||
public static readonly String ToFTempCalibrate = "GENESISFLOW_ToFTempCalibrate";
|
||||
public static readonly String StoreConfiguration = "GENESISFLOW_StoreConfiguration";
|
||||
public static readonly String AmplitudePeakDetectEnd = "GENESISFLOW_AmplitudePeakDetectEnd";
|
||||
public static readonly String FirstHitLvlDown1 = "GENESISFLOW_FirstHitLvlDown1";
|
||||
public static readonly String FirstHitLvlDown2 = "GENESISFLOW_FirstHitLvlDown2";
|
||||
public static readonly String FirstHitLvlDown3 = "GENESISFLOW_FirstHitLvlDown3";
|
||||
public static readonly String FirstHitLvlUp1 = "GENESISFLOW_FirstHitLvlUp1";
|
||||
public static readonly String FirstHitLvlUp2 = "GENESISFLOW_FirstHitLvlUp2";
|
||||
public static readonly String FirstHitLvlUp3 = "GENESISFLOW_FirstHitLvlUp3";
|
||||
public static readonly String StartHit = "GENESISFLOW_StartHit";
|
||||
public static readonly String NumFirePulses = "GENESISFLOW_NumFirePulses";
|
||||
public static readonly String LookupFileCrc = "GENESISFLOW_LookupFileCrc";
|
||||
public static readonly String DisplayUnits = "GENESISFLOW_DisplayUnits";
|
||||
public static readonly String DisplayPow10 = "GENESISFLOW_DisplayPow10";
|
||||
public static readonly String SealDisplay = "GENESISFLOW_SealDisplay";
|
||||
};
|
||||
|
||||
public static class Powermon
|
||||
{
|
||||
public static readonly String BatteryVoltage = "POWERMON_BatteryVoltage";
|
||||
public static readonly String BatteryQuantity = "POWERMON_BatteryQuantity";
|
||||
public static readonly String BatteryDrainedLoad = "POWERMON_TotalUsedCharge";
|
||||
public static readonly String BatteryInitialLoad = "POWERMON_BatteryMilliAHrRating";
|
||||
public static readonly String BatteryExceededSeconds = "POWERMON_TotalUsedSeconds";
|
||||
public static readonly String StoreConfiguration = "POWERMON_StoreConfiguration";
|
||||
public static readonly String RemainingSeconds = "POWERMON_RemainingSeconds";
|
||||
}
|
||||
|
||||
|
||||
public static class Sensusradio
|
||||
{
|
||||
public static readonly String FrequencyIndicator = "SENSUSRADIO_FrequencyIndicator";
|
||||
public static readonly String WakeupInterval = "SENSUSRADIO_WakeupInterval";
|
||||
public static readonly String SystemState = "SENSUSRADIO_SystemState";
|
||||
public static readonly String StoreConfiguration = "SENSUSRADIO_StoreConfiguration";
|
||||
public static readonly String EncryptionKey = "SENSUSRADIO_EncryptionKey";
|
||||
}
|
||||
|
||||
public static class Metrologyasst
|
||||
{
|
||||
public static readonly String PulseEvenDistribution = "METROLOGYASST_PulseEvenDistribution";
|
||||
public static readonly String PulseMode = "METROLOGYASST_PulseMode";
|
||||
public static readonly String PressurePresent = "METROLOGYASST_PressurePresent";
|
||||
public static readonly String StoreConfiguration = "METROLOGYASST_StoreConfiguration";
|
||||
public static readonly String FlowUnits = "METROLOGYASST_FlowUnits";
|
||||
}
|
||||
|
||||
public static class Irda
|
||||
{
|
||||
public static readonly String PulseSequence = "IRDA_PulseSequence";
|
||||
public static readonly String AdapterId = "IRDA_AdapterID";
|
||||
public static readonly String StoreConfiguration = "IRDA_StoreConfiguration";
|
||||
}
|
||||
|
||||
public static class Logger
|
||||
{
|
||||
public static readonly String StoreConfiguration = "LOGGER_StoreConfiguration";
|
||||
}
|
||||
|
||||
public static List<String> GetAvoidLogRegisters()
|
||||
{
|
||||
return new List<String>()
|
||||
{
|
||||
Configexchange.Privilege,
|
||||
Configexchange.Password,
|
||||
Configexchange.FileOpen,
|
||||
Configexchange.FileClose,
|
||||
Configexchange.FileWrite,
|
||||
Configexchange.FileRead,
|
||||
Configexchange.Catalogue,
|
||||
Configexchange.GetFilePointerOffset,
|
||||
Configexchange.SetFilePointerOffset,
|
||||
Configexchange.FileRemove,
|
||||
|
||||
//Sensusradio.EncryptionKey
|
||||
};
|
||||
}
|
||||
public static List<String> GetMinRequiredRegisters()
|
||||
{
|
||||
return new List<String>()
|
||||
{
|
||||
System.CheckFwPresence,
|
||||
System.CheckFwCrc,
|
||||
System.TriggerFwUpgrade,
|
||||
System.CoreRevision,
|
||||
System.MonotonicSeconds,
|
||||
System.MetrologyUpgradePermission,
|
||||
|
||||
Customer.AlarmStatus0,
|
||||
Customer.AlarmStatus1,
|
||||
Customer.TriggerAlarmCancel,
|
||||
Customer.AlarmStatus2,
|
||||
Customer.AlarmStatus3,
|
||||
Customer.AlarmStatus4,
|
||||
Customer.AlarmStatus5,
|
||||
Customer.AlarmStatus6,
|
||||
Customer.AlarmStatus7,
|
||||
Customer.StoreConfiguration,
|
||||
|
||||
Configexchange.Privilege,
|
||||
Configexchange.Password,
|
||||
Configexchange.PcbSerialNumber,
|
||||
Configexchange.FileOpen,
|
||||
Configexchange.FileClose,
|
||||
Configexchange.FileWrite,
|
||||
Configexchange.FileRead,
|
||||
Configexchange.Catalogue,
|
||||
Configexchange.GetFilePointerOffset,
|
||||
Configexchange.SetFilePointerOffset,
|
||||
Configexchange.FileRemove,
|
||||
|
||||
Genesisflow.TriggerActive,
|
||||
Genesisflow.TriggerIdle,
|
||||
Genesisflow.LedMode,
|
||||
Genesisflow.SampleRate,
|
||||
Genesisflow.MeterSize,
|
||||
Genesisflow.LookupFileCrc,
|
||||
Genesisflow.StoreCalibration,
|
||||
Genesisflow.StoreConfiguration,
|
||||
|
||||
Powermon.BatteryVoltage,
|
||||
Powermon.BatteryQuantity,
|
||||
Powermon.BatteryDrainedLoad,
|
||||
Powermon.BatteryInitialLoad,
|
||||
Powermon.BatteryExceededSeconds,
|
||||
Powermon.StoreConfiguration,
|
||||
|
||||
Sensusradio.WakeupInterval,
|
||||
Sensusradio.FrequencyIndicator,
|
||||
Sensusradio.SystemState,
|
||||
Sensusradio.StoreConfiguration,
|
||||
|
||||
Metrologyasst.PulseMode,
|
||||
Metrologyasst.PulseEvenDistribution,
|
||||
Metrologyasst.StoreConfiguration,
|
||||
|
||||
Irda.AdapterId,
|
||||
Irda.PulseSequence,
|
||||
Irda.StoreConfiguration,
|
||||
|
||||
Logger.StoreConfiguration
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns of status from routines
|
||||
/// </summary>
|
||||
public enum StatusReturn
|
||||
{
|
||||
/// <summary>
|
||||
/// undefined status does not fit in any other status defined below
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// skipped test and therefore the return identifies this
|
||||
/// </summary>
|
||||
Skipped,
|
||||
|
||||
/// <summary>
|
||||
/// successfully executed without any restrictions
|
||||
/// </summary>
|
||||
Okay,
|
||||
|
||||
/// <summary>
|
||||
/// execution failed
|
||||
/// </summary>
|
||||
Failed,
|
||||
|
||||
/// <summary>
|
||||
/// inspection needed as warning returns
|
||||
/// </summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// the measurement is 'in-range' for threshold cheks
|
||||
/// </summary>
|
||||
MeasurementInRange,
|
||||
|
||||
/// <summary>
|
||||
/// the measurement is out of range for threshold checks
|
||||
/// </summary>
|
||||
MeasurementOutOfRange,
|
||||
|
||||
/// <summary>
|
||||
/// The setup value is out of range
|
||||
/// </summary>
|
||||
SetupOutOfRange
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication.Genesis
|
||||
{
|
||||
|
||||
public sealed class TaskWatcher
|
||||
{
|
||||
#region Singleton
|
||||
private static readonly Lazy<TaskWatcher>
|
||||
Lazy =
|
||||
new Lazy<TaskWatcher>
|
||||
(() => new TaskWatcher());
|
||||
|
||||
public static TaskWatcher Instance => Lazy.Value;
|
||||
|
||||
#endregion
|
||||
private TaskWatcher()
|
||||
{
|
||||
_tasks = new ConcurrentDictionary<Guid, Task>();
|
||||
}
|
||||
|
||||
|
||||
private readonly ConcurrentDictionary<Guid, Task> _tasks;
|
||||
public void Add(Task t)
|
||||
{
|
||||
_tasks.TryAdd(Guid.NewGuid(), t);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public sealed class ThreadWatcher
|
||||
{
|
||||
#region Singleton
|
||||
private static readonly Lazy<ThreadWatcher>
|
||||
Lazy =
|
||||
new Lazy<ThreadWatcher>
|
||||
(() => new ThreadWatcher());
|
||||
|
||||
public static ThreadWatcher Instance => Lazy.Value;
|
||||
|
||||
#endregion
|
||||
private ThreadWatcher()
|
||||
{
|
||||
_threads = new ConcurrentDictionary<Guid, Thread>();
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<Guid,Thread> _threads;
|
||||
|
||||
|
||||
public void Start(Thread t)
|
||||
{
|
||||
|
||||
t.Start();
|
||||
|
||||
_threads.TryAdd(Guid.NewGuid(), t);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using Common;
|
||||
using log4net;
|
||||
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.C4.diagnosticLed;
|
||||
using TBF.Rig.TestMethods.iPerlCommunication.communication.Utils;
|
||||
|
||||
|
||||
namespace TBF.Rig.RegisterReaders.GenesisRegReader.communication
|
||||
{
|
||||
public class OptoHeadTest : IDisposable
|
||||
{
|
||||
//protected static readonly ILog rfidDataLogger = LogManager.GetLogger("RfidData");
|
||||
private static readonly ILog log = LogManager.GetLogger(typeof(OptoHeadTest));
|
||||
|
||||
private GenesisSmartReader genesisHead;
|
||||
private SerialDriver serialDriver;
|
||||
|
||||
public static SerialDriver BuildConnection(GenesisSmartReader iHead)
|
||||
{
|
||||
return new SerialDriverBuilder()
|
||||
.WithPort($"COM{iHead.RfidComPortNr}")
|
||||
.WithBaudRate(2400)
|
||||
.WithDataBits(8)
|
||||
.WithParity(Parity.None)
|
||||
.WithStopBits(StopBits.One)
|
||||
.WithTimeouts(4000, 2000)
|
||||
.BuildAndConnect();
|
||||
|
||||
}
|
||||
|
||||
public OptoHeadTest(GenesisSmartReader genesisHead)
|
||||
{
|
||||
this.genesisHead = genesisHead;
|
||||
}
|
||||
|
||||
public void CloseConnection()
|
||||
{
|
||||
if (serialDriver != null)
|
||||
serialDriver.CloseConnection();
|
||||
serialDriver = null;
|
||||
}
|
||||
|
||||
public bool ReadSerialNr()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (genesisHead != null)
|
||||
{
|
||||
if (serialDriver == null)
|
||||
serialDriver = BuildConnection(genesisHead);
|
||||
|
||||
log.Debug("ReadSerialNr called for iHead: " + genesisHead.ToString() + " serialDriver: " + serialDriver);
|
||||
RadioService headService = new RadioService(serialDriver);
|
||||
string serialNo = headService.ReadRequest_PCB(ref genesisHead);
|
||||
if (!string.IsNullOrEmpty(serialNo))
|
||||
{
|
||||
log.Info($"Success Serial No: {serialNo} on COM{genesisHead.RfidComPortNr} serialDriver: {serialDriver}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error($"ReadSerialNr(COM{genesisHead.RfidComPortNr}) - Exception:" + ex.Message);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public string ReadRequest_PCB()
|
||||
{
|
||||
if (genesisHead.DebugLevel == DebugMode.Simulate)
|
||||
{
|
||||
return "-OK Simulated response-";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (genesisHead != null)
|
||||
{
|
||||
if (serialDriver == null)
|
||||
serialDriver = BuildConnection(genesisHead);
|
||||
|
||||
RadioService headService = new RadioService(serialDriver);
|
||||
string serialNo = headService.ReadRequest_PCB(ref genesisHead);
|
||||
log.Info($"PCB Number: {serialNo} on COM{genesisHead.RfidComPortNr} serialDriver: {serialDriver}");
|
||||
return serialNo;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("ReadRequest_PCB() - Exception:" + ex.StackTrace);
|
||||
return (ex.Message.ToString());
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set Test mode
|
||||
/// </summary>
|
||||
/// <param name="iHead"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetTestMode()
|
||||
{
|
||||
log.Debug("SetTestMode called for iHead: " + genesisHead.ToString());
|
||||
bool activityModeActive = SetActivityMode_Active();
|
||||
bool optActiveMode = SetOptTestMode();
|
||||
|
||||
log.Debug("SetTestMode result: optoMod-> " + optActiveMode + " meterModeActive ->" + activityModeActive);
|
||||
return (optActiveMode && activityModeActive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set Active mode
|
||||
/// </summary>
|
||||
/// <param name="iHead"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetActiveMode()
|
||||
{
|
||||
log.Debug("SetActiveMode called for iHead: " + genesisHead.ToString());
|
||||
bool optActiveMode = SetOptActiveMode(genesisHead);
|
||||
//bool activityModeIdle = SetActivityMode_Idle();
|
||||
|
||||
return optActiveMode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set Idle mode - only
|
||||
/// </summary>
|
||||
/// <param name="iHead"></param>
|
||||
/// <returns></returns>
|
||||
public bool SetIdleMode()
|
||||
{
|
||||
log.Debug("SetIdleMode called for iHead: " + genesisHead.ToString());
|
||||
bool activityModeIdle = SetActivityMode_Idle();
|
||||
|
||||
return activityModeIdle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set Test mode - string response
|
||||
/// </summary>
|
||||
/// <param name="iHead"></param>
|
||||
/// <param name="isTestModeSuccessful"></param>
|
||||
/// <returns></returns>
|
||||
public string SetTestMode(ref bool isTestModeSuccessful)
|
||||
{
|
||||
if (genesisHead.DebugLevel == DebugMode.Simulate)
|
||||
{
|
||||
isTestModeSuccessful = true;
|
||||
return "-OK Simulated response-";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
bool testMode = SetTestMode();
|
||||
isTestModeSuccessful = testMode;
|
||||
return testMode ? "Set Test Mode - OK" : "Set Test Mode - FAILED";
|
||||
}catch (Exception ex)
|
||||
{
|
||||
log.Error("SetTestMode() - Exception:" + ex.StackTrace);
|
||||
return "Set Test Mode - Exception";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set Optical -> Test mode
|
||||
/// </summary>
|
||||
/// <param name="iHead"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private bool SetOptTestMode()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (genesisHead != null)
|
||||
{
|
||||
if (serialDriver == null)
|
||||
serialDriver = BuildConnection(genesisHead);
|
||||
|
||||
RadioService headService = new RadioService(serialDriver);
|
||||
bool optTestMode = headService.SetOptTestMode(genesisHead);
|
||||
if (genesisHead.ConfigStruct != null)
|
||||
genesisHead.ConfigStruct.OpthoStatusMode = optTestMode ? DiagnosticLedState.State4 : DiagnosticLedState.StatusUnknown;
|
||||
return optTestMode;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("SetOptTestMode() - Exception:" + ex.StackTrace);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set Active mode - string response
|
||||
/// </summary>
|
||||
/// <param name="iHead"></param>
|
||||
/// <param name="isTestModeSuccessful"></param>
|
||||
/// <returns></returns>
|
||||
public string SetActiveMode(ref bool isTestModeSuccessful)
|
||||
{
|
||||
if (genesisHead.DebugLevel == DebugMode.Simulate)
|
||||
{
|
||||
isTestModeSuccessful = true;
|
||||
return "-OK Simulated response-";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
bool activeMode = SetActiveMode();
|
||||
isTestModeSuccessful = activeMode;
|
||||
return activeMode ? "Set Active Mode - OK" : "Set Active Mode - FAILED";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("SetActiveMode() - Exception:" + ex.StackTrace);
|
||||
return "Set Active Mode - Exception";
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Set Optical -> Active mode
|
||||
/// </summary>
|
||||
/// <param name="iHead"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private bool SetOptActiveMode(GenesisSmartReader iHead)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (iHead != null)
|
||||
{
|
||||
if (serialDriver == null)
|
||||
serialDriver = BuildConnection(iHead);
|
||||
|
||||
RadioService headService = new RadioService(serialDriver);
|
||||
return headService.SetOptActiveMode(iHead);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("SetOptActiveMode() - Exception:" + ex.StackTrace);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set activity mode to active
|
||||
/// </summary>
|
||||
/// <param name="iHead"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private bool SetActivityMode_Active()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (genesisHead != null)
|
||||
{
|
||||
if (serialDriver == null)
|
||||
serialDriver = BuildConnection(genesisHead);
|
||||
|
||||
log.Debug("SetActivityMode_Active called for iHead: " + genesisHead.ToString() + " serialDriver: " + serialDriver);
|
||||
RadioService headService = new RadioService(serialDriver);
|
||||
return headService.SetActivityMode_Active(genesisHead);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("SetActivityMode_Active() - Exception:" + ex.StackTrace);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set activity mode to idle
|
||||
/// </summary>
|
||||
/// <param name="iHead"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
private bool SetActivityMode_Idle()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (genesisHead != null)
|
||||
{
|
||||
if (serialDriver == null)
|
||||
serialDriver = BuildConnection(genesisHead);
|
||||
|
||||
log.Debug("SetActivityMode_Idle called for iHead: " + genesisHead.ToString() + " serialDriver: " + serialDriver);
|
||||
|
||||
RadioService headService = new RadioService(serialDriver);
|
||||
return headService.SetActivityMode_Idle(genesisHead);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Error("SetActivityMode_Idle() - Exception:" + ex.StackTrace);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CloseConnection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read configuration from iHead
|
||||
/// DiagnosticLedState is not readable, mus only be set!
|
||||
/// </summary>
|
||||
/// <param name="iHead"></param>
|
||||
/// <param name="ledState"></param>
|
||||
/// <returns></returns>
|
||||
public bool ReadConfiguration(DiagnosticLedState ledState )
|
||||
{
|
||||
if (genesisHead.DebugLevel == DebugMode.Simulate)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
if (genesisHead != null)
|
||||
{
|
||||
genesisHead.ConfigStruct = new ConfigStruct();
|
||||
|
||||
if (serialDriver == null)
|
||||
serialDriver = BuildConnection(genesisHead);
|
||||
|
||||
RadioService headService = new RadioService(serialDriver);
|
||||
genesisHead.ConfigStruct.PCBNumberString = headService.ReadRequest_PCB(ref genesisHead);
|
||||
genesisHead.ConfigStruct.StatusMode = headService.GetActivityStatusMode(genesisHead);
|
||||
genesisHead.ConfigStruct.Unit = headService.GetUnit(genesisHead);
|
||||
|
||||
if (ledState != DiagnosticLedState.StatusUnknown) // do set
|
||||
{
|
||||
genesisHead.ConfigStruct.OpthoStatusMode = headService.SetOptoStatusMode(genesisHead, ledState);
|
||||
}
|
||||
else
|
||||
{
|
||||
genesisHead.ConfigStruct.OpthoStatusMode = DiagnosticLedState.StatusUnknown;
|
||||
}
|
||||
|
||||
genesisHead.ConfigStruct.Version = headService.GetVersion(genesisHead);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user