#region << 版 本 注 释 >> /*---------------------------------------------------------------- * 命名空间:WIDESEAWCS_Communicator * 创建者:胡童庆 * 创建时间:2024/8/2 16:13:36 * 版本:V1.0.0 * 描述:西门子S7通讯类的封装,继承BaseCommunicator抽象类 * * ---------------------------------------------------------------- * 修改人: * 修改时间: * 版本:V1.0.1 * 修改说明: * *----------------------------------------------------------------*/ #endregion << 版 本 注 释 >> using HslCommunication; using HslCommunication.Core; using HslCommunication.LogNet; using HslCommunication.Profinet.Siemens; using Newtonsoft.Json; using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Net; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Xml.Linq; namespace WIDESEAWCS_Communicator { /// /// 西门子S7通讯类 /// [Description("西门子S7")] public class SiemensS7 : BaseCommunicator, IDisposable { #region Private Member /// /// HSLCommunication的西门子的S7协议的通讯类 /// private SiemensS7Net plc; /// /// 设备的IP地址。 /// private string _ipAddress; /// /// 连接使用的端口号。 /// private int _port; /// /// 当前通讯器是否已连接到PLC。 /// private bool _connected; private string _name; private ILogNet _logNet; #endregion Private Member #region Public Member /// /// 获取当前通讯器是否已连接到PLC。 /// public override bool IsConnected => _connected; public override string Name => _name; public override ILogNet LogNet => _logNet; #endregion Public Member #region Constructor Function /// /// 构造函数 /// /// 设备的IP地址 /// 连接使用的端口号 public SiemensS7(string ipAddress, int port, string name) { string path = AppDomain.CurrentDomain.BaseDirectory + $"Log_PLCReadWrite\\{name}"; _logNet = new LogNetFileSize(path, 3 * 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; } catch (Exception ex) { LogNet.WriteException(Name, $"【{Name}】PLC读取异常,地址:【{address}】,错误信息:【{ex.Message}】", ex); throw new CommunicationException(ex.Message, CommunicationErrorType.ReadException, innerException: ex); } } /// /// /// /// /// /// /// /// /// private bool GetResult(OperateResult operateResult, string address, T value) where T : notnull { try { if (!operateResult.IsSuccess) { throw new CommunicationException(string.Format(CommunicationExceptionMessage.WriteFailedException, typeof(T).Name, address, value, operateResult.Message), CommunicationErrorType.WriteFailed); } else { object? obj = null; for (int i = 0; i < 5; i++) { T readValue = Read(address); obj = readValue; if (readValue.Equals(value)) { return true; } else { Write(address, value); } } throw new CommunicationException(string.Format(CommunicationExceptionMessage.ReadWriteDifferentException, typeof(T).Name, address, value, obj), CommunicationErrorType.WriteFailed); } } catch(Exception ex) { LogNet.WriteException(Name, $"【{Name}】PLC写入异常,地址:【{address}】,写入的数据:【{value}】,错误信息:【{ex.Message}】", ex); throw new CommunicationException(ex.Message, CommunicationErrorType.WriteFailed, innerException: ex); } } /// /// 写入数据 /// /// /// /// /// 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: throw new CommunicationException(string.Format(CommunicationExceptionMessage.DataTypeErrorException, type.Name, address), CommunicationErrorType.TypeError); } } catch (CommunicationException ex) { throw new CommunicationException($"写入错误:" + ex.Message, ex.ErrorType, ex.ErrorCode, ex.InnerException); } catch (Exception ex) { //读取异常时抛出自定义通讯异常类 throw new CommunicationException($"写入数据异常,错误信息:{ex.Message}", CommunicationErrorType.TypeError, innerException: ex); } } private object Read(string address, TypeCode typeCode) { try { switch (typeCode) { case TypeCode.Int32: return (int)GetContent(plc.ReadInt32(address), address); case TypeCode.UInt32: return (uint)GetContent(plc.ReadUInt32(address), address); case TypeCode.Int16: return (short)GetContent(plc.ReadInt16(address), address); case TypeCode.UInt16: return (ushort)GetContent(plc.ReadUInt16(address), address); case TypeCode.Single: return (float)GetContent(plc.ReadFloat(address), address); case TypeCode.Boolean: return (bool)GetContent(plc.ReadBool(address), address); case TypeCode.Byte: return (byte)GetContent(plc.ReadByte(address), address); case TypeCode.String: return (string)GetContent(plc.ReadString(address), address); case TypeCode.Char: return (char)GetContent(plc.ReadByte(address), address); default: throw new CommunicationException($"读取数据失败,未定义数据类型:【{typeCode}】,地址:【{address}】", CommunicationErrorType.TypeError); } } catch (CommunicationException ex) { //读取异常时抛出自定义通讯异常类 throw ex; } catch (Exception ex) { //读取异常时抛出自定义通讯异常类 throw new CommunicationException($"读取数据异常,错误信息:{ex.Message}", CommunicationErrorType.ReadException, innerException: ex); } } #endregion #region Public Method /// /// 连接到PLC。 /// /// 如果连接成功则返回true,否则返回false。 /// 自定义通讯异常类 public override bool Connect() { try { //实例化一个西门子的S7协议的通讯对象 plc = new SiemensS7Net(SiemensPLCS.S1500) { IpAddress = _ipAddress, Port = _port }; OperateResult operateResult = plc.ConnectServer();//连接PLC _connected = operateResult.IsSuccess;//将连接是否成功赋值给当前通讯器是否已连接到PLC if (_connected) LogNet.WriteInfo(Name, $"【{Name}】PLC连接成功,IP【{_ipAddress}】,Port【{_port}】"); else LogNet.WriteError(Name, $"【{Name}】PLC连接失败,IP【{_ipAddress}】,Port【{_port}】,错误信息:{operateResult.Message}"); return operateResult.IsSuccess; } catch (Exception ex) { LogNet.WriteException(Name, $"【{Name}】PLC连接异常,IP【{_ipAddress}】,Port【{_port}】", 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; } } #region Read /// /// 从PLC读取数据。 /// /// 源地址,具体格式取决于使用的工业协议。 /// 要读取的数据长度。 /// 读取到的数据,如果读取失败则可能返回null、空数组或抛出自定义通讯异常。 /// 自定义通讯异常类 public override byte[] Read(string address, int length) { try { return (byte[])GetContent(plc.Read(address, (ushort)length), address); } catch (CommunicationException ex) { //读取异常时抛出自定义通讯异常类 throw ex; } catch (Exception ex) { //读取异常时抛出自定义通讯异常类 throw new CommunicationException($"读取数据异常:{ex.Message}", CommunicationErrorType.ReadFailed, innerException: ex); } } /// /// 从PLC读取数据。 /// /// 读取的数据类型。 /// 源地址,具体格式取决于使用的工业协议。 /// 要读取的数据长度(可选,默认值为1)。 /// 如果读取成功,返回读取的结果,失败则抛出自定义通讯异常 /// 自定义通讯异常类 public override T Read(string address) { try { Type type = typeof(T); return (T)Read(address, Type.GetTypeCode(type)); } catch (CommunicationException ex) { //读取异常时抛出自定义通讯异常类 throw ex; } catch (Exception ex) { //读取异常时抛出自定义通讯异常类 throw new CommunicationException($"读取数据异常,错误信息:{ex.Message}", CommunicationErrorType.ReadException, innerException: ex); } } /// /// 从PLC读取数据返回object。 /// /// 源地址,具体格式取决于使用的工业协议。 /// 读取的数据类型。 /// 如果读取成功,返回读取的结果,失败则抛出自定义通讯异常 /// 自定义通讯异常类 public override object ReadAsObj(string address, string dataType) { try { return Read(address, SiemensDBDataType.GetTypeCode(dataType)); } catch (CommunicationException ex) { //读取异常时抛出自定义通讯异常类 throw ex; } catch (Exception ex) { //读取异常时抛出自定义通讯异常类 throw new CommunicationException($"读取数据异常,数据类型:【{dataType}】,地址:【{address}】,错误信息:{ex.Message}", CommunicationErrorType.ReadException, innerException: ex); } } #endregion #region Write /// /// 向PLC写入数据。 /// /// 源地址,具体格式取决于使用的工业协议。 /// 要写入的数据。 /// 如果写入成功则返回true,如果写入失败则可能返回false或抛出自定义通讯异常。 /// 自定义通讯异常类 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); } } /// /// /// /// /// /// /// /// public override bool Write(string address, T value) { try { return GetResult(Write(address, value), address, value); } catch (CommunicationException ex) { //读取异常时抛出自定义通讯异常类 throw ex; } catch (Exception ex) { //读取异常时抛出自定义通讯异常类 throw new CommunicationException($"读取数据异常,地址:【{address}】,错误信息:{ex.Message}", CommunicationErrorType.TypeError, innerException: ex); } } /// /// 向PLC写入数据。 /// /// 源地址,具体格式取决于使用的工业协议。 /// 要写入的数据类型(PLC的数据类型)。 /// 要写入的数据。 /// 如果写入成功则返回true,失败则抛出自定义通讯异常。 /// public override bool WriteObj(string address, string dataType, object value) { try { bool obj = false; switch (dataType.ToLower()) { case SiemensDBDataType.DataType_DInt: { int writeVal; try { writeVal = Convert.ToInt32(value); } catch (Exception ex) { throw new CommunicationException($"写入数据异常,数据类型:【{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($"写入数据异常,数据类型:【{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($"写入数据异常,数据类型:【{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($"写入数据异常,数据类型:【{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($"写入数据异常,数据类型:【{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($"写入数据异常,数据类型:【{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($"写入数据异常,数据类型:【{dataType}】,地址:【{address}】,数据:【{value}】,数据类型转换错误,错误信息:{ex.Message}", CommunicationErrorType.TypeError, innerException: ex); } obj = GetResult(Write(address, writeVal), address, writeVal); } break; case SiemensDBDataType.DataType_ByteArray: { byte[] writeVal; try { writeVal = (byte[])value; } catch (Exception ex) { throw new CommunicationException($"写入数据异常,数据类型:【{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($"写入数据异常,数据类型:【{dataType}】,地址:【{address}】,数据:【{value}】,数据类型转换错误,错误信息:{ex.Message}", CommunicationErrorType.TypeError, innerException: ex); } obj = GetResult(Write(address, writeVal), address, writeVal); } break; case SiemensDBDataType.DataType_Char: //obj = GetResult(plc.Write(address, (int)value), address, (int)value); break; case SiemensDBDataType.DataType_CharArray: break; default: throw new CommunicationException($"写入数据失败,未定义数据类型:【{dataType}】,地址:【{address}】,数据:【{value}】", CommunicationErrorType.TypeError); } return obj; } catch (CommunicationException ex) { //写入异常时抛出自定义通讯异常类 throw ex; } catch (Exception ex) { //写入异常时抛出自定义通讯异常类 throw new CommunicationException($"写入数据异常,数据类型:【{dataType}】,地址:【{address}】,数据:【{value}】,错误信息:{ex.Message}", CommunicationErrorType.TypeError, innerException: ex); } } #endregion #region ReadCustomer public override T ReadCustomer(string address) { 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); } } #endregion #region WriteCustomer public override bool WriteCustomer(string address, T value) { try { OperateResult operateResult = plc.WriteCustomer(address, value); if (operateResult.IsSuccess) { for (int i = 0; i < 5; i++) { T readValue = ReadCustomer(address); PropertyInfo[] propertyInfos = typeof(T).GetProperties(); foreach (var item in propertyInfos) { object writeValueItem = item.GetValue(value); if (writeValueItem != null) { object readValueItem = item.GetValue(readValue); if (writeValueItem.Equals(readValueItem)) { break; } else { plc.WriteCustomer(address, value); } throw new CommunicationException(string.Format(CommunicationExceptionMessage.ReadWriteDifferentException, typeof(T).Name, address, JsonConvert.SerializeObject(value), JsonConvert.SerializeObject(readValue)), CommunicationErrorType.WriteFailed); } } } } else { throw new CommunicationException(string.Format(CommunicationExceptionMessage.WriteFailedException, typeof(T).Name, address, JsonConvert.SerializeObject(value), operateResult.Message), CommunicationErrorType.WriteFailed); } return operateResult.IsSuccess; } catch(Exception ex) { LogNet.WriteException(Name, $"【{Name}】PLC写入异常,地址:【{address}】,写入的数据:【{JsonConvert.SerializeObject(value)}】,错误信息:【{ex.Message}】", ex); throw new CommunicationException(ex.Message, CommunicationErrorType.WriteFailed, innerException: ex); } } #endregion // 显式实现IDisposable接口以提供垃圾回收时的清理 public void Dispose() { Disconnect(); GC.SuppressFinalize(this); } 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.ReadByte(address); if (!read.IsSuccess) return OperateResult.CreateFailedResult(read); if (read.Content == 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(); } } #endregion #region Destruction Function /// /// 析构函数,确保在不再需要时关闭连接 /// ~SiemensS7() { Dispose(); } #endregion } }