using HslCommunication; using HslCommunication.Core; using HslCommunication.LogNet; using HslCommunication.ModBus; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace WIDESEAWCS_Communicator { /// /// ModbusTcp通讯类 /// [Description("ModbusTcp")] public class ModbusTcp : BaseCommunicator { #region PrivateMember private ModbusTcpNet plc; private string _ipAddress; private int _port; private bool _connected; private string _name; private ILogNet _logNet; private bool _isPing = true; #endregion #region PublicMember /// /// 日志记录实例对象 /// public override ILogNet LogNet => _logNet; /// /// 设备名称 /// public override string Name => _name; /// /// 获取当前通讯器是否已连接到PLC。 /// public override bool IsConnected => _connected; /// /// 是否在写入数据后读取数据确认。 /// public override bool IsReadAfterWrite { get; set; } = true; #endregion #region Constructor Function /// /// 构造函数 /// /// 设备的IP地址 /// 连接使用的端口号 /// 设备名称 public ModbusTcp(string ipAddress, int port, string name) { string path = AppDomain.CurrentDomain.BaseDirectory + $"Log_PLCReadWrite\\{name}"; _logNet = new LogNetFileSize(path, 10 * 1024 * 1024, 100); bool ipCheck = IPAddress.TryParse(ipAddress, out IPAddress? address); if (!ipCheck) { _logNet.WriteError(name, string.Format(CommunicationExceptionMessage.IpAddressErrorException, ipAddress)); throw new CommunicationException(string.Format(CommunicationExceptionMessage.IpAddressErrorException, ipAddress), CommunicationErrorType.IpAddressError); } _ipAddress = ipAddress;//通过构造函数赋值设备的IP地址 _port = port;//通过构造函数赋值连接使用的端口号 _name = name; } #endregion #region Private Method /// /// 从OperateResult对象中获取读取的数据。 /// /// 读取的数据类型。 /// HSLCommunication读取的OperateResult对象 /// 如果读取成功,返回读取结果,读取失败,抛出自定义通讯异常 /// 自定义通讯异常类 private object GetContent(OperateResult operateResult, string address) { try { if (!operateResult.IsSuccess) { throw new CommunicationException(string.Format(CommunicationExceptionMessage.ReadFailedException, typeof(T).Name, address, operateResult.Message), CommunicationErrorType.ReadFailed); } return operateResult.Content ?? throw new CommunicationException(string.Format(CommunicationExceptionMessage.ReadDataIsNull, address), CommunicationErrorType.ReadFailed); } catch (Exception ex) { LogNet.WriteException(Name, ex.Message, ex); throw new CommunicationException(ex.Message, CommunicationErrorType.ReadFailed, innerException: ex); } } /// /// /// /// /// /// /// /// /// private bool GetResult(OperateResult operateResult, string address, T value) where T : notnull { StringBuilder stringBuilder = new StringBuilder(); try { stringBuilder.AppendLine(string.Format(CommunicationInfoMessage.WriteData, address, value)); if (!operateResult.IsSuccess) { throw new CommunicationException(string.Format(CommunicationExceptionMessage.WriteFailedException, typeof(T).Name, address, value, operateResult.Message), CommunicationErrorType.WriteFailed); } else { if (IsReadAfterWrite) { object? obj = null; for (int i = 0; i < 5; i++) { T readValue = Read(address); stringBuilder.AppendLine(string.Format(CommunicationInfoMessage.WriteAfterRead, readValue, value)); obj = readValue; if (readValue.Equals(value)) { stringBuilder.AppendLine(string.Format(CommunicationInfoMessage.WriteAndReadCheckSuccess, address, value, readValue)); return true; } else if (i < 4) { Write(address, value); } } stringBuilder.AppendLine(string.Format(CommunicationExceptionMessage.WriteAndReadCheckFaild, address, value, obj)); throw new CommunicationException(stringBuilder.ToString(), CommunicationErrorType.WriteFailed); } else { return true; } } } catch (Exception ex) { LogNet.WriteException(Name, ex.Message, ex); throw new CommunicationException(ex.Message, CommunicationErrorType.WriteFailed, innerException: ex); } finally { LogNet.WriteInfo(Name, stringBuilder.ToString()); } } /// /// 写入数据 /// /// /// /// /// private OperateResult Write(string address, object value) { try { Type type = value.GetType(); switch (Type.GetTypeCode(type)) { case TypeCode.Int32: return plc.Write(address, Convert.ToInt32(value)); case TypeCode.UInt32: return plc.Write(address, Convert.ToUInt32(value)); case TypeCode.Int16: return plc.Write(address, Convert.ToInt16(value)); case TypeCode.UInt16: return plc.Write(address, Convert.ToUInt16(value)); case TypeCode.Single: return plc.Write(address, Convert.ToSingle(value)); case TypeCode.Boolean: return plc.Write(address, Convert.ToBoolean(value)); case TypeCode.Byte: return plc.Write(address, Convert.ToByte(value)); case TypeCode.String: return plc.Write(address, Convert.ToString(value)); case TypeCode.Char: return plc.Write(address, Convert.ToChar(value)); default: if (value is int[]) { return plc.Write(address, value as int[]); } else if (value is uint[]) { return plc.Write(address, value as uint[]); } else if (value is short[]) { return plc.Write(address, value as short[]); } else if (value is ushort[]) { return plc.Write(address, value as ushort[]); } else if (value is bool[]) { return plc.Write(address, value as bool[]); } else if (value is float[]) { return plc.Write(address, value as float[]); } else if (value is double[]) { return plc.Write(address, value as double[]); } else if (value is byte[]) { return plc.Write(address, value as byte[]); } throw new CommunicationException(string.Format(CommunicationExceptionMessage.DataTypeErrorException, type.Name, address), CommunicationErrorType.TypeError); } } catch (CommunicationException ex) { throw new CommunicationException(ex.Message, ex.ErrorType); } catch (Exception ex) { //读取异常时抛出自定义通讯异常类 throw new CommunicationException(string.Format(CommunicationExceptionMessage.DataTypeErrorException, address, value), CommunicationErrorType.TypeError, innerException: ex); } } private object Read(string address, TypeCode typeCode, ushort length = 1) { try { switch (typeCode) { case TypeCode.Int32: if (length > 1) return (int[])GetContent(plc.ReadInt32(address, length), address); else return (int)GetContent(plc.ReadInt32(address), address); case TypeCode.UInt32: if (length > 1) return (uint[])GetContent(plc.ReadUInt32(address, length), address); else return (uint)GetContent(plc.ReadUInt32(address), address); case TypeCode.Int16: if (length > 1) return (short[])GetContent(plc.ReadInt16(address, length), address); else return (short)GetContent(plc.ReadInt16(address), address); case TypeCode.UInt16: if (length > 1) return (ushort[])GetContent(plc.ReadUInt16(address, length), address); else return (ushort)GetContent(plc.ReadUInt16(address), address); case TypeCode.Single: if (length > 1) return (float[])GetContent(plc.ReadFloat(address, length), address); else return (float)GetContent(plc.ReadFloat(address), address); case TypeCode.Boolean: if (length > 1) return (bool[])GetContent(plc.ReadBool(address, length), address); else return (bool)GetContent(plc.ReadBool(address), address); case TypeCode.Byte: return (byte[])GetContent(plc.Read(address, length), address); case TypeCode.String: return (string)GetContent(plc.ReadString(address, length), address); case TypeCode.Char: return (char[])GetContent(plc.Read(address, length), address); default: throw new CommunicationException(string.Format(CommunicationExceptionMessage.DataTypeErrorException, typeCode.ToString(), address), CommunicationErrorType.TypeError); } } catch (CommunicationException ex) { //读取异常时抛出自定义通讯异常类 throw new CommunicationException(ex.Message, ex.ErrorType); } catch (Exception ex) { //读取异常时抛出自定义通讯异常类 throw new CommunicationException($"读取数据异常,错误信息:{ex.Message}", CommunicationErrorType.ReadException, innerException: ex); } } private void Ping() { Task.Run(() => { while (_isPing) { try { IPStatus status = plc.IpAddressPing(); if (status == IPStatus.Success) _connected = true; else _connected = false; } finally { Thread.Sleep(100); } } }); } #endregion #region PublicMethod /// /// 连接到PLC。 /// /// 如果连接成功则返回true,否则返回false。 public override bool Connect() { try { plc = new ModbusTcpNet(_ipAddress, _port); OperateResult operateResult = plc.ConnectServer();//连接PLC _connected = operateResult.IsSuccess;//将连接是否成功赋值给当前通讯器是否已连接到PLC if (_connected) LogNet.WriteInfo(Name, string.Format(CommunicationInfoMessage.ConnectSuccess, _ipAddress, _port)); else LogNet.WriteError(Name, string.Format(CommunicationExceptionMessage.ConnectFaild, _ipAddress, _port, operateResult.Message)); Ping(); return operateResult.IsSuccess; } catch (Exception ex) { LogNet.WriteException(Name, string.Format(CommunicationExceptionMessage.ConnectFaild, _ipAddress, _port, ex.Message), ex); //连接异常时抛出自定义异常类 throw new CommunicationException(ex.Message, CommunicationErrorType.ConnectionFailed, innerException: ex); } } /// /// 断开与工业设备的连接。 /// /// 如果成功断开连接则返回true,如果已经是断开状态则返回false。 public override bool Disconnect() { try { if (plc != null) { OperateResult operateResult = plc.ConnectClose();//断开与PLC的连接 return operateResult.IsSuccess; } return false; } catch (Exception ex) { return false; } finally { _connected = false; } } /// /// 释放对象资源的接口。 /// public override void Dispose() { _isPing = false; Disconnect(); plc.Dispose(); GC.SuppressFinalize(this); } /// /// 从PLC读取数据。 /// /// 源地址,具体格式取决于使用的工业协议。 /// 要读取的数据长度。 /// 读取到的数据,如果读取失败则可能返回null或空数组。 public override byte[] Read(string address, int length) { return (byte[])GetContent(plc.Read(address, (ushort)length), address); } /// /// 从PLC读取数据。 /// /// 读取数据的类型泛型。 /// 源地址,具体格式取决于使用的工业协议。 /// 读取到的数据,如果读取失败则可能返回null或抛出异常。 public override T Read(string address) { Type type = typeof(T); return (T)Read(address, Type.GetTypeCode(type)); } /// /// 从PLC读取数据。 /// /// 读取数据的类型泛型。 /// 源地址,具体格式取决于使用的工业协议。 /// 读取的长度。 /// 读取到的数据,如果读取失败则可能返回null或抛出异常。 public override T[] Read(string address, ushort length) { Type type = typeof(T); return (T[])Read(address, Type.GetTypeCode(type), length); } /// /// 从PLC读取数据。 /// /// 源地址,具体格式取决于使用的工业协议。 /// 数据类型。 /// 读取到的数据,如果读取失败则可能返回null或抛出异常。 public override object ReadAsObj(string address, string dataType) { return Read(address, SiemensDBDataType.GetTypeCode(dataType)); } /// /// 读取自定义的数据类型,需要继承自IDataTransfer接口,返回一个新的类型的实例对象。 /// /// 自定义的数据类型泛型。 /// 源地址,具体格式取决于使用的工业协议。 /// 成功返回自定义类型数据,失败抛出异常。 public override T ReadCustomer(string address) { throw new Exception("该方法暂不可用"); //try //{ // return plc.ReadCustomer(address).Content; //} //catch (Exception ex) //{ // LogNet.WriteException(Name, $"【{Name}】PLC读取异常,地址:【{address}】,错误信息:【{ex.Message}】", ex); // throw new CommunicationException(ex.Message, CommunicationErrorType.ReadException, innerException: ex); //} } /// /// 等待指定地址的泛型类型值为指定的值 /// /// 指定的值的类型泛型。 /// 源地址,具体格式取决于使用的工业协议。 /// 读取的频率。 /// 等待的超时时间,如果超时时间为-1的话,则是无期限等待。 /// 等待检测的值 /// 是否等待成功的结果对象,一旦通信失败,或是等待超时就返回失败。否则返回成功,并告知调用方等待了多久。 public override OperateResult Wait(string address, int readInterval, int waitTimeout, T value) { TypeCode typeCode = Type.GetTypeCode(typeof(T)); switch (typeCode) { case TypeCode.Byte: DateTime start = DateTime.Now; while (true) { OperateResult read = plc.Read(address, 1); if (!read.IsSuccess) return OperateResult.CreateFailedResult(read); if (read.Content[0] == Convert.ToByte(value)) return OperateResult.CreateSuccessResult(DateTime.Now - start); if (waitTimeout > 0 && (DateTime.Now - start).TotalMilliseconds > waitTimeout) { return new OperateResult(StringResources.Language.CheckDataTimeout + waitTimeout); } HslHelper.ThreadSleep(readInterval); } case TypeCode.Int16: OperateResult operateResultShort = plc.Wait(address, Convert.ToInt16(value), readInterval, waitTimeout); return operateResultShort; case TypeCode.Int32: OperateResult operateResultInt = plc.Wait(address, Convert.ToInt16(value), readInterval, waitTimeout); return operateResultInt; case TypeCode.UInt16: OperateResult operateResultUShort = plc.Wait(address, Convert.ToInt16(value), readInterval, waitTimeout); return operateResultUShort; case TypeCode.UInt32: OperateResult operateResultUInt = plc.Wait(address, Convert.ToInt16(value), readInterval, waitTimeout); return operateResultUInt; default: throw new NotSupportedException(); } } /// /// 向PLC写入数据。 /// /// 源地址,具体格式取决于使用的工业协议。 /// 要写入的数据。 /// 如果写入成功则返回true,否则抛出异常。 public override bool Write(string address, byte[] data) { try { OperateResult result = plc.Write(address, data); if (result.IsSuccess) { return result.IsSuccess; } else { //todo 写入失败 return false; } } catch (Exception ex) { //写入异常时抛出自定义通讯异常类 throw new CommunicationException($"写入数据异常,地址:【{address}】,错误信息: {ex.Message}", CommunicationErrorType.ReadFailed, innerException: ex); } } /// /// 向PLC写入数据。 /// /// 写入数据的类型泛型。 /// 源地址,具体格式取决于使用的工业协议。 /// 要写入的数据。 /// 如果写入成功则返回true,否则抛出异常。 public override bool Write(string address, T value) { return GetResult(Write(address, value), address, value); } /// /// 向PLC写入数据。 /// /// 写入数据的类型泛型。 /// 源地址,具体格式取决于使用的工业协议。 /// 要写入的数据。 /// 如果写入成功则返回true,否则抛出异常。 public override bool Write(string address, T[] values) { return GetResult(Write(address, values), address, values); } /// /// 写入自定义类型的数据,该类型必须继承自IDataTransfer接口。 /// /// 自定义的数据类型泛型。 /// 源地址,具体格式取决于使用的工业协议。 /// 要写入数据。 /// 如果写入成功则返回true,失败则抛出异常。 public override bool WriteCustomer(string address, [NotNull] T value) { throw new Exception("该方法暂不可用"); //StringBuilder stringBuilder = new StringBuilder(); //try //{ // OperateResult operateResult = plc.WriteCustomer(address, value); // stringBuilder.AppendLine(string.Format(CommunicationInfoMessage.WriteData, address, JsonConvert.SerializeObject(value))); // if (operateResult.IsSuccess) // { // object? obj = null; // for (int i = 0; i < 5; i++) // { // T readValue = ReadCustomer(address); // stringBuilder.AppendLine(string.Format(CommunicationInfoMessage.WriteAfterRead, address, JsonConvert.SerializeObject(readValue))); // obj = readValue; // PropertyInfo[] propertyInfos = typeof(T).GetProperties(); // for (int j = 0; j < propertyInfos.Length; j++) // { // object? writeValueItem = propertyInfos[j].GetValue(value); // object? readValueItem = propertyInfos[j].GetValue(readValue); // if (writeValueItem.Equals(readValueItem)) // { // stringBuilder.AppendLine(string.Format(CommunicationInfoMessage.WriteAndReadCheckSuccess, address, JsonConvert.SerializeObject(value), JsonConvert.SerializeObject(readValue))); // } // else // { // break; // } // if (j == propertyInfos.Length - 1) // return true; // } // plc.WriteCustomer(address, value); // } // stringBuilder.AppendLine(string.Format(CommunicationExceptionMessage.WriteAndReadCheckFaild, address, JsonConvert.SerializeObject(value), JsonConvert.SerializeObject(obj))); // throw new CommunicationException(string.Format(CommunicationExceptionMessage.WriteAndReadCheckFaild, address, JsonConvert.SerializeObject(value), JsonConvert.SerializeObject(obj)), CommunicationErrorType.WriteFailed); // } // else // { // throw new CommunicationException(string.Format(CommunicationExceptionMessage.WriteFailedException, typeof(T).Name, address, JsonConvert.SerializeObject(value), operateResult.Message), CommunicationErrorType.WriteFailed); // } //} //catch (Exception ex) //{ // LogNet.WriteException(Name, ex.Message, ex); // throw new CommunicationException(ex.Message, CommunicationErrorType.WriteFailed, innerException: ex); //} //finally //{ // LogNet.WriteInfo(Name, stringBuilder.ToString()); //} } /// /// 向PLC写入数据。 /// /// 源地址,具体格式取决于使用的工业协议。 /// 要写入的数据类型。 /// 要写入的数据。 /// 如果写入成功则返回true,失败则抛出异常。 /// public override bool WriteObj(string address, string dataType, object value) { bool obj = false; switch (dataType.ToLower()) { case SiemensDBDataType.DataType_DInt: { int writeVal; try { writeVal = Convert.ToInt32(value); } catch (Exception ex) { throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex); } obj = GetResult(Write(address, writeVal), address, writeVal); } break; case SiemensDBDataType.DataType_DW: { uint writeVal; try { writeVal = Convert.ToUInt32(value); } catch (Exception ex) { throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex); } obj = GetResult(Write(address, writeVal), address, writeVal); } break; case SiemensDBDataType.DataType_Int: { short writeVal; try { writeVal = Convert.ToInt16(value); } catch (Exception ex) { throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex); } obj = GetResult(Write(address, writeVal), address, writeVal); } break; case SiemensDBDataType.DataType_W: { ushort writeVal; try { writeVal = Convert.ToUInt16(value); } catch (Exception ex) { throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex); } obj = GetResult(Write(address, writeVal), address, writeVal); } break; case SiemensDBDataType.DataType_Float: { float writeVal; try { writeVal = Convert.ToSingle(value); } catch (Exception ex) { throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex); } obj = GetResult(Write(address, writeVal), address, writeVal); } break; case SiemensDBDataType.DataType_Bool: { bool writeVal; try { writeVal = Convert.ToBoolean(value); } catch (Exception ex) { throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex); } obj = GetResult(Write(address, writeVal), address, writeVal); } break; case SiemensDBDataType.DataType_Byte: { byte writeVal; try { writeVal = Convert.ToByte(value); } catch (Exception ex) { throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex); } obj = GetResult(Write(address, writeVal), address, writeVal); } break; case SiemensDBDataType.DataType_String: { string writeVal; try { writeVal = value.ToString(); } catch (Exception ex) { throw new CommunicationException(string.Format(CommunicationExceptionMessage.TypeConvertError, dataType, address, value, ex.Message), CommunicationErrorType.TypeError, innerException: ex); } obj = GetResult(Write(address, writeVal), address, writeVal); } break; case SiemensDBDataType.DataType_Char: break; default: throw new CommunicationException(string.Format(CommunicationExceptionMessage.DataTypeErrorException, dataType, address), CommunicationErrorType.TypeError); } return obj; } #endregion #region Destruction Function /// /// 析构函数,确保在不再需要时关闭连接 /// ~ModbusTcp() { Dispose(); } #endregion } }