Add (release) new component Modbus.WaterAnalyzerUni derived from original component Modbus.WaterAnalyzer. New component is expanded by the possibility to choose conductivity reading directly from the sensor (Modbus protocol) (original solution) or using a 4-20mA converter to serial line (RS232/485) (Modbus protocol).

This commit is contained in:
Marek Frniak 2026-07-02 09:01:00 +02:00
parent 7867044c0a
commit 110b880304
13 changed files with 1965 additions and 0 deletions

View File

@ -0,0 +1,359 @@
using Common;
using log4net;
///
/// Copyright (c) 2020 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using System.Diagnostics;
using TBF.Boxes;
using TBF.Rig.Generic;
using TBF.Rig.Modbus.WaterAnalyzerUni;
namespace TBF.Rig.Modbus.WaterAnalyzerUni
{
public class Analyzer : ComponentBase, IDevice
{
private static readonly ILog log = LogManager.GetLogger(typeof(Analyzer));
public override string ToString() { return string.Format("{0}({1})", ClassName, Cfg.ToString(-1)); }
readonly AnalyzerCfg myCfg;
Common.Modbus modbus;
float conductivity; // [uS/cm]
double temperature; // [°C]
int msrmntTimeStamp;
int ticketNumber;
ushort[] rawRegs;
DateTime lastUpdate;
byte[] lastTelegram;
public float Conductivity { get { return conductivity; } }
public AnalyzerDiagnostics Diagnostics { get; private set; }
public Analyzer()
{
Diagnostics = new AnalyzerDiagnostics();
}
public Analyzer(IComponentCfg cfg, IList<IComponent> components)
: base(cfg)
{
myCfg = cfg as AnalyzerCfg;
Diagnostics = new AnalyzerDiagnostics();
}
public override void Initialize()
{
modbus = TbfComponents.FindComponent(myCfg.ParentName) as Common.Modbus;
if (modbus == null) throw new Exception("Cannot find " + Name + " parent");
conductivity = 0.0F;
temperature = 0.0;
msrmntTimeStamp = 0;
ticketNumber = -1;
rawRegs = new ushort[Math.Max(1, (int)myCfg.ConverterRegisterCount)];
modbus.ComponentNames[myCfg.ModbusAddress] = Name;
ticketNumber = modbus.RegisterForPolling();
log.FatalFormat("{0} initialized: {1}", Name, this);
}
public float ReadConductivity()
{
return conductivity;
}
public double ReadTemperature()
{
return temperature;
}
public ReadConductivityOp ReadConductivityOp(ref FloatBox conduct)
{
return new ReadConductivityOp(this, ref conduct);
}
public IOperation ReadTempOp(ref DoubleBox temperature)
{
return new ReadTempOp(this, ref temperature);
}
public IOperation ReadTempOp(ref DoubleBox temperature, Event eventDone)
{
return new ReadTempOp(this, ref temperature, eventDone);
}
public void RunDeviceBefore()
{
if (myCfg.DebugLevel == DebugMode.Simulate ||
myCfg.DebugLevel == DebugMode.FailureDuringOperation)
{
msrmntTimeStamp = StateMachine.Time;
return;
}
var queue = modbus.ReceivedTelegrams[myCfg.ModbusAddress];
if (queue.Count == 0) return;
lastTelegram = queue.Dequeue();
if (myCfg.ConnectionType == AnalyzerConnectionType.DirectConductivityMeter)
{
ParseDirectAnalyzerTelegram(lastTelegram);
}
else
{
ParseConverterTelegram(lastTelegram);
}
}
public void RunDeviceAfter()
{
if (myCfg.DebugLevel == DebugMode.Simulate ||
myCfg.DebugLevel == DebugMode.FailureDuringOperation ||
!modbus.IsMyTurn(ticketNumber))
{
return;
}
if (myCfg.ConnectionType == AnalyzerConnectionType.DirectConductivityMeter)
{
SendDirectAnalyzerRequest();
}
else
{
SendConverterRequest();
}
}
public void StopDevice() { }
public void StopDevice2() { }
void SendDirectAnalyzerRequest()
{
ushort regAddr = myCfg.DirectRegisterAddress;
ushort count = 2;
byte[] msg = new byte[8];
msg[0] = myCfg.ModbusAddress;
msg[1] = 3;
msg[2] = (byte)(regAddr >> 8);
msg[3] = (byte)(regAddr & 0xFF);
msg[4] = (byte)(count >> 8);
msg[5] = (byte)(count & 0xFF);
Diagnostics.SetRequest(myCfg.ModbusAddress, 3, regAddr, count);
modbus.SendMessage(msg, Name);
}
void SendConverterRequest()
{
Diagnostics.SetRequest(
myCfg.ModbusAddress,
myCfg.ConverterReadFunction,
myCfg.ConverterFirstRegister,
myCfg.ConverterRegisterCount);
modbus.SendMessage(
myCfg.ModbusAddress,
myCfg.ConverterReadFunction,
myCfg.ConverterFirstRegister,
myCfg.ConverterRegisterCount,
Name);
}
void ParseDirectAnalyzerTelegram(byte[] telegram)
{
if (telegram == null) return;
if (telegram.Length == 9 && telegram[1] == 3 && telegram[2] == 4)
{
byte[] conductBytes = CreateFloatBytes(
telegram[3],
telegram[4],
telegram[5],
telegram[6],
myCfg.DirectFloatByteOrder);
conductivity = BitConverter.ToSingle(conductBytes, 0);
lastUpdate = DateTime.Now;
UpdateProcessData(conductivity);
Diagnostics.SetResponse(
telegram,
null,
conductivity,
0,
conductivity,
myCfg.Unit);
string line = string.Format("conductivity = {0} {1}", conductivity, myCfg.Unit);
log.Debug(line);
Debug.WriteLine(line);
}
}
void ParseConverterTelegram(byte[] telegram)
{
try
{
if (telegram == null || telegram.Length < 5) return;
if (telegram[1] != myCfg.ConverterReadFunction) return;
int byteCount = telegram[2];
int expectedBytes = myCfg.ConverterRegisterCount * 2;
if (byteCount < expectedBytes) return;
if (telegram.Length < 3 + expectedBytes) return;
if (rawRegs == null || rawRegs.Length != myCfg.ConverterRegisterCount)
rawRegs = new ushort[Math.Max(1, (int)myCfg.ConverterRegisterCount)];
for (int i = 0; i < myCfg.ConverterRegisterCount; i++)
{
int ix = 3 + i * 2;
rawRegs[i] = (ushort)((telegram[ix] << 8) | telegram[ix + 1]);
}
double raw = ReadConverterRawValue();
double current = ConvertRawToMilliAmps(raw);
conductivity = (float)ConvertRawToConductivity(raw);
lastUpdate = DateTime.Now;
UpdateProcessData(conductivity);
Diagnostics.SetResponse(
telegram,
rawRegs,
raw,
current,
conductivity,
myCfg.Unit);
string line = string.Format(
"converter raw = {0:0.###}, conductivity = {1:0.###} {2}",
raw,
conductivity,
myCfg.Unit);
log.Debug(line);
Debug.WriteLine(line);
}
catch (Exception ex)
{
Diagnostics.SetError(
ex.Message + Environment.NewLine +
"Telegram: " + BitConverter.ToString(telegram));
log.WarnFormat("{0}: Failed to parse converter telegram. {1}", Name, ex.Message);
Debug.WriteLine(ex);
}
}
double ReadConverterRawValue()
{
if (myCfg.ConverterValueSource == AnalyzerValueSource.IntegerRegister)
{
int ch = myCfg.ConverterChannel;
if (rawRegs == null || ch < 0 || ch >= rawRegs.Length)
return 0.0;
return rawRegs[ch];
}
if (rawRegs == null || rawRegs.Length < 4)
return 0.0;
return ModbusFloat(
rawRegs[2],
rawRegs[3],
myCfg.ConverterFloatByteOrder);
}
double ConvertRawToConductivity(double raw)
{
double denom = myCfg.RawValueAt20mA - myCfg.RawValueAt4mA;
if (Math.Abs(denom) < 1e-12) return 0;
return myCfg.ConductivityAt4mA +
(raw - myCfg.RawValueAt4mA) / denom *
(myCfg.ConductivityAt20mA - myCfg.ConductivityAt4mA);
}
double ConvertRawToMilliAmps(double raw)
{
double denom = myCfg.RawValueAt20mA - myCfg.RawValueAt4mA;
if (Math.Abs(denom) < 1e-12) return 0;
return 4.0 + (raw - myCfg.RawValueAt4mA) * (16.0 / denom);
}
static byte[] CreateFloatBytes(
byte b0,
byte b1,
byte b2,
byte b3,
AnalyzerFloatByteOrder order)
{
switch (order)
{
case AnalyzerFloatByteOrder.ABCD:
return BitConverter.IsLittleEndian
? new byte[] { b3, b2, b1, b0 }
: new byte[] { b0, b1, b2, b3 };
case AnalyzerFloatByteOrder.BADC:
return BitConverter.IsLittleEndian
? new byte[] { b2, b3, b0, b1 }
: new byte[] { b1, b0, b3, b2 };
case AnalyzerFloatByteOrder.CDAB:
return BitConverter.IsLittleEndian
? new byte[] { b1, b0, b3, b2 }
: new byte[] { b2, b3, b0, b1 };
case AnalyzerFloatByteOrder.DCBA:
return BitConverter.IsLittleEndian
? new byte[] { b0, b1, b2, b3 }
: new byte[] { b3, b2, b1, b0 };
default:
return BitConverter.IsLittleEndian
? new byte[] { b3, b2, b1, b0 }
: new byte[] { b0, b1, b2, b3 };
}
}
static float ModbusFloat(
ushort hi,
ushort lo,
AnalyzerFloatByteOrder order)
{
byte a = (byte)(hi >> 8);
byte b = (byte)(hi & 0xFF);
byte c = (byte)(lo >> 8);
byte d = (byte)(lo & 0xFF);
byte[] bytes = CreateFloatBytes(a, b, c, d, order);
return BitConverter.ToSingle(bytes, 0);
}
void UpdateProcessData(float conduct)
{
TBF.Rig.Sequences.ProcessData.Conductivity.Val = conduct;
}
public void ShowDiagnostics()
{
new AnalyzerDiagnosticsForm(this).Show();
}
}
}

View File

@ -0,0 +1,242 @@
///
/// Copyright (c) 2020 Sensus Slovensko a.s.
///
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
using Common;
using Config.Entities;
using TBF.Rig.Generic;
namespace TBF.Rig.Modbus.WaterAnalyzerUni
{
public enum AnalyzerConnectionType
{
DirectConductivityMeter,
CurrentLoop420mA
}
public enum AnalyzerValueSource
{
IntegerRegister,
FloatRegisters
}
public enum AnalyzerFloatByteOrder
{
ABCD,
BADC,
CDAB,
DCBA
}
public class AnalyzerCfg : ComponentCfgBase, Generic.IChildComponentCfg, IParamsProvider
{
public static XmlSerializer Serializer = XmlSerializer.FromTypes(new[] { typeof(AnalyzerCfg) })[0];
public override XmlSerializer GetSerializer() { return Serializer; }
public IComponentCfgCtrl GetControl(IList<Component> cmpntEntities)
{
return new AnalyzerCfgCtrl();
}
public AnalyzerConnectionType ConnectionType;
public byte ModbusAddress;
/// Direct analyzer
public ushort DirectRegisterAddress;
public AnalyzerFloatByteOrder DirectFloatByteOrder;
/// Papouch converter
public byte ConverterReadFunction;
public ushort ConverterFirstRegister;
public ushort ConverterRegisterCount;
public int ConverterChannel;
public AnalyzerValueSource ConverterValueSource;
public AnalyzerFloatByteOrder ConverterFloatByteOrder;
/// Raw scaling
public double RawValueAt4mA;
public double RawValueAt20mA;
/// Conductivity scaling
public double ConductivityAt4mA;
public double ConductivityAt20mA;
/// Display
public string Unit;
public string DisplayFormat;
public double LowLimit;
public double HighLimit;
/// Timeout
public int DataTimeoutMs;
public double ConverterIntegerScale;
AnalyzerCfg() { }
public AnalyzerCfg(string name, IComponentFactory factory)
: this()
{
Factory = factory;
Name = name;
ParentName = "ModbusUSB";
InitializeAll();
}
public string ComponentName { get { return Name; } }
public void InitializeAll()
{
ModbusAddress = 49;
ConnectionType = AnalyzerConnectionType.CurrentLoop420mA;
DirectRegisterAddress = 0x50;
DirectFloatByteOrder = AnalyzerFloatByteOrder.BADC;
ConverterReadFunction = 4;
ConverterFirstRegister = 0;
ConverterRegisterCount = 4;
ConverterChannel = 1;
ConverterValueSource = AnalyzerValueSource.IntegerRegister;
ConverterFloatByteOrder = AnalyzerFloatByteOrder.ABCD;
RawValueAt4mA = 0;
RawValueAt20mA = 9990;
ConductivityAt4mA = 10;
ConductivityAt20mA = 20000;
Unit = "uS/cm";
DisplayFormat = "{0:0}";
LowLimit = 0;
HighLimit = 5000;
DataTimeoutMs = 3000;
}
string[] paramNames = new string[]
{
"Connection type",
"Modbus address"
};
public string ParamName(int i) { return paramNames[i]; }
public int ParamsCount() { return paramNames.Length; }
public ICollection<string> ParamValues(int i)
{
switch (i)
{
case 0:
return Enum.GetNames(typeof(AnalyzerConnectionType));
default:
return null;
}
}
public string ToString(int i)
{
if (i == -1)
{
return string.Format(
"ConnectionType={0}, ModbusAddr={1}",
ConnectionType,
ModbusAddress);
}
switch (i)
{
case 0: return ConnectionType.ToString();
case 1: return ModbusAddress.ToString();
default: return string.Empty;
}
}
public CfgUpdateFlags UpdateParam(int i, string strValue)
{
switch (i)
{
case 0:
ConnectionType = (AnalyzerConnectionType)Enum.Parse(typeof(AnalyzerConnectionType), strValue);
return CfgUpdateFlags.RestartRqrd;
case 1:
ModbusAddress = byte.Parse(strValue);
return CfgUpdateFlags.RestartRqrd;
default:
return CfgUpdateFlags.None;
}
}
public bool ValidateParam(int i, string strValue, out string message)
{
message = string.Empty;
int dummy;
switch (i)
{
case 0:
if (Enum.IsDefined(typeof(AnalyzerConnectionType), strValue)) return true;
message = ParamName(i) + " is invalid";
return false;
case 1:
if (int.TryParse(strValue, out dummy) && dummy >= 1 && dummy <= 247) return true;
message = ParamName(i) + " is invalid. Address range is 1 .. 247";
return false;
default:
message = "Invalid index";
return false;
}
}
void CopyContentTo(AnalyzerCfg prms)
{
prms.ParentName = ParentName;
prms.ModbusAddress = ModbusAddress;
prms.ConnectionType = ConnectionType;
prms.DirectRegisterAddress = DirectRegisterAddress;
prms.DirectFloatByteOrder = DirectFloatByteOrder;
prms.ConverterReadFunction = ConverterReadFunction;
prms.ConverterFirstRegister = ConverterFirstRegister;
prms.ConverterRegisterCount = ConverterRegisterCount;
prms.ConverterChannel = ConverterChannel;
prms.ConverterValueSource = ConverterValueSource;
prms.ConverterFloatByteOrder = ConverterFloatByteOrder;
prms.RawValueAt4mA = RawValueAt4mA;
prms.RawValueAt20mA = RawValueAt20mA;
prms.ConductivityAt4mA = ConductivityAt4mA;
prms.ConductivityAt20mA = ConductivityAt20mA;
prms.Unit = Unit;
prms.DisplayFormat = DisplayFormat;
prms.LowLimit = LowLimit;
prms.HighLimit = HighLimit;
prms.DataTimeoutMs = DataTimeoutMs;
}
public IParamsProvider Clone()
{
AnalyzerCfg pars = new AnalyzerCfg();
CopyContentTo(pars);
return pars;
}
public bool UpdateEmbeddedDbEntity()
{
return true;
}
}
}

View File

@ -0,0 +1,394 @@
namespace TBF.Rig.Modbus.WaterAnalyzerUni
{
partial class AnalyzerCfgCtrl
{
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.Label componentNameLabel;
private System.Windows.Forms.Label nameLabel;
private System.Windows.Forms.TextBox nameTextBox;
private System.Windows.Forms.Label parentLabel;
private System.Windows.Forms.ComboBox parentNameComboBox;
private System.Windows.Forms.Label connectionTypeLabel;
private System.Windows.Forms.ComboBox connectionTypeComboBox;
private System.Windows.Forms.Label modbusAddressLabel;
private System.Windows.Forms.TextBox modbusAddressTextBox;
private System.Windows.Forms.GroupBox directGroupBox;
private System.Windows.Forms.Label directRegisterLabel;
private System.Windows.Forms.TextBox directRegisterTextBox;
private System.Windows.Forms.Label directFloatByteOrderLabel;
private System.Windows.Forms.ComboBox directFloatByteOrderComboBox;
private System.Windows.Forms.GroupBox converterGroupBox;
private System.Windows.Forms.Label converterFunctionLabel;
private System.Windows.Forms.TextBox converterFunctionTextBox;
private System.Windows.Forms.Label converterFirstRegisterLabel;
private System.Windows.Forms.TextBox converterFirstRegisterTextBox;
private System.Windows.Forms.Label converterRegisterCountLabel;
private System.Windows.Forms.TextBox converterRegisterCountTextBox;
private System.Windows.Forms.Label converterChannelLabel;
private System.Windows.Forms.TextBox converterChannelTextBox;
private System.Windows.Forms.Label valueSourceLabel;
private System.Windows.Forms.ComboBox valueSourceComboBox;
private System.Windows.Forms.Label floatByteOrderLabel;
private System.Windows.Forms.ComboBox floatByteOrderComboBox;
private System.Windows.Forms.Label raw4Label;
private System.Windows.Forms.TextBox raw4TextBox;
private System.Windows.Forms.Label raw20Label;
private System.Windows.Forms.TextBox raw20TextBox;
private System.Windows.Forms.Label cond4Label;
private System.Windows.Forms.TextBox cond4TextBox;
private System.Windows.Forms.Label cond20Label;
private System.Windows.Forms.TextBox cond20TextBox;
private System.Windows.Forms.Label unitLabel;
private System.Windows.Forms.TextBox unitTextBox;
private System.Windows.Forms.Label formatLabel;
private System.Windows.Forms.TextBox formatTextBox;
private System.Windows.Forms.Label lowLimitLabel;
private System.Windows.Forms.TextBox lowLimitTextBox;
private System.Windows.Forms.Label highLimitLabel;
private System.Windows.Forms.TextBox highLimitTextBox;
private System.Windows.Forms.Label dataTimeoutLabel;
private System.Windows.Forms.TextBox dataTimeoutTextBox;
private System.Windows.Forms.Button diagnosticsButton;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null)) components.Dispose();
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.componentNameLabel = new System.Windows.Forms.Label();
this.nameLabel = new System.Windows.Forms.Label();
this.nameTextBox = new System.Windows.Forms.TextBox();
this.parentLabel = new System.Windows.Forms.Label();
this.parentNameComboBox = new System.Windows.Forms.ComboBox();
this.connectionTypeLabel = new System.Windows.Forms.Label();
this.connectionTypeComboBox = new System.Windows.Forms.ComboBox();
this.modbusAddressLabel = new System.Windows.Forms.Label();
this.modbusAddressTextBox = new System.Windows.Forms.TextBox();
this.directGroupBox = new System.Windows.Forms.GroupBox();
this.directRegisterLabel = new System.Windows.Forms.Label();
this.directRegisterTextBox = new System.Windows.Forms.TextBox();
this.directFloatByteOrderLabel = new System.Windows.Forms.Label();
this.directFloatByteOrderComboBox = new System.Windows.Forms.ComboBox();
this.converterGroupBox = new System.Windows.Forms.GroupBox();
this.converterFunctionLabel = new System.Windows.Forms.Label();
this.converterFunctionTextBox = new System.Windows.Forms.TextBox();
this.converterFirstRegisterLabel = new System.Windows.Forms.Label();
this.converterFirstRegisterTextBox = new System.Windows.Forms.TextBox();
this.converterRegisterCountLabel = new System.Windows.Forms.Label();
this.converterRegisterCountTextBox = new System.Windows.Forms.TextBox();
this.converterChannelLabel = new System.Windows.Forms.Label();
this.converterChannelTextBox = new System.Windows.Forms.TextBox();
this.valueSourceLabel = new System.Windows.Forms.Label();
this.valueSourceComboBox = new System.Windows.Forms.ComboBox();
this.floatByteOrderLabel = new System.Windows.Forms.Label();
this.floatByteOrderComboBox = new System.Windows.Forms.ComboBox();
this.raw4Label = new System.Windows.Forms.Label();
this.raw4TextBox = new System.Windows.Forms.TextBox();
this.raw20Label = new System.Windows.Forms.Label();
this.raw20TextBox = new System.Windows.Forms.TextBox();
this.cond4Label = new System.Windows.Forms.Label();
this.cond4TextBox = new System.Windows.Forms.TextBox();
this.cond20Label = new System.Windows.Forms.Label();
this.cond20TextBox = new System.Windows.Forms.TextBox();
this.unitLabel = new System.Windows.Forms.Label();
this.unitTextBox = new System.Windows.Forms.TextBox();
this.formatLabel = new System.Windows.Forms.Label();
this.formatTextBox = new System.Windows.Forms.TextBox();
this.lowLimitLabel = new System.Windows.Forms.Label();
this.lowLimitTextBox = new System.Windows.Forms.TextBox();
this.highLimitLabel = new System.Windows.Forms.Label();
this.highLimitTextBox = new System.Windows.Forms.TextBox();
this.dataTimeoutLabel = new System.Windows.Forms.Label();
this.dataTimeoutTextBox = new System.Windows.Forms.TextBox();
this.diagnosticsButton = new System.Windows.Forms.Button();
this.directGroupBox.SuspendLayout();
this.converterGroupBox.SuspendLayout();
this.SuspendLayout();
// componentNameLabel
this.componentNameLabel.AutoSize = true;
this.componentNameLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Bold);
this.componentNameLabel.Location = new System.Drawing.Point(12, 10);
this.componentNameLabel.Name = "componentNameLabel";
this.componentNameLabel.Size = new System.Drawing.Size(95, 15);
this.componentNameLabel.TabIndex = 0;
this.componentNameLabel.Text = "WaterAnalyzer";
// nameLabel
this.nameLabel.AutoSize = true;
this.nameLabel.Location = new System.Drawing.Point(12, 40);
this.nameLabel.Name = "nameLabel";
this.nameLabel.Size = new System.Drawing.Size(35, 13);
this.nameLabel.TabIndex = 1;
this.nameLabel.Text = "Name";
// nameTextBox
this.nameTextBox.Location = new System.Drawing.Point(170, 37);
this.nameTextBox.Name = "nameTextBox";
this.nameTextBox.Size = new System.Drawing.Size(220, 20);
this.nameTextBox.TabIndex = 2;
// parentLabel
this.parentLabel.AutoSize = true;
this.parentLabel.Location = new System.Drawing.Point(12, 66);
this.parentLabel.Name = "parentLabel";
this.parentLabel.Size = new System.Drawing.Size(95, 13);
this.parentLabel.TabIndex = 3;
this.parentLabel.Text = "Parent Component";
// parentNameComboBox
this.parentNameComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDown;
this.parentNameComboBox.FormattingEnabled = true;
this.parentNameComboBox.Location = new System.Drawing.Point(170, 63);
this.parentNameComboBox.Name = "parentNameComboBox";
this.parentNameComboBox.Size = new System.Drawing.Size(220, 21);
this.parentNameComboBox.TabIndex = 4;
// connectionTypeLabel
this.connectionTypeLabel.AutoSize = true;
this.connectionTypeLabel.Location = new System.Drawing.Point(12, 93);
this.connectionTypeLabel.Name = "connectionTypeLabel";
this.connectionTypeLabel.Size = new System.Drawing.Size(86, 13);
this.connectionTypeLabel.TabIndex = 5;
this.connectionTypeLabel.Text = "Connection Type";
// connectionTypeComboBox
this.connectionTypeComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.connectionTypeComboBox.FormattingEnabled = true;
this.connectionTypeComboBox.Location = new System.Drawing.Point(170, 90);
this.connectionTypeComboBox.Name = "connectionTypeComboBox";
this.connectionTypeComboBox.Size = new System.Drawing.Size(220, 21);
this.connectionTypeComboBox.TabIndex = 6;
this.connectionTypeComboBox.SelectedIndexChanged += new System.EventHandler(this.connectionTypeComboBox_SelectedIndexChanged);
// modbusAddressLabel
this.modbusAddressLabel.AutoSize = true;
this.modbusAddressLabel.Location = new System.Drawing.Point(12, 120);
this.modbusAddressLabel.Name = "modbusAddressLabel";
this.modbusAddressLabel.Size = new System.Drawing.Size(84, 13);
this.modbusAddressLabel.TabIndex = 7;
this.modbusAddressLabel.Text = "Modbus Address";
// modbusAddressTextBox
this.modbusAddressTextBox.Location = new System.Drawing.Point(170, 117);
this.modbusAddressTextBox.Name = "modbusAddressTextBox";
this.modbusAddressTextBox.Size = new System.Drawing.Size(80, 20);
this.modbusAddressTextBox.TabIndex = 8;
// directGroupBox
this.directGroupBox.Controls.Add(this.directRegisterLabel);
this.directGroupBox.Controls.Add(this.directRegisterTextBox);
this.directGroupBox.Controls.Add(this.directFloatByteOrderLabel);
this.directGroupBox.Controls.Add(this.directFloatByteOrderComboBox);
this.directGroupBox.Location = new System.Drawing.Point(15, 150);
this.directGroupBox.Name = "directGroupBox";
this.directGroupBox.Size = new System.Drawing.Size(375, 90);
this.directGroupBox.TabIndex = 9;
this.directGroupBox.TabStop = false;
this.directGroupBox.Text = "Direct conductivity meter";
this.directRegisterLabel.AutoSize = true;
this.directRegisterLabel.Location = new System.Drawing.Point(12, 28);
this.directRegisterLabel.Name = "directRegisterLabel";
this.directRegisterLabel.Size = new System.Drawing.Size(82, 13);
this.directRegisterLabel.TabIndex = 0;
this.directRegisterLabel.Text = "Register Address";
this.directRegisterTextBox.Location = new System.Drawing.Point(155, 25);
this.directRegisterTextBox.Name = "directRegisterTextBox";
this.directRegisterTextBox.Size = new System.Drawing.Size(120, 20);
this.directRegisterTextBox.TabIndex = 1;
this.directFloatByteOrderLabel.AutoSize = true;
this.directFloatByteOrderLabel.Location = new System.Drawing.Point(12, 55);
this.directFloatByteOrderLabel.Name = "directFloatByteOrderLabel";
this.directFloatByteOrderLabel.Size = new System.Drawing.Size(84, 13);
this.directFloatByteOrderLabel.TabIndex = 2;
this.directFloatByteOrderLabel.Text = "Float Byte Order";
this.directFloatByteOrderComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.directFloatByteOrderComboBox.FormattingEnabled = true;
this.directFloatByteOrderComboBox.Location = new System.Drawing.Point(155, 52);
this.directFloatByteOrderComboBox.Name = "directFloatByteOrderComboBox";
this.directFloatByteOrderComboBox.Size = new System.Drawing.Size(200, 21);
this.directFloatByteOrderComboBox.TabIndex = 3;
// converterGroupBox
this.converterGroupBox.Controls.Add(this.converterFunctionLabel);
this.converterGroupBox.Controls.Add(this.converterFunctionTextBox);
this.converterGroupBox.Controls.Add(this.converterFirstRegisterLabel);
this.converterGroupBox.Controls.Add(this.converterFirstRegisterTextBox);
this.converterGroupBox.Controls.Add(this.converterRegisterCountLabel);
this.converterGroupBox.Controls.Add(this.converterRegisterCountTextBox);
this.converterGroupBox.Controls.Add(this.converterChannelLabel);
this.converterGroupBox.Controls.Add(this.converterChannelTextBox);
this.converterGroupBox.Controls.Add(this.valueSourceLabel);
this.converterGroupBox.Controls.Add(this.valueSourceComboBox);
this.converterGroupBox.Controls.Add(this.floatByteOrderLabel);
this.converterGroupBox.Controls.Add(this.floatByteOrderComboBox);
this.converterGroupBox.Controls.Add(this.raw4Label);
this.converterGroupBox.Controls.Add(this.raw4TextBox);
this.converterGroupBox.Controls.Add(this.raw20Label);
this.converterGroupBox.Controls.Add(this.raw20TextBox);
this.converterGroupBox.Controls.Add(this.cond4Label);
this.converterGroupBox.Controls.Add(this.cond4TextBox);
this.converterGroupBox.Controls.Add(this.cond20Label);
this.converterGroupBox.Controls.Add(this.cond20TextBox);
this.converterGroupBox.Controls.Add(this.unitLabel);
this.converterGroupBox.Controls.Add(this.unitTextBox);
this.converterGroupBox.Controls.Add(this.formatLabel);
this.converterGroupBox.Controls.Add(this.formatTextBox);
this.converterGroupBox.Controls.Add(this.lowLimitLabel);
this.converterGroupBox.Controls.Add(this.lowLimitTextBox);
this.converterGroupBox.Controls.Add(this.highLimitLabel);
this.converterGroupBox.Controls.Add(this.highLimitTextBox);
this.converterGroupBox.Controls.Add(this.dataTimeoutLabel);
this.converterGroupBox.Controls.Add(this.dataTimeoutTextBox);
this.converterGroupBox.Location = new System.Drawing.Point(15, 250);
this.converterGroupBox.Name = "converterGroupBox";
this.converterGroupBox.Size = new System.Drawing.Size(375, 430);
this.converterGroupBox.TabIndex = 10;
this.converterGroupBox.TabStop = false;
this.converterGroupBox.Text = "4-20 mA converter";
this.converterFunctionLabel.AutoSize = true;
this.converterFunctionLabel.Location = new System.Drawing.Point(12, 25);
this.converterFunctionLabel.Text = "Function (3 or 4)";
this.converterFunctionTextBox.Location = new System.Drawing.Point(155, 22);
this.converterFunctionTextBox.Size = new System.Drawing.Size(80, 20);
this.converterFirstRegisterLabel.AutoSize = true;
this.converterFirstRegisterLabel.Location = new System.Drawing.Point(12, 51);
this.converterFirstRegisterLabel.Text = "First Register";
this.converterFirstRegisterTextBox.Location = new System.Drawing.Point(155, 48);
this.converterFirstRegisterTextBox.Size = new System.Drawing.Size(120, 20);
this.converterRegisterCountLabel.AutoSize = true;
this.converterRegisterCountLabel.Location = new System.Drawing.Point(12, 77);
this.converterRegisterCountLabel.Text = "Register Count";
this.converterRegisterCountTextBox.Location = new System.Drawing.Point(155, 74);
this.converterRegisterCountTextBox.Size = new System.Drawing.Size(120, 20);
this.converterChannelLabel.AutoSize = true;
this.converterChannelLabel.Location = new System.Drawing.Point(12, 103);
this.converterChannelLabel.Text = "Channel";
this.converterChannelTextBox.Location = new System.Drawing.Point(155, 100);
this.converterChannelTextBox.Size = new System.Drawing.Size(80, 20);
this.valueSourceLabel.AutoSize = true;
this.valueSourceLabel.Location = new System.Drawing.Point(12, 130);
this.valueSourceLabel.Text = "Value Source";
this.valueSourceComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.valueSourceComboBox.Location = new System.Drawing.Point(155, 127);
this.valueSourceComboBox.Size = new System.Drawing.Size(200, 21);
this.floatByteOrderLabel.AutoSize = true;
this.floatByteOrderLabel.Location = new System.Drawing.Point(12, 157);
this.floatByteOrderLabel.Text = "Float Byte Order";
this.floatByteOrderComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.floatByteOrderComboBox.Location = new System.Drawing.Point(155, 154);
this.floatByteOrderComboBox.Size = new System.Drawing.Size(200, 21);
this.raw4Label.AutoSize = true;
this.raw4Label.Location = new System.Drawing.Point(12, 190);
this.raw4Label.Text = "Raw Value @ 4 mA";
this.raw4TextBox.Location = new System.Drawing.Point(155, 187);
this.raw4TextBox.Size = new System.Drawing.Size(120, 20);
this.raw20Label.AutoSize = true;
this.raw20Label.Location = new System.Drawing.Point(12, 216);
this.raw20Label.Text = "Raw Value @ 20 mA";
this.raw20TextBox.Location = new System.Drawing.Point(155, 213);
this.raw20TextBox.Size = new System.Drawing.Size(120, 20);
this.cond4Label.AutoSize = true;
this.cond4Label.Location = new System.Drawing.Point(12, 242);
this.cond4Label.Text = "Conductivity @ 4 mA";
this.cond4TextBox.Location = new System.Drawing.Point(155, 239);
this.cond4TextBox.Size = new System.Drawing.Size(120, 20);
this.cond20Label.AutoSize = true;
this.cond20Label.Location = new System.Drawing.Point(12, 268);
this.cond20Label.Text = "Conductivity @ 20 mA";
this.cond20TextBox.Location = new System.Drawing.Point(155, 265);
this.cond20TextBox.Size = new System.Drawing.Size(120, 20);
this.unitLabel.AutoSize = true;
this.unitLabel.Location = new System.Drawing.Point(12, 294);
this.unitLabel.Text = "Unit";
this.unitTextBox.Location = new System.Drawing.Point(155, 291);
this.unitTextBox.Size = new System.Drawing.Size(120, 20);
this.formatLabel.AutoSize = true;
this.formatLabel.Location = new System.Drawing.Point(12, 320);
this.formatLabel.Text = "Display Format";
this.formatTextBox.Location = new System.Drawing.Point(155, 317);
this.formatTextBox.Size = new System.Drawing.Size(200, 20);
this.lowLimitLabel.AutoSize = true;
this.lowLimitLabel.Location = new System.Drawing.Point(12, 346);
this.lowLimitLabel.Text = "Low Limit";
this.lowLimitTextBox.Location = new System.Drawing.Point(155, 343);
this.lowLimitTextBox.Size = new System.Drawing.Size(120, 20);
this.highLimitLabel.AutoSize = true;
this.highLimitLabel.Location = new System.Drawing.Point(12, 372);
this.highLimitLabel.Text = "High Limit";
this.highLimitTextBox.Location = new System.Drawing.Point(155, 369);
this.highLimitTextBox.Size = new System.Drawing.Size(120, 20);
this.dataTimeoutLabel.AutoSize = true;
this.dataTimeoutLabel.Location = new System.Drawing.Point(12, 398);
this.dataTimeoutLabel.Text = "Data Timeout (ms)";
this.dataTimeoutTextBox.Location = new System.Drawing.Point(155, 395);
this.dataTimeoutTextBox.Size = new System.Drawing.Size(120, 20);
// diagnosticsButton
this.diagnosticsButton.Location = new System.Drawing.Point(170, 700);
this.diagnosticsButton.Name = "diagnosticsButton";
this.diagnosticsButton.Size = new System.Drawing.Size(120, 27);
this.diagnosticsButton.TabIndex = 11;
this.diagnosticsButton.Text = "Diagnostics...";
this.diagnosticsButton.UseVisualStyleBackColor = true;
this.diagnosticsButton.Click += new System.EventHandler(this.diagnosticsButton_Click);
// AnalyzerCfgCtrl
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.componentNameLabel);
this.Controls.Add(this.nameLabel);
this.Controls.Add(this.nameTextBox);
this.Controls.Add(this.parentLabel);
this.Controls.Add(this.parentNameComboBox);
this.Controls.Add(this.connectionTypeLabel);
this.Controls.Add(this.connectionTypeComboBox);
this.Controls.Add(this.modbusAddressLabel);
this.Controls.Add(this.modbusAddressTextBox);
this.Controls.Add(this.directGroupBox);
this.Controls.Add(this.converterGroupBox);
this.Controls.Add(this.diagnosticsButton);
this.Name = "AnalyzerCfgCtrl";
this.Size = new System.Drawing.Size(420, 740);
this.Load += new System.EventHandler(this.AnalyzerCfgCtrl_Load);
this.directGroupBox.ResumeLayout(false);
this.directGroupBox.PerformLayout();
this.converterGroupBox.ResumeLayout(false);
this.converterGroupBox.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
}
}

View File

@ -0,0 +1,379 @@
using Common;
using Config.Entities;
using System;
using System.Drawing;
using System.Windows.Forms;
using TBF.Rig.Generic;
using TBF.UI.Bench.Components;
namespace TBF.Rig.Modbus.WaterAnalyzerUni
{
public partial class AnalyzerCfgCtrl : UserControl, IComponentCfgCtrl
{
ComponentParametersDlg parent;
AnalyzerCfg config;
public bool ShowMore { get { return false; } }
public IComponentCfg Config
{
get { return config as IComponentCfg; }
set
{
config = value as AnalyzerCfg;
Redraw();
}
}
public AnalyzerCfgCtrl()
{
InitializeComponent();
}
private void AnalyzerCfgCtrl_Load(object sender, EventArgs e)
{
parent = ParentForm as ComponentParametersDlg;
if (parent != null && parent.CmpntEntities != null)
{
foreach (var cmpnt in parent.CmpntEntities)
{
if (TbfComponents.CmpntFactoryFromClassName(cmpnt.ClassName) is Modbus.Common.Factory)
{
parentNameComboBox.Items.Add(cmpnt.Name);
}
}
}
connectionTypeComboBox.Items.Clear();
connectionTypeComboBox.Items.Add(AnalyzerConnectionType.DirectConductivityMeter.ToString());
connectionTypeComboBox.Items.Add(AnalyzerConnectionType.CurrentLoop420mA.ToString());
directFloatByteOrderComboBox.Items.Clear();
directFloatByteOrderComboBox.Items.Add(AnalyzerFloatByteOrder.ABCD.ToString());
directFloatByteOrderComboBox.Items.Add(AnalyzerFloatByteOrder.BADC.ToString());
directFloatByteOrderComboBox.Items.Add(AnalyzerFloatByteOrder.CDAB.ToString());
directFloatByteOrderComboBox.Items.Add(AnalyzerFloatByteOrder.DCBA.ToString());
valueSourceComboBox.Items.Clear();
valueSourceComboBox.Items.Add(AnalyzerValueSource.IntegerRegister.ToString());
valueSourceComboBox.Items.Add(AnalyzerValueSource.FloatRegisters.ToString());
floatByteOrderComboBox.Items.Clear();
floatByteOrderComboBox.Items.Add(AnalyzerFloatByteOrder.ABCD.ToString());
floatByteOrderComboBox.Items.Add(AnalyzerFloatByteOrder.BADC.ToString());
floatByteOrderComboBox.Items.Add(AnalyzerFloatByteOrder.CDAB.ToString());
floatByteOrderComboBox.Items.Add(AnalyzerFloatByteOrder.DCBA.ToString());
Lock();
Redraw();
}
public void Closing() { }
void Redraw()
{
if (config == null) return;
componentNameLabel.Text = config.Factory.ClassName;
nameTextBox.Text = config.Name;
parentNameComboBox.Text = string.IsNullOrEmpty(config.ParentName) ? "---" : config.ParentName;
connectionTypeComboBox.Text = config.ConnectionType.ToString();
modbusAddressTextBox.Text = config.ModbusAddress.ToString();
directRegisterTextBox.Text = config.DirectRegisterAddress.ToString();
directFloatByteOrderComboBox.Text = config.DirectFloatByteOrder.ToString();
converterFunctionTextBox.Text = config.ConverterReadFunction.ToString();
converterFirstRegisterTextBox.Text = config.ConverterFirstRegister.ToString();
converterRegisterCountTextBox.Text = config.ConverterRegisterCount.ToString();
converterChannelTextBox.Text = config.ConverterChannel.ToString();
valueSourceComboBox.Text = config.ConverterValueSource.ToString();
floatByteOrderComboBox.Text = config.ConverterFloatByteOrder.ToString();
raw4TextBox.Text = config.RawValueAt4mA.ToString();
raw20TextBox.Text = config.RawValueAt20mA.ToString();
cond4TextBox.Text = config.ConductivityAt4mA.ToString();
cond20TextBox.Text = config.ConductivityAt20mA.ToString();
unitTextBox.Text = config.Unit;
formatTextBox.Text = config.DisplayFormat;
lowLimitTextBox.Text = config.LowLimit.ToString();
highLimitTextBox.Text = config.HighLimit.ToString();
dataTimeoutTextBox.Text = config.DataTimeoutMs.ToString();
UpdateGroupVisibility();
}
int formChromeHeight = -1;
void UpdateGroupVisibility()
{
bool direct =
connectionTypeComboBox.Text ==
AnalyzerConnectionType.DirectConductivityMeter.ToString();
directGroupBox.Visible = false;
converterGroupBox.Visible = !direct;
if (direct)
{
diagnosticsButton.Top = modbusAddressTextBox.Bottom + 20;
}
else
{
converterGroupBox.Top = modbusAddressTextBox.Bottom + 20;
diagnosticsButton.Top = converterGroupBox.Bottom + 15;
}
int requiredControlHeight = diagnosticsButton.Bottom + 15;
this.Height = requiredControlHeight;
Form form = FindForm();
if (form != null)
{
form.ClientSize = new System.Drawing.Size(
form.ClientSize.Width,
requiredControlHeight + 20);
}
}
private void connectionTypeComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
UpdateGroupVisibility();
}
public void Unlock()
{
nameTextBox.Enabled = true;
parentNameComboBox.Enabled = true;
connectionTypeComboBox.Enabled = true;
modbusAddressTextBox.Enabled = true;
directRegisterTextBox.Enabled = true;
directFloatByteOrderComboBox.Enabled = true;
converterFunctionTextBox.Enabled = true;
converterFirstRegisterTextBox.Enabled = true;
converterRegisterCountTextBox.Enabled = true;
converterChannelTextBox.Enabled = true;
valueSourceComboBox.Enabled = true;
floatByteOrderComboBox.Enabled = true;
raw4TextBox.Enabled = true;
raw20TextBox.Enabled = true;
cond4TextBox.Enabled = true;
cond20TextBox.Enabled = true;
unitTextBox.Enabled = true;
formatTextBox.Enabled = true;
lowLimitTextBox.Enabled = true;
highLimitTextBox.Enabled = true;
dataTimeoutTextBox.Enabled = true;
diagnosticsButton.Enabled = true;
UpdateGroupVisibility();
}
public void Lock()
{
nameTextBox.Enabled = false;
parentNameComboBox.Enabled = false;
connectionTypeComboBox.Enabled = false;
modbusAddressTextBox.Enabled = false;
directRegisterTextBox.Enabled = false;
directFloatByteOrderComboBox.Enabled = false;
converterFunctionTextBox.Enabled = false;
converterFirstRegisterTextBox.Enabled = false;
converterRegisterCountTextBox.Enabled = false;
converterChannelTextBox.Enabled = false;
valueSourceComboBox.Enabled = false;
floatByteOrderComboBox.Enabled = false;
raw4TextBox.Enabled = false;
raw20TextBox.Enabled = false;
cond4TextBox.Enabled = false;
cond20TextBox.Enabled = false;
unitTextBox.Enabled = false;
formatTextBox.Enabled = false;
lowLimitTextBox.Enabled = false;
highLimitTextBox.Enabled = false;
dataTimeoutTextBox.Enabled = false;
diagnosticsButton.Enabled = false;
}
public CfgUpdateFlags VerifyCfg(ref string message)
{
CfgUpdateFlags flags = CfgUpdateFlags.None;
int iVal;
double dVal;
bool direct =
connectionTypeComboBox.Text ==
AnalyzerConnectionType.DirectConductivityMeter.ToString();
if (!parentNameComboBox.Items.Contains(parentNameComboBox.Text))
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Invalid 'Parent Component'";
}
if (!connectionTypeComboBox.Items.Contains(connectionTypeComboBox.Text))
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Invalid 'Connection Type'";
}
if (!int.TryParse(modbusAddressTextBox.Text, out iVal) || iVal < 1 || iVal > 247)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'Modbus Address' must be between 1 and 247";
}
if (!int.TryParse(converterFunctionTextBox.Text, out iVal) || (iVal != 3 && iVal != 4))
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'Converter Function' must be 3 or 4";
}
if (!int.TryParse(converterFirstRegisterTextBox.Text, out iVal) || iVal < 0 || iVal > 65535)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'Converter First Register' must be between 0 and 65535";
}
if (!int.TryParse(converterRegisterCountTextBox.Text, out iVal) || iVal < 1 || iVal > 8)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'Converter Register Count' must be between 1 and 8";
}
if (!int.TryParse(converterChannelTextBox.Text, out iVal) || iVal < 0)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Invalid 'Converter Channel'";
}
if (!valueSourceComboBox.Items.Contains(valueSourceComboBox.Text))
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Invalid 'Value Source'";
}
if (!floatByteOrderComboBox.Items.Contains(floatByteOrderComboBox.Text))
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Invalid 'Float Byte Order'";
}
if (!double.TryParse(raw4TextBox.Text, out dVal) ||
!double.TryParse(raw20TextBox.Text, out dVal))
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Invalid raw scaling values";
}
if (!double.TryParse(cond4TextBox.Text, out dVal) ||
!double.TryParse(cond20TextBox.Text, out dVal))
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Invalid conductivity scaling values";
}
if (!double.TryParse(lowLimitTextBox.Text, out dVal) ||
!double.TryParse(highLimitTextBox.Text, out dVal))
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Invalid limit values";
}
if (!int.TryParse(dataTimeoutTextBox.Text, out iVal) || iVal < 100)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'Data Timeout (ms)' must be at least 100";
}
return flags;
}
public CfgUpdateFlags UpdateCfg()
{
if (config == null) return CfgUpdateFlags.Error;
config.Name = nameTextBox.Text;
config.ParentName = parentNameComboBox.Text.Equals("---") ? string.Empty : parentNameComboBox.Text;
config.ConnectionType =
(AnalyzerConnectionType)Enum.Parse(
typeof(AnalyzerConnectionType),
connectionTypeComboBox.Text);
config.ModbusAddress = (byte)int.Parse(modbusAddressTextBox.Text);
config.DirectRegisterAddress = (ushort)int.Parse(directRegisterTextBox.Text);
config.DirectFloatByteOrder =
(AnalyzerFloatByteOrder)Enum.Parse(
typeof(AnalyzerFloatByteOrder),
directFloatByteOrderComboBox.Text);
config.ConverterReadFunction = (byte)int.Parse(converterFunctionTextBox.Text);
config.ConverterFirstRegister = (ushort)int.Parse(converterFirstRegisterTextBox.Text);
config.ConverterRegisterCount = (ushort)int.Parse(converterRegisterCountTextBox.Text);
config.ConverterChannel = int.Parse(converterChannelTextBox.Text);
config.ConverterValueSource =
(AnalyzerValueSource)Enum.Parse(
typeof(AnalyzerValueSource),
valueSourceComboBox.Text);
config.ConverterFloatByteOrder =
(AnalyzerFloatByteOrder)Enum.Parse(
typeof(AnalyzerFloatByteOrder),
floatByteOrderComboBox.Text);
config.RawValueAt4mA = Utils.ParseSDouble(raw4TextBox.Text);
config.RawValueAt20mA = Utils.ParseSDouble(raw20TextBox.Text);
config.ConductivityAt4mA = Utils.ParseSDouble(cond4TextBox.Text);
config.ConductivityAt20mA = Utils.ParseSDouble(cond20TextBox.Text);
config.Unit = unitTextBox.Text;
config.DisplayFormat = formatTextBox.Text;
config.LowLimit = Utils.ParseSDouble(lowLimitTextBox.Text);
config.HighLimit = Utils.ParseSDouble(highLimitTextBox.Text);
config.DataTimeoutMs = int.Parse(dataTimeoutTextBox.Text);
return CfgUpdateFlags.RestartRqrd;
}
private void diagnosticsButton_Click(object sender, EventArgs e)
{
if (config == null) return;
Analyzer analyzer = TbfComponents.FindComponent(config.Name) as Analyzer;
if (analyzer == null)
{
MessageBox.Show(
"Runtime component was not found. Diagnostics are available only while the component is initialized.",
"Diagnostics",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
return;
}
analyzer.ShowDiagnostics();
}
}
}

View File

@ -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>

View File

@ -0,0 +1,167 @@
using System;
using System.Text;
using TBF.Rig.Modbus.WaterAnalyzerUni;
namespace TBF.Rig.Modbus.WaterAnalyzerUni
{
public class AnalyzerDiagnostics
{
public string LastRequest { get; private set; }
public string LastResponse { get; private set; }
public string LastError { get; private set; }
public int RequestCount { get; private set; }
public int ResponseCount { get; private set; }
public int ErrorCount { get; private set; }
public void Clear()
{
LastRequest = string.Empty;
LastResponse = string.Empty;
LastError = string.Empty;
RequestCount = 0;
ResponseCount = 0;
ErrorCount = 0;
}
public void SetRequest(byte address, byte function, ushort firstRegister, ushort registerCount)
{
RequestCount++;
LastRequest = string.Format(
"TX {0:HH:mm:ss.fff}{1}Address: {2}{1}Function: 0x{3:X2}{1}First register: {4}{1}Register count: {5}",
DateTime.Now,
Environment.NewLine,
address,
function,
firstRegister,
registerCount);
}
public void SetResponse(
byte[] telegram,
ushort[] rawRegisters,
double rawValue,
double milliAmps,
double conductivity,
string unit)
{
ResponseCount++;
var sb = new StringBuilder();
sb.AppendFormat("RX {0:HH:mm:ss.fff}", DateTime.Now);
sb.AppendLine();
sb.Append("Telegram: ");
sb.AppendLine(ToHex(telegram));
if (rawRegisters != null)
{
for (int i = 0; i < rawRegisters.Length; i++)
{
sb.AppendFormat("Raw[{0}]: {1} / 0x{1:X4}", i, rawRegisters[i]);
sb.AppendLine();
}
if (rawRegisters.Length >= 4)
{
sb.AppendFormat("Float ABCD: {0}", ToFloat(rawRegisters[2], rawRegisters[3], AnalyzerFloatByteOrder.ABCD));
sb.AppendLine();
sb.AppendFormat("Float BADC: {0}", ToFloat(rawRegisters[2], rawRegisters[3], AnalyzerFloatByteOrder.BADC));
sb.AppendLine();
sb.AppendFormat("Float CDAB: {0}", ToFloat(rawRegisters[2], rawRegisters[3], AnalyzerFloatByteOrder.CDAB));
sb.AppendLine();
sb.AppendFormat("Float DCBA: {0}", ToFloat(rawRegisters[2], rawRegisters[3], AnalyzerFloatByteOrder.DCBA));
sb.AppendLine();
}
}
sb.AppendFormat("Raw value: {0:0.###}", rawValue);
sb.AppendLine();
sb.AppendFormat("Current: {0:0.000} mA", milliAmps);
sb.AppendLine();
sb.AppendFormat("Conductivity: {0:0.###} {1}", conductivity, unit);
LastResponse = sb.ToString();
LastError = string.Empty;
}
public void SetError(string message)
{
ErrorCount++;
LastError = string.Format("{0:HH:mm:ss.fff} {1}", DateTime.Now, message);
}
public string GetText()
{
var sb = new StringBuilder();
sb.AppendLine("Water Analyzer Diagnostics");
sb.AppendLine("--------------------------------");
sb.AppendFormat("Requests: {0}", RequestCount);
sb.AppendLine();
sb.AppendFormat("Responses: {0}", ResponseCount);
sb.AppendLine();
sb.AppendFormat("Errors: {0}", ErrorCount);
sb.AppendLine();
sb.AppendLine();
if (!string.IsNullOrEmpty(LastRequest))
{
sb.AppendLine(LastRequest);
sb.AppendLine();
}
if (!string.IsNullOrEmpty(LastResponse))
{
sb.AppendLine(LastResponse);
sb.AppendLine();
}
if (!string.IsNullOrEmpty(LastError))
{
sb.AppendLine("Last error:");
sb.AppendLine(LastError);
}
return sb.ToString();
}
static string ToHex(byte[] data)
{
if (data == null) return string.Empty;
var sb = new StringBuilder();
for (int i = 0; i < data.Length; i++)
{
if (i > 0) sb.Append(" ");
sb.Append(data[i].ToString("X2"));
}
return sb.ToString();
}
static float ToFloat(ushort hi, ushort lo, AnalyzerFloatByteOrder order)
{
byte a = (byte)(hi >> 8);
byte b = (byte)(hi & 0xFF);
byte c = (byte)(lo >> 8);
byte d = (byte)(lo & 0xFF);
byte[] bytes;
switch (order)
{
case AnalyzerFloatByteOrder.ABCD: bytes = new byte[] { d, c, b, a }; break;
case AnalyzerFloatByteOrder.BADC: bytes = new byte[] { c, d, a, b }; break;
case AnalyzerFloatByteOrder.CDAB: bytes = new byte[] { b, a, d, c }; break;
case AnalyzerFloatByteOrder.DCBA: bytes = new byte[] { a, b, c, d }; break;
default: bytes = new byte[] { d, c, b, a }; break;
}
return BitConverter.ToSingle(bytes, 0);
}
}
}

View File

@ -0,0 +1,91 @@
namespace TBF.Rig.Modbus.WaterAnalyzerUni
{
partial class AnalyzerDiagnosticsForm
{
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.TextBox diagnosticsTextBox;
private System.Windows.Forms.Button clearButton;
private System.Windows.Forms.Button closeButton;
private System.Windows.Forms.Timer refreshTimer;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
components.Dispose();
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.diagnosticsTextBox = new System.Windows.Forms.TextBox();
this.clearButton = new System.Windows.Forms.Button();
this.closeButton = new System.Windows.Forms.Button();
this.refreshTimer = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout();
//
// diagnosticsTextBox
//
this.diagnosticsTextBox.Anchor =
((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top |
System.Windows.Forms.AnchorStyles.Bottom) |
System.Windows.Forms.AnchorStyles.Left) |
System.Windows.Forms.AnchorStyles.Right)));
this.diagnosticsTextBox.Font = new System.Drawing.Font("Consolas", 9F);
this.diagnosticsTextBox.Location = new System.Drawing.Point(12, 12);
this.diagnosticsTextBox.Multiline = true;
this.diagnosticsTextBox.Name = "diagnosticsTextBox";
this.diagnosticsTextBox.ReadOnly = true;
this.diagnosticsTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.diagnosticsTextBox.Size = new System.Drawing.Size(660, 390);
this.diagnosticsTextBox.TabIndex = 0;
this.diagnosticsTextBox.WordWrap = false;
//
// clearButton
//
this.clearButton.Anchor =
((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom |
System.Windows.Forms.AnchorStyles.Left)));
this.clearButton.Location = new System.Drawing.Point(12, 415);
this.clearButton.Name = "clearButton";
this.clearButton.Size = new System.Drawing.Size(90, 27);
this.clearButton.TabIndex = 1;
this.clearButton.Text = "Clear";
this.clearButton.UseVisualStyleBackColor = true;
this.clearButton.Click += new System.EventHandler(this.clearButton_Click);
//
// closeButton
//
this.closeButton.Anchor =
((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom |
System.Windows.Forms.AnchorStyles.Right)));
this.closeButton.Location = new System.Drawing.Point(582, 415);
this.closeButton.Name = "closeButton";
this.closeButton.Size = new System.Drawing.Size(90, 27);
this.closeButton.TabIndex = 2;
this.closeButton.Text = "Close";
this.closeButton.UseVisualStyleBackColor = true;
this.closeButton.Click += new System.EventHandler(this.closeButton_Click);
//
// refreshTimer
//
this.refreshTimer.Interval = 500;
this.refreshTimer.Tick += new System.EventHandler(this.refreshTimer_Tick);
//
// AnalyzerDiagnosticsForm
//
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(684, 454);
this.Controls.Add(this.diagnosticsTextBox);
this.Controls.Add(this.clearButton);
this.Controls.Add(this.closeButton);
this.Name = "AnalyzerDiagnosticsForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Water Analyzer - Modbus Diagnostics";
this.Load += new System.EventHandler(this.AnalyzerDiagnosticsForm_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
}
}

View File

@ -0,0 +1,56 @@
using System;
using System.Windows.Forms;
namespace TBF.Rig.Modbus.WaterAnalyzerUni
{
public partial class AnalyzerDiagnosticsForm : Form
{
readonly Analyzer analyzer;
public AnalyzerDiagnosticsForm(Analyzer analyzer)
{
if (analyzer == null) throw new ArgumentNullException("analyzer");
this.analyzer = analyzer;
InitializeComponent();
}
private void AnalyzerDiagnosticsForm_Load(object sender, EventArgs e)
{
refreshTimer.Start();
RefreshDiagnostics();
}
private void refreshTimer_Tick(object sender, EventArgs e)
{
RefreshDiagnostics();
}
private void RefreshDiagnostics()
{
diagnosticsTextBox.Text =
analyzer.Diagnostics != null
? analyzer.Diagnostics.GetText()
: "Diagnostics are not available.";
}
private void clearButton_Click(object sender, EventArgs e)
{
if (analyzer.Diagnostics != null)
analyzer.Diagnostics.Clear();
RefreshDiagnostics();
}
private void closeButton_Click(object sender, EventArgs e)
{
Close();
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
refreshTimer.Stop();
base.OnFormClosing(e);
}
}
}

View File

@ -0,0 +1,31 @@
///
/// Copyright (c) 2020 Sensus Slovensko a.s.
///
using System.Collections.Generic;
using TBF.Rig.Generic;
namespace TBF.Rig.Modbus.WaterAnalyzerUni
{
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 Analyzer(); }
public IComponent GetComponent(IComponentCfg cfg, IList<IComponent> components)
{
return new Analyzer(cfg, components);
}
public IComponentCfg DefaultConfig()
{
return new AnalyzerCfg(this.GetType().Namespace.Substring(24), this);
}
public IComponentCfg CmpntCfgFromCmpntEntity(Config.Entities.Component component)
{
return ComponentCfgBase.CreateFromDbEntity(AnalyzerCfg.Serializer, component, this);
}
}
}

View File

@ -0,0 +1,40 @@
///
/// Copyright (c) 2020 Sensus Slovensko a.s.
///
using System;
using log4net;
using TBF.Boxes;
namespace TBF.Rig.Modbus.WaterAnalyzerUni
{
public class ReadConductivityOp : IOperation
{
private static readonly ILog log = LogManager.GetLogger(typeof(ReadConductivityOp));
public override string ToString() { return "ReadConductivityOp(.,.)"; }
readonly Analyzer analyzer;
FloatBox conductivity;
public ReadConductivityOp(Analyzer analyzer, ref FloatBox conduct)
{
if (analyzer == null) throw new ArgumentNullException("analyzer");
this.analyzer = analyzer;
this.conductivity = conduct;
log.Debug(this.ToString());
}
public void Start()
{
conductivity.Val = analyzer.ReadConductivity();
}
public Event Run()
{
conductivity.Val = analyzer.ReadConductivity();
return Event.ConductivityDone;
}
public void Stop() { }
}
}

View File

@ -0,0 +1,64 @@
///
/// Copyright (c) 2020 Sensus Slovensko a.s.
///
using System;
using log4net;
using TBF.Boxes;
namespace TBF.Rig.Modbus.WaterAnalyzerUni
{
public class ReadTempOp : IOperation
{
private static readonly ILog log = LogManager.GetLogger(typeof(ReadTempOp));
public override string ToString() { return string.Format("ReadTempOp(.,{0},.)", eventDone); }
/// Set by the constructor
readonly Analyzer analyzer;
readonly Event eventDone;
/// Measured value
DoubleBox temperature;
/// <summary>
/// Events: eventDone or Error
/// </summary>
/// <param name="analyzer">Water temperature meter reference</param>
/// <param name="temperature">Reference to the measured temperature variable, value is in l</param>
/// <param name="eventDone">Event to be returned by Run() when completed OK</param>
public ReadTempOp(Analyzer analyzer, ref DoubleBox temperature, Event eventDone)
{
if (analyzer == null) throw new ArgumentNullException("analyzer");
this.analyzer = analyzer;
this.temperature = temperature;
this.eventDone = eventDone;
log.Debug(this.ToString());
}
public ReadTempOp(Analyzer levelMeter, ref DoubleBox temperature)
: this(levelMeter, ref temperature, Event.TempDone)
{
}
/// <summary>Start this operation</summary>
public void Start()
{
temperature.Val = analyzer.ReadTemperature();
}
/// <summary>Run this operation</summary>
/// <returns>
/// Event.temperatureDone
/// </returns>
public Event Run()
{
temperature.Val = analyzer.ReadTemperature();
return eventDone;
}
/// <summary>Stop this operation</summary>
public void Stop()
{
}
}
}

View File

@ -80,6 +80,7 @@ namespace TBF.Rig
new Modbus.TempMeter.Meret.Factory(), /// Meret temperature meter connected via a modbus on a PC
new Modbus.UltrasoundLevelMeter.Factory(),
new Modbus.WaterAnalyzer.Factory(),
new Modbus.WaterAnalyzerUni.Factory(),
new Modbus.ConductivityMeter.Factory(),
new Network.Adapter.Factory(),
new Network.AdapterFTP.Factory(),

View File

@ -979,6 +979,24 @@
<Compile Include="Rig\Modbus\UltrasoundLevelMeter\ReadLevelOp.cs" />
<Compile Include="Rig\Modbus\UltrasoundLevelMeter\ReadStableLevelOp.cs" />
<Compile Include="Rig\Modbus\UltrasoundLevelMeter\ReadTempOp.cs" />
<Compile Include="Rig\Modbus\WaterAnalyzerUni\Analyzer.cs" />
<Compile Include="Rig\Modbus\WaterAnalyzerUni\AnalyzerCfg.cs" />
<Compile Include="Rig\Modbus\WaterAnalyzerUni\AnalyzerCfgCtrl.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Rig\Modbus\WaterAnalyzerUni\AnalyzerCfgCtrl.Designer.cs">
<DependentUpon>AnalyzerCfgCtrl.cs</DependentUpon>
</Compile>
<Compile Include="Rig\Modbus\WaterAnalyzerUni\AnalyzerDiagnostics.cs" />
<Compile Include="Rig\Modbus\WaterAnalyzerUni\AnalyzerDiagnosticsForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Rig\Modbus\WaterAnalyzerUni\AnalyzerDiagnosticsForm.Designer.cs">
<DependentUpon>AnalyzerDiagnosticsForm.cs</DependentUpon>
</Compile>
<Compile Include="Rig\Modbus\WaterAnalyzerUni\Factory.cs" />
<Compile Include="Rig\Modbus\WaterAnalyzerUni\ReadConductivityOp.cs" />
<Compile Include="Rig\Modbus\WaterAnalyzerUni\ReadTempOp.cs" />
<Compile Include="Rig\Modbus\WaterAnalyzer\Factory.cs" />
<Compile Include="Rig\Modbus\WaterAnalyzer\Analyzer.cs" />
<Compile Include="Rig\Modbus\WaterAnalyzer\AnalyzerCfg.cs" />
@ -3611,6 +3629,9 @@
<EmbeddedResource Include="Rig\Modbus\TankSelector\TankSelectorCfgCtrl.resx">
<DependentUpon>TankSelectorCfgCtrl.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Rig\Modbus\WaterAnalyzerUni\AnalyzerCfgCtrl.resx">
<DependentUpon>AnalyzerCfgCtrl.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Rig\Network\AdapterFTP\NetadapterCfgCtrl.resx">
<DependentUpon>NetadapterCfgCtrl.cs</DependentUpon>
</EmbeddedResource>