using Common.Attributes; using System; using System.IO; using System.IO.Ports; using System.Threading; using System.Threading.Tasks; using static Common.Attributes.ATSCommandAttribute; namespace DeviceCommand.Base { /// /// 提供标准串口 Modbus RTU 协议通信能力。 /// 该类直接管理 SerialPort,并处理 Modbus RTU 帧的构建、发送、接收与 CRC16 校验。 /// [ATSCommand] [DeviceCategory("全部驱动")] // 添加分类属性 public class ModbusRtu_Serial : IDisposable { private SerialPort _serialPort; private readonly object _lock = new(); private bool _disposed = false; protected internal SemaphoreSlim semaphoreSlimLock { get; set; } = new(1, 1); /// /// 获取或设置串口名称(如 "COM1")。 /// public string PortName { get; set; } /// /// 获取或设置波特率。 /// public int BaudRate { get; set; } /// /// 获取或设置数据位数,默认为 8。 /// public int DataBits { get; set; } = 8; /// /// 获取或设置停止位,默认为 One。 /// public StopBits StopBits { get; set; } = StopBits.One; /// /// 获取或设置奇偶校验位,默认为 None。 /// public Parity Parity { get; set; } = Parity.None; /// /// 获取或设置读取超时时间(毫秒),默认为 3000。 /// public int ReadTimeout { get; set; } = 3000; /// /// 获取或设置写入超时时间(毫秒),默认为 3000。 /// public int WriteTimeout { get; set; } = 3000; /// /// 获取或设置通信失败时的最大重试次数,默认为 3 次。 /// public int MaxRetries { get; set; } = 3; public SerialPort _SerialPort { get; set; } = new SerialPort(); /// /// 初始化一个新的 Modbus RTU 串口实例。 /// /// 串口名称,如 "COM1"。 /// 波特率。 /// 数据位,默认 8。 /// 停止位,默认 One。 /// 奇偶校验,默认 None。 /// 读取超时(毫秒)。 /// 写入超时(毫秒)。 public ModbusRtu_Serial CreateDevice(string portName, int baudRate , int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None , int readTimeout = 3000, int writeTimeout = 3000) { PortName = portName; BaudRate = baudRate; DataBits = dataBits; StopBits = stopBits; Parity = parity; //ReadTimeout = readTimeout; //WriteTimeout = writeTimeout; return this; } /// /// 修改串口实例参数 /// /// ModbusRtu串口实例 /// 串口名称(如"COM1") /// 波特率 /// 数据位 /// 停止位 /// 校验位(根据设备需求设置) /// 读取超时 /// 写入超时 public static void ChangeDeviceConfig(ModbusRtu_Serial modbusRtu, string portName, int baudRate, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, int readTimeout = 3000, int writeTimeout = 3000) { // 更新配置实例参数 modbusRtu.PortName = portName; modbusRtu.BaudRate = baudRate; modbusRtu.DataBits = dataBits; modbusRtu.StopBits = stopBits; modbusRtu.Parity = parity; if (readTimeout > 0) modbusRtu.ReadTimeout = readTimeout; if (writeTimeout > 0) modbusRtu.WriteTimeout = writeTimeout; // 如果串口已打开,需要重新配置 SerialPort 对象 if (modbusRtu._serialPort != null && modbusRtu._serialPort.IsOpen) { modbusRtu._serialPort.Close(); modbusRtu._serialPort.PortName = modbusRtu.PortName; modbusRtu._serialPort.BaudRate = modbusRtu.BaudRate; modbusRtu._serialPort.Parity = modbusRtu.Parity; modbusRtu._serialPort.DataBits = modbusRtu.DataBits; modbusRtu._serialPort.StopBits = modbusRtu.StopBits; modbusRtu._serialPort.ReadTimeout = modbusRtu.ReadTimeout; modbusRtu._serialPort.WriteTimeout = modbusRtu.WriteTimeout; // 注意:DtrEnable, RtsEnable 等其他属性也可以在此更新,如果需要的话 } } /// /// 异步打开串口连接。 /// /// 支持中途取消操作 /// 连接结果 public static Task ConnectAsync(ModbusRtu_Serial modbusRtu_Serial, CancellationToken ct = default) { if (modbusRtu_Serial._SerialPort.PortName != modbusRtu_Serial.PortName || modbusRtu_Serial._SerialPort.BaudRate != modbusRtu_Serial.BaudRate || modbusRtu_Serial._SerialPort.Parity != modbusRtu_Serial.Parity || modbusRtu_Serial._SerialPort.DataBits != modbusRtu_Serial.DataBits || modbusRtu_Serial._SerialPort.StopBits != modbusRtu_Serial.StopBits || modbusRtu_Serial._SerialPort.ReadTimeout != modbusRtu_Serial.ReadTimeout || modbusRtu_Serial._SerialPort.WriteTimeout != modbusRtu_Serial.WriteTimeout) { // 关闭现有连接并重新配置 modbusRtu_Serial._SerialPort.Close(); //更新串口配置 modbusRtu_Serial._SerialPort.PortName = modbusRtu_Serial.PortName; modbusRtu_Serial._SerialPort.BaudRate = modbusRtu_Serial.BaudRate; modbusRtu_Serial._SerialPort.Parity = modbusRtu_Serial.Parity; modbusRtu_Serial._SerialPort.DataBits = modbusRtu_Serial.DataBits; modbusRtu_Serial._SerialPort.StopBits = modbusRtu_Serial.StopBits; modbusRtu_Serial._SerialPort.ReadTimeout = modbusRtu_Serial.ReadTimeout; modbusRtu_Serial._SerialPort.WriteTimeout = modbusRtu_Serial.WriteTimeout; // 重新打开串口 modbusRtu_Serial._SerialPort.Open(); } else { // 检查串口是否已打开 if (!modbusRtu_Serial._SerialPort.IsOpen) { // 打开串口 modbusRtu_Serial._SerialPort.Open(); } } return Task.FromResult(true); } /// /// 打开串口连接(同步方法)。 /// public void Connect() { if (_disposed) throw new ObjectDisposedException(nameof(ModbusRtu_Serial)); lock (_lock) { if (_serialPort == null) { _serialPort = new SerialPort(PortName, BaudRate, Parity, DataBits, StopBits) { ReadTimeout = ReadTimeout, WriteTimeout = WriteTimeout, // DtrEnable = true, // 可选:根据设备要求设置 // RtsEnable = true // 可选:根据设备要求设置 }; } if (!_serialPort.IsOpen) { _serialPort.Open(); } } } /// /// 关闭串口连接。 /// public void Disconnect() { lock (_lock) { if (_serialPort != null && _serialPort.IsOpen) { _serialPort.Close(); } } } /// /// 异步发送请求帧并接收响应帧,支持重试。 /// /// 待发送的完整 Modbus RTU 请求帧(含 CRC)。 /// 用于取消操作的取消令牌。 /// 接收到的响应帧字节数组。 private async Task SendAndReceiveFrameAsync(byte[] requestFrame, CancellationToken ct) { if (_disposed) throw new ObjectDisposedException(nameof(ModbusRtu_Serial)); if (_serialPort == null || !_serialPort.IsOpen) throw new InvalidOperationException("串口未连接。"); for (int attempt = 0; attempt <= MaxRetries; attempt++) { try { lock (_lock) { _serialPort.DiscardInBuffer(); // 清空接收缓冲区 _serialPort.Write(requestFrame, 0, requestFrame.Length); } // 等待响应,使用超时 await Task.Delay(50, ct); // 短暂等待设备响应 byte[] responseBuffer = new byte[256]; // 预分配足够大的缓冲区 int receivedBytes = 0; int totalBytesToRead = 0; // 等待第一个字节,实现超时 var start = DateTime.Now; while (_serialPort.BytesToRead == 0 && (DateTime.Now - start).TotalMilliseconds < ReadTimeout) { await Task.Delay(10, ct); } if (_serialPort.BytesToRead == 0) { if (attempt == MaxRetries) throw new TimeoutException("读取响应超时。"); continue; // 重试 } // 读取响应 while ((DateTime.Now - start).TotalMilliseconds < ReadTimeout) { int bytesAvailable = _serialPort.BytesToRead; if (bytesAvailable > 0) { int read = _serialPort.Read(responseBuffer, receivedBytes, responseBuffer.Length - receivedBytes); receivedBytes += read; if (receivedBytes >= 5) // 最小响应帧长度 (地址 + 功能 + 数据 + CRC) { // 尝试解析帧长度(根据功能码和数据长度) byte func = responseBuffer[1]; if ((func & 0x80) != 0) // 异常响应 { totalBytesToRead = 5; // 异常响应固定长度 } else { if (func == 0x01 || func == 0x02) // 读线圈/读离散输入 { totalBytesToRead = 3 + responseBuffer[2] + 2; // 地址 + 功能 + 字节计数 + 数据 + CRC } else if (func == 0x03 || func == 0x04) // 读保持/输入寄存器 { totalBytesToRead = 3 + (responseBuffer[2] * 2) + 2; // 地址 + 功能 + 字节计数 + (数据 * 2) + CRC } else if (func == 0x05 || func == 0x06) // 写单个线圈/寄存器 { totalBytesToRead = 6; // 响应长度固定 (地址 + 功能 + 起始地址 + CRC) } else if (func == 0x10) // 写多个寄存器 { totalBytesToRead = 6; // 响应长度固定 (地址 + 功能 + 起始地址 + 寄存器数量 + CRC) } else { totalBytesToRead = receivedBytes; // 无法预知长度,先读到当前为止 } } if (receivedBytes >= totalBytesToRead) { break; // 接收完成 } } } await Task.Delay(10, ct); } if (receivedBytes == 0) { if (attempt == MaxRetries) throw new TimeoutException("读取响应超时。"); continue; // 重试 } byte[] response = new byte[receivedBytes]; Array.Copy(responseBuffer, response, receivedBytes); return response; } catch (TimeoutException) { if (attempt == MaxRetries) throw; } catch (Exception) { if (attempt == MaxRetries) throw; 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 功能方法 // ———————————————————————— /// /// 异步读取从站的线圈状态(功能码 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 SendAndReceiveFrameAsync(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 SendAndReceiveFrameAsync(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 SendAndReceiveFrameAsync(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 SendAndReceiveFrameAsync(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 SendAndReceiveFrameAsync(request, ct); ValidateResponse(response, slaveAddress, 0x06); } /// /// 异步写入多个保持寄存器(功能码 10 (0x10))。 /// /// 从站地址。 /// 起始寄存器地址(0-based)。 /// 要写入的值数组。 /// 取消令牌。 /// 任务完成表示写入成功。 public async Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default) { if (values == null || values.Length == 0) throw new ArgumentException("写入值数组不能为空。", nameof(values)); int byteCount = values.Length * 2; byte[] data = new byte[4 + byteCount]; // 起始地址(2) + 寄存器数量(2) + 字节计数(1) + 数据(N) data[0] = (byte)(startAddress >> 8); data[1] = (byte)(startAddress & 0xFF); data[2] = (byte)(values.Length >> 8); data[3] = (byte)(values.Length & 0xFF); data[4] = (byte)byteCount; // 字节计数 for (int i = 0; i < values.Length; i++) { data[5 + i * 2] = (byte)(values[i] >> 8); // High byte data[5 + i * 2 + 1] = (byte)(values[i] & 0xFF); // Low byte } byte[] request = BuildRtuFrame(slaveAddress, 0x10, data); byte[] response = await SendAndReceiveFrameAsync(request, ct); ValidateResponse(response, slaveAddress, 0x10); } /// /// 异步读取从站的输入寄存器值(功能码 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 SendAndReceiveFrameAsync(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; } /// /// 释放串口资源并标记为已处置。 /// public void Dispose() { if (!_disposed) { Disconnect(); if (_serialPort != null) { _serialPort.Dispose(); _serialPort = null; } _disposed = true; } } } }