using HslCommunication.Profinet.Siemens; using HslCommunication; using System; using System.Threading.Tasks; using XP.Common.Logging.Interfaces; using XP.Hardware.Plc.Abstractions; using XP.Hardware.Plc.Exceptions; using XP.Hardware.PLC.Configs; using XP.Hardware.PLC.Helpers; using PlcType = XP.Hardware.PLC.Configs.PlcType; namespace XP.Hardware.Plc.Core { /// /// 西门子 S7 PLC 客户端实现 | Siemens S7 PLC client implementation /// public class S7PlcClient : IPlcClient { private SiemensS7Net _siemensTcpNet; private PlcConfig _config; private readonly ILoggerService _logger; private bool _isConnected = false; /// /// 获取 PLC 连接状态 | Get PLC connection status /// public bool IsConnected => _siemensTcpNet != null && _isConnected; /// /// 构造函数 | Constructor /// /// 日志服务 | Logger service public S7PlcClient(ILoggerService logger) { _logger = logger.ForModule("S7PlcClient"); } /// /// 连接到 PLC | Connect to PLC /// /// PLC 配置 | PLC configuration /// 连接是否成功 | Whether connection succeeded public async Task ConnectAsync(PlcConfig config) { _config = config; // 释放旧连接 | Release old connection Disconnect(); try { _logger.Info("正在连接 PLC: {IpAddress}:{Port}, Rack={Rack}, Slot={Slot}, 型号={PlcType}", config.IpAddress, config.Port, config.Rack, config.Slot, config.PlcType); // 根据西门子 PLC 型号创建连接对象 | Create connection object based on Siemens PLC model SiemensPLCS plcType = ConvertPlcType(config.PlcType); _siemensTcpNet = new SiemensS7Net(plcType); _siemensTcpNet.IpAddress = _config.IpAddress; _siemensTcpNet.Port = _config.Port; // 配置超时参数 | Configure timeout parameters _siemensTcpNet.ConnectTimeOut = _config.ConnectTimeoutMs; _siemensTcpNet.ReceiveTimeOut = _config.ReadTimeoutMs; var connectResult = await Task.Run(() => _siemensTcpNet.ConnectServer()); if (connectResult.IsSuccess) { _isConnected = true; _logger.Info("PLC 连接成功: {IpAddress}", config.IpAddress); return true; } else { _logger.Error(null, "PLC 连接失败: {IpAddress}, 错误: {Message}", config.IpAddress, connectResult.Message); throw new PlcException($"连接失败: {connectResult.Message}"); } } catch (PlcException) { // 重新抛出 PlcException | Re-throw PlcException throw; } catch (Exception ex) { // 包装异常 | Wrap exception _logger.Error(ex, "连接 PLC 时发生错误: {IpAddress}, 错误: {Message}", config.IpAddress, ex.Message); throw new PlcException($"连接PLC时发生错误: {ex.Message}", ex); } } /// /// 断开 PLC 连接 | Disconnect from PLC /// public void Disconnect() { if (_siemensTcpNet != null) { _logger.Info("正在断开 PLC 连接"); _siemensTcpNet.ConnectClose(); _siemensTcpNet = null; _isConnected = false; _logger.Info("PLC 连接已断开"); } } /// /// 转换 PLC 类型枚举到 HslCommunication 的 PLC 类型 | Convert PLC type enum to HslCommunication PLC type /// /// PLC 类型 | PLC type /// HslCommunication PLC 类型 | HslCommunication PLC type private SiemensPLCS ConvertPlcType(PlcType plcType) { return plcType switch { PlcType.S200Smart => SiemensPLCS.S200Smart, PlcType.S300 => SiemensPLCS.S300, PlcType.S400 => SiemensPLCS.S400, PlcType.S1200 => SiemensPLCS.S1200, PlcType.S1500 => SiemensPLCS.S1500, _ => throw new PlcException($"不支持的 PLC 类型: {plcType}") }; } /// /// 读取 PLC 数据 | Read PLC data /// /// 数据类型 | Data type /// PLC 地址 | PLC address /// 读取的数据 | Read data public async Task ReadAsync(string address) { if (!IsConnected) { _logger.Error(null, "读取失败: PLC 未连接, 地址={Address}", address); throw new PlcException("PLC未连接"); } try { _logger.Debug("读取 PLC 数据: 地址={Address}, 类型={Type}", address, typeof(T).Name); return await Task.Run(() => { OperateResult result; object content = null; // 根据类型调用不同的读取方法 | Call different read methods based on type if (typeof(T) == typeof(bool)) { var readResult = _siemensTcpNet.ReadBool(address); result = readResult; content = readResult.Content; } else if (typeof(T) == typeof(byte)) { var readResult = _siemensTcpNet.ReadByte(address); result = readResult; content = readResult.Content; } else if (typeof(T) == typeof(short)) { var readResult = _siemensTcpNet.ReadInt16(address); result = readResult; content = readResult.Content; } else if (typeof(T) == typeof(ushort)) { var readResult = _siemensTcpNet.ReadUInt16(address); result = readResult; content = readResult.Content; } else if (typeof(T) == typeof(int)) { var readResult = _siemensTcpNet.ReadInt32(address); result = readResult; content = readResult.Content; } else if (typeof(T) == typeof(uint)) { var readResult = _siemensTcpNet.ReadUInt32(address); result = readResult; content = readResult.Content; } else if (typeof(T) == typeof(float)) { var readResult = _siemensTcpNet.ReadFloat(address); result = readResult; content = readResult.Content; } else if (typeof(T) == typeof(double)) { var readResult = _siemensTcpNet.ReadDouble(address); result = readResult; content = readResult.Content; } else if (typeof(T) == typeof(string)) { // 字符串需要指定长度,这里使用默认长度 10 var readResult = _siemensTcpNet.ReadString(address, 10); result = readResult; content = readResult.Content; } else { throw new PlcException($"不支持的数据类型: {typeof(T).Name}"); } PlcHelper.CheckSuccess(result); _logger.Debug("读取成功: 地址={Address}, 值={Value}", address, content); return (T)content; }); } catch (PlcException) { _logger.Error(null, "读取失败: 地址={Address}", address); throw; } catch (Exception ex) { _logger.Error(ex, "读取 PLC 数据时发生错误: 地址={Address}, 错误={Message}", address, ex.Message); throw new PlcException($"读取失败: {ex.Message}", ex); } } /// /// 写入 PLC 数据 | Write PLC data /// /// 数据类型 | Data type /// PLC 地址 | PLC address /// 要写入的值 | Value to write /// 写入是否成功 | Whether write succeeded public async Task WriteAsync(string address, T value) { if (!IsConnected) { _logger.Error(null, "写入失败: PLC 未连接, 地址={Address}", address); throw new PlcException("PLC未连接"); } try { _logger.Debug("写入 PLC 数据: 地址={Address}, 值={Value}", address, value); OperateResult writeResult; // 根据类型调用不同的写入方法 | Call different write methods based on type if (typeof(T) == typeof(bool)) { writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToBoolean(value))); } else if (typeof(T) == typeof(byte)) { writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToByte(value))); } else if (typeof(T) == typeof(short)) { writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToInt16(value))); } else if (typeof(T) == typeof(ushort)) { writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToUInt16(value))); } else if (typeof(T) == typeof(int)) { writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToInt32(value))); } else if (typeof(T) == typeof(uint)) { writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToUInt32(value))); } else if (typeof(T) == typeof(float)) { writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToSingle(value))); } else if (typeof(T) == typeof(double)) { writeResult = await Task.Run(() => _siemensTcpNet.Write(address, Convert.ToDouble(value))); } else if (typeof(T) == typeof(string)) { writeResult = await Task.Run(() => _siemensTcpNet.Write(address, value.ToString())); } else { throw new PlcException($"不支持的数据类型: {typeof(T).Name}"); } if (writeResult.IsSuccess) { _logger.Debug("写入成功: 地址={Address}", address); return true; } else { _logger.Error(null, "写入失败: 地址={Address}, 错误={Message}", address, writeResult.Message); return false; } } catch (Exception ex) { _logger.Error(ex, "写入 PLC 数据时发生错误: 地址={Address}, 错误={Message}", address, ex.Message); throw new PlcException($"写入失败: {ex.Message}", ex); } } /// /// 批量读取字节数组 | Read byte array in bulk /// /// 数据块 | Data block /// 起始地址 | Start address /// 读取长度 | Read length /// 读取的字节数组 | Read byte array /// /// 按指定长度读取字符串 | Read string with specified length /// public async Task ReadStringAsync(string address, ushort length) { if (!IsConnected) { _logger.Error(null, "读取字符串失败: PLC 未连接, 地址={Address}", address); throw new PlcException("PLC未连接"); } try { _logger.Debug("读取 PLC 字符串: 地址={Address}, 长度={Length}", address, length); return await Task.Run(() => { var readResult = _siemensTcpNet.ReadString(address, length); PlcHelper.CheckSuccess(readResult); _logger.Debug("读取字符串成功: 地址={Address}, 值={Value}", address, readResult.Content); return readResult.Content; }); } catch (PlcException) { throw; } catch (Exception ex) { _logger.Error(ex, "读取字符串时发生错误: 地址={Address}, 错误={Message}", address, ex.Message); throw new PlcException($"读取字符串失败: {ex.Message}", ex); } } /// /// 按指定长度写入字符串 | Write string with specified length /// public async Task WriteStringAsync(string address, string value, ushort length) { if (!IsConnected) { _logger.Error(null, "写入字符串失败: PLC 未连接, 地址={Address}", address); throw new PlcException("PLC未连接"); } try { _logger.Debug("写入 PLC 字符串: 地址={Address}, 值={Value}, 长度={Length}", address, value, length); // 截断或填充字符串到指定长度 | Truncate or pad string to specified length var bytes = System.Text.Encoding.ASCII.GetBytes(value ?? string.Empty); var buffer = new byte[length]; Array.Copy(bytes, 0, buffer, 0, Math.Min(bytes.Length, length)); var addr = address; return await Task.Run(() => { var result = _siemensTcpNet.Write(addr, buffer); if (result.IsSuccess) { _logger.Debug("写入字符串成功: 地址={Address}", addr); return true; } else { _logger.Error(null, "写入字符串失败: 地址={Address}, 错误={Message}", addr, result.Message); return false; } }); } catch (Exception ex) { _logger.Error(ex, "写入字符串时发生错误: 地址={Address}, 错误={Message}", address, ex.Message); throw new PlcException($"写入字符串失败: {ex.Message}", ex); } } public async Task ReadBytesAsync(string dbBlock, int startAddress, int length) { if (!IsConnected) { _logger.Error(null, "批量读取失败: PLC 未连接, 数据块={DbBlock}, 地址={Address}, 长度={Length}", dbBlock, startAddress, length); throw new PlcException("PLC未连接"); } try { //_logger.Debug("批量读取 PLC 数据: 数据块={DbBlock}, 起始地址={Address}, 长度={Length}", dbBlock, startAddress, length); var address = $"{dbBlock}.{startAddress}"; var result = await Task.Run(() => _siemensTcpNet.Read(address, (ushort)length)); PlcHelper.CheckSuccess(result); //_logger.Debug("批量读取成功: 数据块={DbBlock}, 读取字节数={Length}", dbBlock, result.Content.Length); return result.Content; } catch (PlcException) { _logger.Error(null, "批量读取失败: 数据块={DbBlock}, 地址={Address}", dbBlock, startAddress); throw; } catch (Exception ex) { _logger.Error(ex, "批量读取 PLC 数据时发生错误: 数据块={DbBlock}, 地址={Address}, 错误={Message}", dbBlock, startAddress, ex.Message); throw new PlcException($"批量读取失败: {ex.Message}", ex); } } /// /// 批量写入字节数组 | Write byte array in bulk /// /// 数据块 | Data block /// 起始地址 | Start address /// 要写入的数据 | Data to write /// 写入是否成功 | Whether write succeeded public async Task WriteBytesAsync(string dbBlock, int startAddress, byte[] data) { if (!IsConnected) { _logger.Error(null, "批量写入失败: PLC 未连接, 数据块={DbBlock}, 地址={Address}, 长度={Length}", dbBlock, startAddress, data?.Length ?? 0); throw new PlcException("PLC未连接"); } try { _logger.Debug("批量写入 PLC 数据: 数据块={DbBlock}, 起始地址={Address}, 长度={Length}", dbBlock, startAddress, data.Length); var address = $"{dbBlock}.{startAddress}"; var result = await Task.Run(() => _siemensTcpNet.Write(address, data)); if (result.IsSuccess) { _logger.Debug("批量写入成功: 数据块={DbBlock}, 写入字节数={Length}", dbBlock, data.Length); return true; } else { _logger.Error(null, "批量写入失败: 数据块={DbBlock}, 地址={Address}, 错误={Message}", dbBlock, startAddress, result.Message); return false; } } catch (Exception ex) { _logger.Error(ex, "批量写入 PLC 数据时发生错误: 数据块={DbBlock}, 地址={Address}, 错误={Message}", dbBlock, startAddress, ex.Message); throw new PlcException($"批量写入失败: {ex.Message}", ex); } } #region IDisposable Support private bool disposedValue = false; /// /// 释放资源 | Dispose resources /// /// 是否释放托管资源 | Whether to dispose managed resources protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { _logger.Info("正在释放 S7PlcClient 资源"); Disconnect(); } disposedValue = true; } } /// /// 释放资源 | Dispose resources /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion } }