using Common.Attributes; using NModbus; using NModbus.Device; using NModbus.Serial; //using DeviceCommand.Interface; using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.IO.Ports; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using System.Windows; using static Common.Attributes.ATSCommandAttribute; namespace DeviceCommand.Base { [ATSCommand] [DeviceCategory("全部驱动")] // 添加分类属性 public class ModbusRtu_Tcp { private TcpClient _tcpClient; private NetworkStream _networkStream; private readonly object _lock = new(); private bool _disposed = false; /// /// 获取目标设备的 IP 地址。 /// public string RemoteIpAddress { get; private set; } /// /// 获取目标设备的 TCP 端口号,默认为 502。 /// public int RemotePort { get; private set; } = 502; /// /// 接收和发送的超时时间(毫秒)。 /// public int ReceiveTimeout { get; set; } = 3000; /// /// 发送超时时间 /// public int SendTimeout { get; set; } = 3000; public TcpClient TcpClient { get => _tcpClient; set => _tcpClient = value; } /// /// 获取内部的 NetworkStream 对象。 /// public NetworkStream NetworkStream => _networkStream; /// /// 初始化一个新的 Modbus RTU over TCP 实例。 /// /// 目标设备的 IPv4 地址。 /// 目标 TCP 端口,默认为 502。 /// 发送超时时间。 /// 接收超时时间。 public ModbusRtu_Tcp CreateDevice(string ipAddress, int port = 502, int sendTimeout = 3000, int receiveTimeout = 3000) { RemoteIpAddress = ipAddress; RemotePort = port; return this; } /// /// 连接到 Modbus RTU over TCP 设备。 /// /// /// 异步连接到 Modbus RTU over TCP 设备。 /// /// Modbus RTU over TCP 设备对象实例。 /// 用于取消操作的取消令牌。 /// 连接结果,成功为 true,失败为 false。 public static async Task ConnectAsync(ModbusRtu_Tcp modbusTcp, CancellationToken ct = default) { if (modbusTcp._disposed) throw new ObjectDisposedException(nameof(ModbusRtu_Tcp)); lock (modbusTcp._lock) // 确保线程安全 { if (modbusTcp._tcpClient == null) { modbusTcp._tcpClient = new TcpClient(); } } // 检查连接状态和端点匹配 if (!modbusTcp._tcpClient.Connected) { // 未连接,直接创建新连接 lock (modbusTcp._lock) { modbusTcp._tcpClient.Close(); // 关闭可能存在的旧连接 modbusTcp._tcpClient.Dispose(); modbusTcp._tcpClient = new TcpClient(); modbusTcp._tcpClient.ReceiveTimeout = modbusTcp.ReceiveTimeout; modbusTcp._tcpClient.SendTimeout = modbusTcp.SendTimeout; } await modbusTcp._tcpClient.ConnectAsync(modbusTcp.RemoteIpAddress, modbusTcp.RemotePort, ct); } else { // 已连接,检查端点是否匹配 var remoteEndPoint = (IPEndPoint)modbusTcp._tcpClient.Client.RemoteEndPoint!; var ip = remoteEndPoint.Address.MapToIPv4().ToString(); bool isSameAddress = ip.Equals(modbusTcp.RemoteIpAddress); bool isSamePort = remoteEndPoint.Port == modbusTcp.RemotePort; if (!isSameAddress || !isSamePort) { // 端点不匹配,断开旧连接并创建新连接 lock (modbusTcp._lock) { modbusTcp._tcpClient.Close(); modbusTcp._tcpClient.Dispose(); modbusTcp._tcpClient = new TcpClient(); modbusTcp._tcpClient.ReceiveTimeout = modbusTcp.ReceiveTimeout; modbusTcp._tcpClient.SendTimeout = modbusTcp.SendTimeout; } await modbusTcp._tcpClient.ConnectAsync(modbusTcp.RemoteIpAddress, modbusTcp.RemotePort, ct); } // 如果端点匹配,则无需操作 } // 连接成功后,初始化 NetworkStream lock (modbusTcp._lock) { if (modbusTcp._tcpClient.Connected) { modbusTcp._networkStream = modbusTcp._tcpClient.GetStream(); return true; } } return false; // 理论上不应到达此处,除非连接状态判断有误 } /// /// 修改 Modbus RTU over TCP 设备的连接配置。 /// /// /// Modbus RTU over TCP 设备对象实例。 /// 目标设备的 IPv4 地址。 /// 目标 TCP 端口,默认为 502。 /// 通信超时时间(毫秒)。 public static void ChangeDeviceConfig(ModbusRtu_Tcp modbusTcp, string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000) { modbusTcp.RemoteIpAddress = ipAddress; modbusTcp.RemotePort = port; modbusTcp.ReceiveTimeout = receiveTimeout; modbusTcp.SendTimeout = sendTimeout; } /// /// 断开与 Modbus RTU over TCP 设备的连接。 /// public void Disconnect() { lock (_lock) { _networkStream?.Close(); _networkStream = null; _tcpClient?.Close(); _tcpClient = null; } } /// /// 执行设备初始化操作(当前为占位实现,TCP 无状态故无需特殊初始化)。 /// public void InitializeDevice() { // TCP 连接建立后通常无需额外初始化,保留接口一致性 } /// /// 触发紧急停止:立即断开 TCP 连接。 /// public void EmergencyStop() { Disconnect(); } /// /// 异步发送 Modbus RTU 请求并通过 TCP 接收响应,支持重试机制。 /// /// 待发送的完整 Modbus RTU 请求帧(含 CRC)。 /// 用于取消操作的取消令牌。 /// 接收到的响应帧字节数组。 private async Task SendRequestAndReceiveFrameAsync(byte[] requestFrame, CancellationToken ct) { if (_disposed) throw new ObjectDisposedException(nameof(ModbusRtu_Tcp)); if (_networkStream == null || !_tcpClient.Connected) throw new InvalidOperationException("TCP 连接未建立。"); for (int attempt = 0; attempt <= 3; attempt++) { try { await _networkStream.WriteAsync(requestFrame, 0, requestFrame.Length, ct); // 读取响应长度(最小响应帧长度为 5: 从站地址 + 功能码 + 字节计数 + CRC(2)) // 先读取至少 5 个字节 byte[] initialBuffer = new byte[5]; int totalRead = 0; while (totalRead < 5) { int n = await _networkStream.ReadAsync(initialBuffer, totalRead, 5 - totalRead, ct); if (n == 0) throw new IOException("连接中断。"); totalRead += n; } // 解析功能码和字节计数 byte func = initialBuffer[1]; int pduDataLength = 0; int expectedTotalLength = 0; if ((func & 0x80) != 0) // 异常响应 { expectedTotalLength = 5; // 异常响应固定长度 } else { if (func == 0x01 || func == 0x02) // 读线圈/读离散输入 { pduDataLength = initialBuffer[2]; } else if (func == 0x03 || func == 0x04) // 读保持/输入寄存器 { pduDataLength = initialBuffer[2]; } // 其他功能码可以继续扩展 expectedTotalLength = 2 + 1 + pduDataLength + 2; // 从站地址 + 功能码 + 字节计数(或数据) + CRC(2) } byte[] response = new byte[expectedTotalLength]; Array.Copy(initialBuffer, response, 5); if (expectedTotalLength > 5) { totalRead = 5; while (totalRead < expectedTotalLength) { int n = await _networkStream.ReadAsync(response, totalRead, expectedTotalLength - totalRead, ct); if (n == 0) throw new IOException("连接中断。"); totalRead += n; } } return response; } catch (Exception ex) when (ex is SocketException || ex is TimeoutException || ex is IOException) { if (attempt == 3) throw new TimeoutException($"Modbus RTU over TCP 通信超时,已重试 3 次。"); await Task.Delay(100, ct); } } throw new InvalidOperationException("通信失败。"); } /// /// 计算 Modbus RTU 帧的 CRC16 校验值(小端格式,多项式 0xA001)。 /// /// 待计算 CRC 的字节数组。 /// 起始偏移量。 /// 数据长度。 /// 16 位 CRC 校验值。 private static ushort CalculateCRC16(byte[] data, int offset, int length) { ushort crc = 0xFFFF; for (int i = 0; i < length; i++) { crc ^= data[offset + i]; for (int j = 0; j < 8; j++) { if ((crc & 0x0001) != 0) { crc >>= 1; crc ^= 0xA001; } else crc >>= 1; } } return crc; } /// /// 构建完整的 Modbus RTU 帧(含 CRC16 校验)。 /// /// 从站地址(1-247)。 /// 功能码(如 0x01、0x03 等)。 /// PDU 数据部分(不含地址和功能码)。 /// 完整的 RTU 帧字节数组。 private static byte[] BuildRtuFrame(byte slaveAddress, byte functionCode, byte[] data) { byte[] pdu = new byte[2 + data.Length]; pdu[0] = slaveAddress; pdu[1] = functionCode; Array.Copy(data, 0, pdu, 2, data.Length); ushort crc = CalculateCRC16(pdu, 0, pdu.Length); byte[] frame = new byte[pdu.Length + 2]; Array.Copy(pdu, frame, pdu.Length); frame[pdu.Length] = (byte)(crc & 0xFF); frame[pdu.Length + 1] = (byte)(crc >> 8); return frame; } /// /// 验证接收到的 Modbus RTU 响应帧是否有效。 /// /// 接收到的完整响应帧。 /// 期望的从站地址。 /// 期望的功能码。 /// 若帧有效则返回 true;否则抛出异常。 /// 当收到异常响应或 CRC 校验失败时抛出。 private static bool ValidateResponse(byte[] response, byte expectedSlave, byte expectedFunction) { if (response.Length < 4) throw new InvalidDataException("响应帧长度不足。"); if (response[0] != expectedSlave) throw new InvalidDataException("从站地址不匹配。"); if (response[1] != expectedFunction && response[1] != (byte)(expectedFunction | 0x80)) throw new InvalidDataException("功能码不匹配。"); if ((response[1] & 0x80) != 0) throw new Exception($"Modbus 异常响应:功能码 {response[1] & 0x7F},错误码 {response[2]}"); ushort receivedCrc = (ushort)(response[response.Length - 2] | (response[response.Length - 1] << 8)); ushort calculatedCrc = CalculateCRC16(response, 0, response.Length - 2); if (receivedCrc != calculatedCrc) throw new InvalidDataException("响应帧 CRC 校验失败。"); return true; } // ———————————————————————— // 公共 Modbus 功能方法 (编号 68-70) // ———————————————————————— /// /// 异步读取从站的线圈状态(功能码 01)。 /// /// 从站地址。 /// 起始线圈地址(0-based)。 /// 要读取的线圈数量。 /// 取消令牌。 /// 布尔数组,表示每个线圈的 ON/OFF 状态。 public async Task ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default) { byte[] data = { (byte)(startAddress >> 8), (byte)startAddress, (byte)(numberOfPoints >> 8), (byte)numberOfPoints }; byte[] request = BuildRtuFrame(slaveAddress, 0x01, data); byte[] response = await SendRequestAndReceiveFrameAsync(request, ct); ValidateResponse(response, slaveAddress, 0x01); int byteCount = response[2]; bool[] result = new bool[numberOfPoints]; for (int i = 0; i < numberOfPoints; i++) { int byteIndex = i / 8; int bitIndex = i % 8; result[i] = (response[3 + byteIndex] & (1 << bitIndex)) != 0; } return result; } /// /// 异步写入单个线圈状态(功能码 05)。 /// /// 从站地址。 /// 线圈地址(0-based)。 /// 目标值(true = ON, false = OFF)。 /// 取消令牌。 /// 任务完成表示写入成功。 public async Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default) { ushort coilValue = value ? (ushort)0xFF00 : (ushort)0x0000; byte[] data = { (byte)(coilAddress >> 8), (byte)coilAddress, (byte)(coilValue >> 8), (byte)coilValue }; byte[] request = BuildRtuFrame(slaveAddress, 0x05, data); byte[] response = await SendRequestAndReceiveFrameAsync(request, ct); ValidateResponse(response, slaveAddress, 0x05); } /// /// 异步读取从站的离散输入状态(功能码 02)。 /// /// 从站地址。 /// 起始输入地址(0-based)。 /// 要读取的输入点数量。 /// 取消令牌。 /// 布尔数组,表示每个输入的状态。 public async Task ReadDiscreteInputsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default) { byte[] data = { (byte)(startAddress >> 8), (byte)startAddress, (byte)(numberOfPoints >> 8), (byte)numberOfPoints }; byte[] request = BuildRtuFrame(slaveAddress, 0x02, data); byte[] response = await SendRequestAndReceiveFrameAsync(request, ct); ValidateResponse(response, slaveAddress, 0x02); int byteCount = response[2]; bool[] result = new bool[numberOfPoints]; for (int i = 0; i < numberOfPoints; i++) { int byteIndex = i / 8; int bitIndex = i % 8; result[i] = (response[3 + byteIndex] & (1 << bitIndex)) != 0; } return result; } /// /// 异步读取从站的保持寄存器值(功能码 03)。 /// /// 从站地址。 /// 起始寄存器地址(0-based)。 /// 要读取的寄存器数量。 /// 取消令牌。 /// 16 位无符号整数数组,表示寄存器值。 public async Task ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfRegisters, CancellationToken ct = default) { byte[] data = { (byte)(startAddress >> 8), (byte)startAddress, (byte)(numberOfRegisters >> 8), (byte)numberOfRegisters }; byte[] request = BuildRtuFrame(slaveAddress, 0x03, data); byte[] response = await SendRequestAndReceiveFrameAsync(request, ct); ValidateResponse(response, slaveAddress, 0x03); ushort[] result = new ushort[numberOfRegisters]; for (int i = 0; i < numberOfRegisters; i++) { result[i] = (ushort)((response[3 + i * 2] << 8) | response[4 + i * 2]); } return result; } /// /// 异步写入单个保持寄存器(功能码 06)。 /// /// 从站地址。 /// 寄存器地址(0-based)。 /// 要写入的值。 /// 取消令牌。 /// 任务完成表示写入成功。 public async Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default) { byte[] data = { (byte)(registerAddress >> 8), (byte)registerAddress, (byte)(value >> 8), (byte)value }; byte[] request = BuildRtuFrame(slaveAddress, 0x06, data); byte[] response = await SendRequestAndReceiveFrameAsync(request, ct); ValidateResponse(response, slaveAddress, 0x06); } /// /// 异步读取从站的输入寄存器值(功能码 04)。 /// /// 从站地址。 /// 起始寄存器地址(0-based)。 /// 要读取的寄存器数量。 /// 取消令牌。 /// 16 位无符号整数数组,表示输入寄存器值。 public async Task ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfRegisters, CancellationToken ct = default) { byte[] data = { (byte)(startAddress >> 8), (byte)startAddress, (byte)(numberOfRegisters >> 8), (byte)numberOfRegisters }; byte[] request = BuildRtuFrame(slaveAddress, 0x04, data); byte[] response = await SendRequestAndReceiveFrameAsync(request, ct); ValidateResponse(response, slaveAddress, 0x04); ushort[] result = new ushort[numberOfRegisters]; for (int i = 0; i < numberOfRegisters; i++) { result[i] = (ushort)((response[3 + i * 2] << 8) | response[4 + i * 2]); } return result; } /// /// 释放 TCP 客户端资源并标记为已处置。 /// public void Dispose() { if (!_disposed) { Disconnect(); _disposed = true; } } } }