//****************************************************************************************************** // ModbusHubClient.cs - Gbtc // // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 07/18/2016 - J. Ritchie Carroll // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.IO.Ports; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using GSF; using GSF.Web.Hubs; using Modbus.Device; using Modbus.Utility; namespace ModbusAdapters { /// /// Represents a client instance of a for Modbus connections. /// public class ModbusHubClient : HubClientBase { #region [ Members ] // Fields private IModbusMaster m_modbusConnection; private TcpClient m_tcpClient; private UdpClient m_udpClient; private SerialPort m_serialClient; private byte m_unitID; private bool m_disposed; #endregion #region [ Methods ] /// /// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { if (!m_disposed) { try { // This will be done regardless of whether the object is finalized or disposed. if (disposing) DisposeConnections(); } finally { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } } private void DisposeConnections() { m_tcpClient?.Dispose(); m_udpClient?.Dispose(); m_serialClient?.Dispose(); m_modbusConnection?.Dispose(); } private void UpdateStatus(string message, UpdateType type) { string color = "white"; switch (type) { case UpdateType.Warning: color = "yellow"; break; case UpdateType.Alarm: color = "red"; break; } ClientScript?.connectionStatusUpdate(message, color); } public void Disconnect() { DisposeConnections(); ClientScript?.connectionTerminated(); UpdateStatus("Device disconnected.", UpdateType.Warning); } public Task Connect(string connectionString) { return Task.Factory.StartNew(() => { try { Dictionary parameters = connectionString.ParseKeyValuePairs(); string frameFormat, transport, setting; if (!parameters.TryGetValue("frameFormat", out frameFormat) || string.IsNullOrWhiteSpace(frameFormat)) throw new ArgumentException("Connection string is missing \"frameFormat\"."); if (!parameters.TryGetValue("transport", out transport) || string.IsNullOrWhiteSpace(transport)) throw new ArgumentException("Connection string is missing \"transport\"."); if (!parameters.TryGetValue("unitID", out setting) || !byte.TryParse(setting, out m_unitID)) throw new ArgumentException("Connection string is missing \"unitID\" or value is invalid."); bool useIP = false; bool useRTU = false; switch (frameFormat.ToUpperInvariant()) { case "RTU": useRTU = true; break; case "TCP": useIP = true; break; } if (useIP) { int port; if (!parameters.TryGetValue("port", out setting) || !int.TryParse(setting, out port)) throw new ArgumentException("Connection string is missing \"port\" or value is invalid."); if (transport.ToUpperInvariant() == "TCP") { string hostName; if (!parameters.TryGetValue("hostName", out hostName) || string.IsNullOrWhiteSpace(hostName)) throw new ArgumentException("Connection string is missing \"hostName\"."); return ConnectTcpModbusMaster(hostName, port); } string interfaceIP; if (!parameters.TryGetValue("interface", out interfaceIP)) interfaceIP = "0.0.0.0"; return ConnectUdpModbusMaster(interfaceIP, port); } string portName; int baudRate; int dataBits; Parity parity; StopBits stopBits; if (!parameters.TryGetValue("portName", out portName) || string.IsNullOrWhiteSpace(portName)) throw new ArgumentException("Connection string is missing \"portName\"."); if (!parameters.TryGetValue("baudRate", out setting) || !int.TryParse(setting, out baudRate)) throw new ArgumentException("Connection string is missing \"baudRate\" or value is invalid."); if (!parameters.TryGetValue("dataBits", out setting) || !int.TryParse(setting, out dataBits)) throw new ArgumentException("Connection string is missing \"dataBits\" or value is invalid."); if (!parameters.TryGetValue("parity", out setting) || !Enum.TryParse(setting, out parity)) throw new ArgumentException("Connection string is missing \"parity\" or value is invalid."); if (!parameters.TryGetValue("stopBits", out setting) || !Enum.TryParse(setting, out stopBits)) throw new ArgumentException("Connection string is missing \"stopBits\" or value is invalid."); return ConnectSerialModbusMaster(useRTU, portName, baudRate, dataBits, parity, stopBits); } catch (Exception ex) { ClientScript?.connectionFailed(); UpdateStatus($"Failed to connect to device: {ex.Message}", UpdateType.Alarm); } return false; }); } private bool ConnectTcpModbusMaster(string hostName, int port) { DisposeConnections(); try { AttemptingConnection("TCP"); m_tcpClient = new TcpClient(hostName, port); m_modbusConnection = ModbusIpMaster.CreateIp(m_tcpClient); ConnectionSucceeded("TCP"); return true; } catch (Exception ex) { ConnectionFailed("TCP", ex.Message); } return false; } private bool ConnectUdpModbusMaster(string interfaceIP, int port) { DisposeConnections(); try { AttemptingConnection("UDP"); m_udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse(interfaceIP), port)); m_modbusConnection = ModbusIpMaster.CreateIp(m_udpClient); ConnectionSucceeded("UDP"); return true; } catch (Exception ex) { ConnectionFailed("UDP", ex.Message); } return false; } private bool ConnectSerialModbusMaster(bool useRTU, string portName, int baudRate, int dataBits, Parity parity, StopBits stopBits) { DisposeConnections(); try { AttemptingConnection("Serial"); m_serialClient = new SerialPort(portName, baudRate, parity, dataBits, stopBits); m_modbusConnection = useRTU ? ModbusSerialMaster.CreateRtu(m_serialClient) : ModbusSerialMaster.CreateAscii(m_serialClient); ConnectionSucceeded("Serial"); return true; } catch (Exception ex) { ConnectionFailed("Serial", ex.Message); } return false; } private void AttemptingConnection(string type) { ClientScript?.attemptingConnection(); UpdateStatus($"Attempting to connect to device using {type}...", UpdateType.Information); } private void ConnectionSucceeded(string type) { ClientScript?.connectionSucceeded(); UpdateStatus($"Connected to device using {type}", UpdateType.Information); } private void ConnectionFailed(string type, string exceptionMessage) { ClientScript?.connectionFailed(); UpdateStatus($"Failed to connect to device using {type}: {exceptionMessage}", UpdateType.Alarm); } public async Task ReadDiscreteInputs(ushort startAddress, ushort pointCount) { return await m_modbusConnection.ReadInputsAsync(m_unitID, startAddress, pointCount); } public async Task ReadCoils(ushort startAddress, ushort pointCount) { return await m_modbusConnection.ReadCoilsAsync(m_unitID, startAddress, pointCount); } public async Task ReadInputRegisters(ushort startAddress, ushort pointCount) { return await m_modbusConnection.ReadInputRegistersAsync(m_unitID, startAddress, pointCount); } public async Task ReadHoldingRegisters(ushort startAddress, ushort pointCount) { return await m_modbusConnection.ReadHoldingRegistersAsync(m_unitID, startAddress, pointCount); } public async Task WriteCoils(ushort startAddress, bool[] data) { await m_modbusConnection.WriteMultipleCoilsAsync(m_unitID, startAddress, data); } public async Task WriteHoldingRegisters(ushort startAddress, ushort[] data) { await m_modbusConnection.WriteMultipleRegistersAsync(m_unitID, startAddress, data); } public string DeriveString(ushort[] values) { return Encoding.Default.GetString(values.Select(BigEndianOrder.Default.GetBytes).SelectMany(bytes => bytes).ToArray()); } public float DeriveSingle(ushort highValue, ushort lowValue) { return ModbusUtility.GetSingle(highValue, lowValue); } public double DeriveDouble(ushort b3, ushort b2, ushort b1, ushort b0) { return ModbusUtility.GetDouble(b3, b2, b1, b0); } public int DeriveInt32(ushort highValue, ushort lowValue) { return (int)ModbusUtility.GetUInt32(highValue, lowValue); } public uint DeriveUInt32(ushort highValue, ushort lowValue) { return ModbusUtility.GetUInt32(highValue, lowValue); } public long DeriveInt64(ushort b3, ushort b2, ushort b1, ushort b0) { return (long)DeriveUInt64(b3, b2, b1, b0); } public ulong DeriveUInt64(ushort b3, ushort b2, ushort b1, ushort b0) { return Word.MakeQuadWord(ModbusUtility.GetUInt32(b3, b2), ModbusUtility.GetUInt32(b1, b0)); } #endregion } }