diff --git a/TBF/Rig/Modbus/WaterAnalyzerUni/Analyzer.cs b/TBF/Rig/Modbus/WaterAnalyzerUni/Analyzer.cs new file mode 100644 index 000000000..1670fc82c --- /dev/null +++ b/TBF/Rig/Modbus/WaterAnalyzerUni/Analyzer.cs @@ -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 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(); + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerCfg.cs b/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerCfg.cs new file mode 100644 index 000000000..8d80d710e --- /dev/null +++ b/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerCfg.cs @@ -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 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 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; + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerCfgCtrl.Designer.cs b/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerCfgCtrl.Designer.cs new file mode 100644 index 000000000..0912b56f0 --- /dev/null +++ b/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerCfgCtrl.Designer.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerCfgCtrl.cs b/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerCfgCtrl.cs new file mode 100644 index 000000000..fbaed3818 --- /dev/null +++ b/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerCfgCtrl.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerCfgCtrl.resx b/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerCfgCtrl.resx new file mode 100644 index 000000000..1af7de150 --- /dev/null +++ b/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerCfgCtrl.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerDiagnostics.cs b/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerDiagnostics.cs new file mode 100644 index 000000000..43e934a05 --- /dev/null +++ b/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerDiagnostics.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerDiagnosticsForm.Designer.cs b/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerDiagnosticsForm.Designer.cs new file mode 100644 index 000000000..115d5cd5a --- /dev/null +++ b/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerDiagnosticsForm.Designer.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerDiagnosticsForm.cs b/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerDiagnosticsForm.cs new file mode 100644 index 000000000..28c65fa07 --- /dev/null +++ b/TBF/Rig/Modbus/WaterAnalyzerUni/AnalyzerDiagnosticsForm.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Modbus/WaterAnalyzerUni/Factory.cs b/TBF/Rig/Modbus/WaterAnalyzerUni/Factory.cs new file mode 100644 index 000000000..1250fa540 --- /dev/null +++ b/TBF/Rig/Modbus/WaterAnalyzerUni/Factory.cs @@ -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 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); + } + } +} \ No newline at end of file diff --git a/TBF/Rig/Modbus/WaterAnalyzerUni/ReadConductivityOp.cs b/TBF/Rig/Modbus/WaterAnalyzerUni/ReadConductivityOp.cs new file mode 100644 index 000000000..2d2c89b0d --- /dev/null +++ b/TBF/Rig/Modbus/WaterAnalyzerUni/ReadConductivityOp.cs @@ -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() { } + } +} \ No newline at end of file diff --git a/TBF/Rig/Modbus/WaterAnalyzerUni/ReadTempOp.cs b/TBF/Rig/Modbus/WaterAnalyzerUni/ReadTempOp.cs new file mode 100644 index 000000000..bf8822721 --- /dev/null +++ b/TBF/Rig/Modbus/WaterAnalyzerUni/ReadTempOp.cs @@ -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; + + /// + /// Events: eventDone or Error + /// + /// Water temperature meter reference + /// Reference to the measured temperature variable, value is in l + /// Event to be returned by Run() when completed OK + 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) + { + } + + + /// Start this operation + public void Start() + { + temperature.Val = analyzer.ReadTemperature(); + } + + /// Run this operation + /// + /// Event.temperatureDone + /// + public Event Run() + { + temperature.Val = analyzer.ReadTemperature(); + return eventDone; + } + + /// Stop this operation + public void Stop() + { + } + } +} diff --git a/TBF/Rig/TbfComponents.cs b/TBF/Rig/TbfComponents.cs index 38fa3d8c3..677fc5fb7 100644 --- a/TBF/Rig/TbfComponents.cs +++ b/TBF/Rig/TbfComponents.cs @@ -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(), diff --git a/TBF/TBF.csproj b/TBF/TBF.csproj index 19c5a817b..35f912781 100644 --- a/TBF/TBF.csproj +++ b/TBF/TBF.csproj @@ -979,6 +979,24 @@ + + + + UserControl + + + AnalyzerCfgCtrl.cs + + + + Form + + + AnalyzerDiagnosticsForm.cs + + + + @@ -3611,6 +3629,9 @@ TankSelectorCfgCtrl.cs + + AnalyzerCfgCtrl.cs + NetadapterCfgCtrl.cs