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;
}
}
}
}