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