什么是 Modbus?
Modbus 是一种在工业电子设备中广泛使用的通信协议,允许设备之间进行信息交换。它的设计初衷是为了支持各类设备之间的互操作性。Modbus 协议主要有两个重载:Modbus RTU 和 Modbus TCP。本文将重点讨论 Modbus TCP 的实现。
安装 NModbus4.Core
首先,我们需要通过 NuGet 包管理器来安装 NModbus4.Core。可以使用以下命令:
Install-Package NModbus4
创建 Modbus 客户端接口
我们首先定义一个 IModbusClient
接口,作为通用的 Modbus 客户端接口来管理连接和数据操作。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Rick.Core.Services.Modbus
{
/// <summary>
/// Modbus客户端接口,定义了Modbus通信的基本操作
/// </summary>
public interface IModbusClient : IDisposable
{
/// <summary>
/// 当前连接状态
/// </summary>
bool IsConnected { get; }
/// <summary>
/// 连接到Modbus服务器
/// </summary>
void Connect();
/// <summary>
/// 异步连接到Modbus服务器
/// </summary>
Task ConnectAsync();
/// <summary>
/// 断开连接
/// </summary>
void Disconnect();
/// <summary>
/// 读取线圈状态
/// </summary>
/// <param name="slaveAddress">从站地址</param>
/// <param name="startAddress">起始地址</param>
/// <param name="count">数量</param>
/// <returns>线圈状态数组</returns>
bool[] ReadCoils(byte slaveAddress, ushort startAddress, ushort count);
/// <summary>
/// 异步读取线圈状态
/// </summary>
Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort count);
/// <summary>
/// 读取离散输入
/// </summary>
bool[] ReadDiscreteInputs(byte slaveAddress, ushort startAddress, ushort count);
/// <summary>
/// 异步读取离散输入
/// </summary>
Task<bool[]> ReadDiscreteInputsAsync(byte slaveAddress, ushort startAddress, ushort count);
/// <summary>
/// 读取保持寄存器
/// </summary>
ushort[] ReadHoldingRegisters(byte slaveAddress, ushort startAddress, ushort count);
/// <summary>
/// 异步读取保持寄存器
/// </summary>
Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort count);
/// <summary>
/// 读取输入寄存器
/// </summary>
ushort[] ReadInputRegisters(byte slaveAddress, ushort startAddress, ushort count);
/// <summary>
/// 异步读取输入寄存器
/// </summary>
Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort count);
/// <summary>
/// 写单个线圈
/// </summary>
void WriteSingleCoil(byte slaveAddress, ushort coilAddress, bool value);
/// <summary>
/// 异步写单个线圈
/// </summary>
Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value);
/// <summary>
/// 写单个寄存器
/// </summary>
void WriteSingleRegister(byte slaveAddress, ushort registerAddress, ushort value);
/// <summary>
/// 异步写单个寄存器
/// </summary>
Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value);
/// <summary>
/// 写多个线圈
/// </summary>
void WriteMultipleCoils(byte slaveAddress, ushort startAddress, bool[] values);
/// <summary>
/// 异步写多个线圈
/// </summary>
Task WriteMultipleCoilsAsync(byte slaveAddress, ushort startAddress, bool[] values);
/// <summary>
/// 写多个寄存器
/// </summary>
void WriteMultipleRegisters(byte slaveAddress, ushort startAddress, ushort[] values);
/// <summary>
/// 异步写多个寄存器
/// </summary>
Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values);
}
}
连接管理
Connect()
方法用于建立连接,Disconnect()
方法则用于断开连接,而 IsConnected
属性用于检查当前连接状态。
实现 ModbusTcpClient
我们接下来实现 ModbusTcpClient
类,该类继承 IModbusClient
接口,并使用 NModbus4 来处理 TCP 通信。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Modbus.Device;
namespace Rick.Core.Services.Modbus
{
/// <summary>
/// Modbus TCP客户端类,基于NModbus4实现
/// </summary>
publicclass ModbusTcpClient : IModbusClient
{
private readonly string _ipAddress;
private readonly int _port;
private readonly int _connectionTimeout;
private TcpClient _tcpClient;
private ModbusIpMaster _master;
/// <summary>
/// 是否已连接
/// </summary>
publicbool IsConnected => _tcpClient?.Connected ?? false;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="ipAddress">IP地址</param>
/// <param name="port">端口号,默认为502</param>
/// <param name="connectionTimeout">连接超时时间(毫秒),默认为3000</param>
public ModbusTcpClient(string ipAddress, int port = 502, int connectionTimeout = 3000)
{
_ipAddress = ipAddress ?? thrownew ArgumentNullException(nameof(ipAddress));
_port = port;
_connectionTimeout = connectionTimeout;
}
/// <summary>
/// 连接到Modbus服务器
/// </summary>
public void Connect()
{
if (IsConnected)
return;
try
{
_tcpClient = new TcpClient();
var result = _tcpClient.BeginConnect(_ipAddress, _port, null, null);
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(_connectionTimeout));
if (!success)
{
thrownew ModbusException($"连接超时: {_ipAddress}:{_port}");
}
_tcpClient.EndConnect(result);
// 创建Modbus主站
_master = ModbusIpMaster.CreateIp(_tcpClient);
}
catch (Exception ex)
{
_tcpClient?.Dispose();
_tcpClient = null;
_master = null;
thrownew ModbusException($"连接到 {_ipAddress}:{_port} 失败", ex);
}
}
/// <summary>
/// 异步连接到Modbus服务器
/// </summary>
public async Task ConnectAsync()
{
if (IsConnected)
return;
try
{
_tcpClient = new TcpClient();
var connectTask = _tcpClient.ConnectAsync(_ipAddress, _port);
// 添加超时
if (await Task.WhenAny(connectTask, Task.Delay(_connectionTimeout)) != connectTask)
{
thrownew ModbusException($"连接超时: {_ipAddress}:{_port}");
}
await connectTask; // 等待连接完成或抛出异常
// 创建Modbus主站
_master = ModbusIpMaster.CreateIp(_tcpClient);
}
catch (Exception ex)
{
_tcpClient?.Dispose();
_tcpClient = null;
_master = null;
thrownew ModbusException($"连接到 {_ipAddress}:{_port} 失败", ex);
}
}
/// <summary>
/// 断开连接
/// </summary>
public void Disconnect()
{
_master = null;
if (_tcpClient != null)
{
if (_tcpClient.Connected)
{
_tcpClient.GetStream().Close();
}
_tcpClient.Close();
_tcpClient.Dispose();
_tcpClient = null;
}
}
/// <summary>
/// 读取线圈状态
/// </summary>
publicbool[] ReadCoils(byte slaveAddress, ushort startAddress, ushort count)
{
EnsureConnected();
return _master.ReadCoils(slaveAddress, startAddress, count);
}
/// <summary>
/// 异步读取线圈状态
/// </summary>
public async Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort count)
{
EnsureConnected();
return await _master.ReadCoilsAsync(slaveAddress, startAddress, count);
}
/// <summary>
/// 读取离散输入
/// </summary>
publicbool[] ReadDiscreteInputs(byte slaveAddress, ushort startAddress, ushort count)
{
EnsureConnected();
return _master.ReadInputs(slaveAddress, startAddress, count);
}
/// <summary>
/// 异步读取离散输入
/// </summary>
public async Task<bool[]> ReadDiscreteInputsAsync(byte slaveAddress, ushort startAddress, ushort count)
{
EnsureConnected();
return await _master.ReadInputsAsync(slaveAddress, startAddress, count);
}
/// <summary>
/// 读取保持寄存器
/// </summary>
public ushort[] ReadHoldingRegisters(byte slaveAddress, ushort startAddress, ushort count)
{
EnsureConnected();
return _master.ReadHoldingRegisters(slaveAddress, startAddress, count);
}
/// <summary>
/// 异步读取保持寄存器
/// </summary>
public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort count)
{
EnsureConnected();
return await _master.ReadHoldingRegistersAsync(slaveAddress, startAddress, count);
}
/// <summary>
/// 读取输入寄存器
/// </summary>
public ushort[] ReadInputRegisters(byte slaveAddress, ushort startAddress, ushort count)
{
EnsureConnected();
return _master.ReadInputRegisters(slaveAddress, startAddress, count);
}
/// <summary>
/// 异步读取输入寄存器
/// </summary>
public async Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort count)
{
EnsureConnected();
return await _master.ReadInputRegistersAsync(slaveAddress, startAddress, count);
}
/// <summary>
/// 写单个线圈
/// </summary>
public void WriteSingleCoil(byte slaveAddress, ushort coilAddress, bool value)
{
EnsureConnected();
_master.WriteSingleCoil(slaveAddress, coilAddress, value);
}
/// <summary>
/// 异步写单个线圈
/// </summary>
public async Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value)
{
EnsureConnected();
await _master.WriteSingleCoilAsync(slaveAddress, coilAddress, value);
}
/// <summary>
/// 写单个寄存器
/// </summary>
public void WriteSingleRegister(byte slaveAddress, ushort registerAddress, ushort value)
{
EnsureConnected();
_master.WriteSingleRegister(slaveAddress, registerAddress, value);
}
/// <summary>
/// 异步写单个寄存器
/// </summary>
public async Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value)
{
EnsureConnected();
await _master.WriteSingleRegisterAsync(slaveAddress, registerAddress, value);
}
/// <summary>
/// 写多个线圈
/// </summary>
public void WriteMultipleCoils(byte slaveAddress, ushort startAddress, bool[] values)
{
EnsureConnected();
_master.WriteMultipleCoils(slaveAddress, startAddress, values);
}
/// <summary>
/// 异步写多个线圈
/// </summary>
public async Task WriteMultipleCoilsAsync(byte slaveAddress, ushort startAddress, bool[] values)
{
EnsureConnected();
await _master.WriteMultipleCoilsAsync(slaveAddress, startAddress, values);
}
/// <summary>
/// 写多个寄存器
/// </summary>
public void WriteMultipleRegisters(byte slaveAddress, ushort startAddress, ushort[] values)
{
EnsureConnected();
_master.WriteMultipleRegisters(slaveAddress, startAddress, values);
}
/// <summary>
/// 异步写多个寄存器
/// </summary>
public async Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values)
{
EnsureConnected();
await _master.WriteMultipleRegistersAsync(slaveAddress, startAddress, values);
}
/// <summary>
/// 确保已连接
/// </summary>
private void EnsureConnected()
{
if (!IsConnected)
{
thrownew ModbusException("客户端未连接,请先调用Connect方法");
}
}
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
Disconnect();
}
}
}
字节顺序处理
在使用 Modbus 协议时,处理数据的字节顺序非常重要。我们定义以下枚举来处理字节顺序:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Rick.Core.Services.Modbus
{
/// <summary>
/// 字节顺序枚举,用于浮点数和双精度数等多字节数据的读写
/// </summary>
publicenum ByteOrder
{
/// <summary>
/// 标准顺序 - 1234
/// </summary>
ABCD,
/// <summary>
/// 完全倒序 - 4321
/// </summary>
DCBA,
/// <summary>
/// 交换字顺序 - 2143
/// </summary>
BADC,
/// <summary>
/// 交换字节顺序 - 3412
/// </summary>
CDAB
}
}
异常处理
在 Modbus 通信中,可能会出现一些异常情况。我们可以定义一个 ModbusException
类来捕获和处理这些异常:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Rick.Core.Services.Modbus
{
/// <summary>
/// Modbus通信异常类
/// </summary>
publicclass ModbusException : Exception
{
public ModbusException() : base() { }
public ModbusException(string message) : base(message) { }
public ModbusException(string message, Exception innerException)
: base(message, innerException) { }
}
}
辅助类 ModbusDataHelper
最后,我们可以创建一个辅助类 ModbusDataHelper
,用于实现字节转换等实用方法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Rick.Core.Services.Modbus
{
/// <summary>
/// Modbus数据处理辅助类
/// 提供各种数据类型与Modbus寄存器之间的转换功能
/// </summary>
publicstaticclass ModbusDataHelper
{
/// <summary>
/// 将浮点数写入寄存器
/// </summary>
/// <param name="client">Modbus客户端</param>
/// <param name="slaveAddress">从站地址</param>
/// <param name="startAddress">起始地址</param>
/// <param name="value">浮点数值</param>
/// <param name="byteOrder">字节顺序</param>
public static void WriteFloat(IModbusClient client, byte slaveAddress, ushort startAddress, float value, ByteOrder byteOrder = ByteOrder.ABCD)
{
byte[] floatBytes = BitConverter.GetBytes(value);
byte[] orderedBytes = OrderBytes(floatBytes, byteOrder);
ushort[] registers = new ushort[2];
registers[0] = BitConverter.ToUInt16(orderedBytes, 0);
registers[1] = BitConverter.ToUInt16(orderedBytes, 2);
client.WriteMultipleRegisters(slaveAddress, startAddress, registers);
}
/// <summary>
/// 异步将浮点数写入寄存器
/// </summary>
public static async Task WriteFloatAsync(IModbusClient client, byte slaveAddress, ushort startAddress, float value, ByteOrder byteOrder = ByteOrder.ABCD)
{
byte[] floatBytes = BitConverter.GetBytes(value);
byte[] orderedBytes = OrderBytes(floatBytes, byteOrder);
ushort[] registers = new ushort[2];
registers[0] = BitConverter.ToUInt16(orderedBytes, 0);
registers[1] = BitConverter.ToUInt16(orderedBytes, 2);
await client.WriteMultipleRegistersAsync(slaveAddress, startAddress, registers);
}
/// <summary>
/// 从寄存器读取浮点数
/// </summary>
/// <param name="client">Modbus客户端</param>
/// <param name="slaveAddress">从站地址</param>
/// <param name="startAddress">起始地址</param>
/// <param name="byteOrder">字节顺序</param>
/// <returns>浮点数值</returns>
public static float ReadFloat(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD)
{
ushort[] registers = client.ReadHoldingRegisters(slaveAddress, startAddress, 2);
return ConvertRegistersToFloat(registers, byteOrder);
}
/// <summary>
/// 异步从寄存器读取浮点数
/// </summary>
publicstatic async Task<float> ReadFloatAsync(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD)
{
ushort[] registers = await client.ReadHoldingRegistersAsync(slaveAddress, startAddress, 2);
return ConvertRegistersToFloat(registers, byteOrder);
}
/// <summary>
/// 将双精度浮点数写入寄存器
/// </summary>
public static void WriteDouble(IModbusClient client, byte slaveAddress, ushort startAddress, double value, ByteOrder byteOrder = ByteOrder.ABCD)
{
byte[] doubleBytes = BitConverter.GetBytes(value);
byte[] orderedBytes = OrderBytes(doubleBytes, byteOrder);
ushort[] registers = new ushort[4];
registers[0] = BitConverter.ToUInt16(orderedBytes, 0);
registers[1] = BitConverter.ToUInt16(orderedBytes, 2);
registers[2] = BitConverter.ToUInt16(orderedBytes, 4);
registers[3] = BitConverter.ToUInt16(orderedBytes, 6);
client.WriteMultipleRegisters(slaveAddress, startAddress, registers);
}
/// <summary>
/// 异步将双精度浮点数写入寄存器
/// </summary>
public static async Task WriteDoubleAsync(IModbusClient client, byte slaveAddress, ushort startAddress, double value, ByteOrder byteOrder = ByteOrder.ABCD)
{
byte[] doubleBytes = BitConverter.GetBytes(value);
byte[] orderedBytes = OrderBytes(doubleBytes, byteOrder);
ushort[] registers = new ushort[4];
registers[0] = BitConverter.ToUInt16(orderedBytes, 0);
registers[1] = BitConverter.ToUInt16(orderedBytes, 2);
registers[2] = BitConverter.ToUInt16(orderedBytes, 4);
registers[3] = BitConverter.ToUInt16(orderedBytes, 6);
await client.WriteMultipleRegistersAsync(slaveAddress, startAddress, registers);
}
/// <summary>
/// 从寄存器读取双精度浮点数
/// </summary>
public static double ReadDouble(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD)
{
ushort[] registers = client.ReadHoldingRegisters(slaveAddress, startAddress, 4);
return ConvertRegistersToDouble(registers, byteOrder);
}
/// <summary>
/// 异步从寄存器读取双精度浮点数
/// </summary>
publicstatic async Task<double> ReadDoubleAsync(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD)
{
ushort[] registers = await client.ReadHoldingRegistersAsync(slaveAddress, startAddress, 4);
return ConvertRegistersToDouble(registers, byteOrder);
}
/// <summary>
/// 将Int32写入寄存器
/// </summary>
public static void WriteInt32(IModbusClient client, byte slaveAddress, ushort startAddress, int value, ByteOrder byteOrder = ByteOrder.ABCD)
{
byte[] intBytes = BitConverter.GetBytes(value);
byte[] orderedBytes = OrderBytes(intBytes, byteOrder);
ushort[] registers = new ushort[2];
registers[0] = BitConverter.ToUInt16(orderedBytes, 0);
registers[1] = BitConverter.ToUInt16(orderedBytes, 2);
client.WriteMultipleRegisters(slaveAddress, startAddress, registers);
}
/// <summary>
/// 异步将Int32写入寄存器
/// </summary>
public static async Task WriteInt32Async(IModbusClient client, byte slaveAddress, ushort startAddress, int value, ByteOrder byteOrder = ByteOrder.ABCD)
{
byte[] intBytes = BitConverter.GetBytes(value);
byte[] orderedBytes = OrderBytes(intBytes, byteOrder);
ushort[] registers = new ushort[2];
registers[0] = BitConverter.ToUInt16(orderedBytes, 0);
registers[1] = BitConverter.ToUInt16(orderedBytes, 2);
await client.WriteMultipleRegistersAsync(slaveAddress, startAddress, registers);
}
/// <summary>
/// 从寄存器读取Int32
/// </summary>
public static int ReadInt32(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD)
{
ushort[] registers = client.ReadHoldingRegisters(slaveAddress, startAddress, 2);
return ConvertRegistersToInt32(registers, byteOrder);
}
/// <summary>
/// 异步从寄存器读取Int32
/// </summary>
publicstatic async Task<int> ReadInt32Async(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD)
{
ushort[] registers = await client.ReadHoldingRegistersAsync(slaveAddress, startAddress, 2);
return ConvertRegistersToInt32(registers, byteOrder);
}
/// <summary>
/// 将UInt32写入寄存器
/// </summary>
public static void WriteUInt32(IModbusClient client, byte slaveAddress, ushort startAddress, uint value, ByteOrder byteOrder = ByteOrder.ABCD)
{
byte[] uintBytes = BitConverter.GetBytes(value);
byte[] orderedBytes = OrderBytes(uintBytes, byteOrder);
ushort[] registers = new ushort[2];
registers[0] = BitConverter.ToUInt16(orderedBytes, 0);
registers[1] = BitConverter.ToUInt16(orderedBytes, 2);
client.WriteMultipleRegisters(slaveAddress, startAddress, registers);
}
/// <summary>
/// 异步将UInt32写入寄存器
/// </summary>
public static async Task WriteUInt32Async(IModbusClient client, byte slaveAddress, ushort startAddress, uint value, ByteOrder byteOrder = ByteOrder.ABCD)
{
byte[] uintBytes = BitConverter.GetBytes(value);
byte[] orderedBytes = OrderBytes(uintBytes, byteOrder);
ushort[] registers = new ushort[2];
registers[0] = BitConverter.ToUInt16(orderedBytes, 0);
registers[1] = BitConverter.ToUInt16(orderedBytes, 2);
await client.WriteMultipleRegistersAsync(slaveAddress, startAddress, registers);
}
/// <summary>
/// 从寄存器读取UInt32
/// </summary>
public static uint ReadUInt32(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD)
{
ushort[] registers = client.ReadHoldingRegisters(slaveAddress, startAddress, 2);
return ConvertRegistersToUInt32(registers, byteOrder);
}
/// <summary>
/// 异步从寄存器读取UInt32
/// </summary>
publicstatic async Task<uint> ReadUInt32Async(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD)
{
ushort[] registers = await client.ReadHoldingRegistersAsync(slaveAddress, startAddress, 2);
return ConvertRegistersToUInt32(registers, byteOrder);
}
/// <summary>
/// 将Int64写入寄存器
/// </summary>
public static void WriteInt64(IModbusClient client, byte slaveAddress, ushort startAddress, long value, ByteOrder byteOrder = ByteOrder.ABCD)
{
byte[] longBytes = BitConverter.GetBytes(value);
byte[] orderedBytes = OrderBytes(longBytes, byteOrder);
ushort[] registers = new ushort[4];
registers[0] = BitConverter.ToUInt16(orderedBytes, 0);
registers[1] = BitConverter.ToUInt16(orderedBytes, 2);
registers[2] = BitConverter.ToUInt16(orderedBytes, 4);
registers[3] = BitConverter.ToUInt16(orderedBytes, 6);
client.WriteMultipleRegisters(slaveAddress, startAddress, registers);
}
/// <summary>
/// 异步将Int64写入寄存器
/// </summary>
public static async Task WriteInt64Async(IModbusClient client, byte slaveAddress, ushort startAddress, long value, ByteOrder byteOrder = ByteOrder.ABCD)
{
byte[] longBytes = BitConverter.GetBytes(value);
byte[] orderedBytes = OrderBytes(longBytes, byteOrder);
ushort[] registers = new ushort[4];
registers[0] = BitConverter.ToUInt16(orderedBytes, 0);
registers[1] = BitConverter.ToUInt16(orderedBytes, 2);
registers[2] = BitConverter.ToUInt16(orderedBytes, 4);
registers[3] = BitConverter.ToUInt16(orderedBytes, 6);
await client.WriteMultipleRegistersAsync(slaveAddress, startAddress, registers);
}
/// <summary>
/// 从寄存器读取Int64
/// </summary>
public static long ReadInt64(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD)
{
ushort[] registers = client.ReadHoldingRegisters(slaveAddress, startAddress, 4);
return ConvertRegistersToInt64(registers, byteOrder);
}
/// <summary>
/// 异步从寄存器读取Int64
/// </summary>
publicstatic async Task<long> ReadInt64Async(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD)
{
ushort[] registers = await client.ReadHoldingRegistersAsync(slaveAddress, startAddress, 4);
return ConvertRegistersToInt64(registers, byteOrder);
}
/// <summary>
/// 将UInt64写入寄存器
/// </summary>
public static void WriteUInt64(IModbusClient client, byte slaveAddress, ushort startAddress, ulong value, ByteOrder byteOrder = ByteOrder.ABCD)
{
byte[] ulongBytes = BitConverter.GetBytes(value);
byte[] orderedBytes = OrderBytes(ulongBytes, byteOrder);
ushort[] registers = new ushort[4];
registers[0] = BitConverter.ToUInt16(orderedBytes, 0);
registers[1] = BitConverter.ToUInt16(orderedBytes, 2);
registers[2] = BitConverter.ToUInt16(orderedBytes, 4);
registers[3] = BitConverter.ToUInt16(orderedBytes, 6);
client.WriteMultipleRegisters(slaveAddress, startAddress, registers);
}
/// <summary>
/// 异步将UInt64写入寄存器
/// </summary>
public static async Task WriteUInt64Async(IModbusClient client, byte slaveAddress, ushort startAddress, ulong value, ByteOrder byteOrder = ByteOrder.ABCD)
{
byte[] ulongBytes = BitConverter.GetBytes(value);
byte[] orderedBytes = OrderBytes(ulongBytes, byteOrder);
ushort[] registers = new ushort[4];
registers[0] = BitConverter.ToUInt16(orderedBytes, 0);
registers[1] = BitConverter.ToUInt16(orderedBytes, 2);
registers[2] = BitConverter.ToUInt16(orderedBytes, 4);
registers[3] = BitConverter.ToUInt16(orderedBytes, 6);
await client.WriteMultipleRegistersAsync(slaveAddress, startAddress, registers);
}
/// <summary>
/// 从寄存器读取UInt64
/// </summary>
public static ulong ReadUInt64(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD)
{
ushort[] registers = client.ReadHoldingRegisters(slaveAddress, startAddress, 4);
return ConvertRegistersToUInt64(registers, byteOrder);
}
/// <summary>
/// 异步从寄存器读取UInt64
/// </summary>
publicstatic async Task<ulong> ReadUInt64Async(IModbusClient client, byte slaveAddress, ushort startAddress, ByteOrder byteOrder = ByteOrder.ABCD)
{
ushort[] registers = await client.ReadHoldingRegistersAsync(slaveAddress, startAddress, 4);
return ConvertRegistersToUInt64(registers, byteOrder);
}
/// <summary>
/// 将字符串写入寄存器
/// </summary>
public static void WriteString(IModbusClient client, byte slaveAddress, ushort startAddress, string value, int maxLength = 20)
{
if (string.IsNullOrEmpty(value))
{
value = string.Empty;
}
// 将字符串转换为字节数组
byte[] stringBytes = System.Text.Encoding.ASCII.GetBytes(value);
// 计算需要的寄存器数量(每个寄存器2字节)
int registerCount = (int)Math.Ceiling((double)maxLength / 2);
ushort[] registers = new ushort[registerCount];
// 填充寄存器
for (int i = 0; i < registerCount; i++)
{
int bytePos = i * 2;
byte lowByte = bytePos < stringBytes.Length ? stringBytes[bytePos] : (byte)0;
byte highByte = bytePos + 1 < stringBytes.Length ? stringBytes[bytePos + 1] : (byte)0;
registers[i] = (ushort)((highByte << 8) | lowByte);
}
client.WriteMultipleRegisters(slaveAddress, startAddress, registers);
}
/// <summary>
/// 异步将字符串写入寄存器
/// </summary>
public static async Task WriteStringAsync(IModbusClient client, byte slaveAddress, ushort startAddress, string value, int maxLength = 20)
{
if (string.IsNullOrEmpty(value))
{
value = string.Empty;
}
// 将字符串转换为字节数组
byte[] stringBytes = System.Text.Encoding.ASCII.GetBytes(value);
// 计算需要的寄存器数量(每个寄存器2字节)
int registerCount = (int)Math.Ceiling((double)maxLength / 2);
ushort[] registers = new ushort[registerCount];
// 填充寄存器
for (int i = 0; i < registerCount; i++)
{
int bytePos = i * 2;
byte lowByte = bytePos < stringBytes.Length ? stringBytes[bytePos] : (byte)0;
byte highByte = bytePos + 1 < stringBytes.Length ? stringBytes[bytePos + 1] : (byte)0;
registers[i] = (ushort)((highByte << 8) | lowByte);
}
await client.WriteMultipleRegistersAsync(slaveAddress, startAddress, registers);
}
/// <summary>
/// 从寄存器读取字符串
/// </summary>
public static string ReadString(IModbusClient client, byte slaveAddress, ushort startAddress, int length)
{
int registerCount = (int)Math.Ceiling((double)length / 2);
ushort[] registers = client.ReadHoldingRegisters(slaveAddress, startAddress, (ushort)registerCount);
return ConvertRegistersToString(registers, length);
}
/// <summary>
/// 异步从寄存器读取字符串
/// </summary>
publicstatic async Task<string> ReadStringAsync(IModbusClient client, byte slaveAddress, ushort startAddress, int length)
{
int registerCount = (int)Math.Ceiling((double)length / 2);
ushort[] registers = await client.ReadHoldingRegistersAsync(slaveAddress, startAddress, (ushort)registerCount);
return ConvertRegistersToString(registers, length);
}
#region 内部辅助方法
/// <summary>
/// 根据字节顺序调整字节数组
/// </summary>
privatestatic byte[] OrderBytes(byte[] bytes, ByteOrder order)
{
byte[] result = new byte[bytes.Length];
switch (order)
{
case ByteOrder.ABCD: // 1234
Array.Copy(bytes, result, bytes.Length);
break;
case ByteOrder.DCBA: // 4321
for (int i = 0; i < bytes.Length; i++)
{
result[i] = bytes[bytes.Length - 1 - i];
}
break;
case ByteOrder.BADC: // 2143
for (int i = 0; i < bytes.Length; i += 2)
{
if (i + 1 < bytes.Length)
{
result[i] = bytes[i + 1];
result[i + 1] = bytes[i];
}
}
break;
case ByteOrder.CDAB: // 3412
int halfLength = bytes.Length / 2;
for (int i = 0; i < halfLength; i++)
{
result[i] = bytes[i + halfLength];
result[i + halfLength] = bytes[i];
}
break;
}
return result;
}
/// <summary>
/// 将寄存器转换为浮点数
/// </summary>
private static float ConvertRegistersToFloat(ushort[] registers, ByteOrder byteOrder)
{
if (registers.Length < 2)
{
thrownew ArgumentException("需要至少2个寄存器来转换为浮点数");
}
byte[] bytes = new byte[4];
bytes[0] = (byte)(registers[0] & 0xFF);
bytes[1] = (byte)(registers[0] >> 8);
bytes[2] = (byte)(registers[1] & 0xFF);
bytes[3] = (byte)(registers[1] >> 8);
byte[] orderedBytes = OrderBytes(bytes, byteOrder);
return BitConverter.ToSingle(orderedBytes, 0);
}
/// <summary>
/// 将寄存器转换为双精度浮点数
/// </summary>
private static double ConvertRegistersToDouble(ushort[] registers, ByteOrder byteOrder)
{
if (registers.Length < 4)
{
thrownew ArgumentException("需要至少4个寄存器来转换为双精度浮点数");
}
byte[] bytes = new byte[8];
bytes[0] = (byte)(registers[0] & 0xFF);
bytes[1] = (byte)(registers[0] >> 8);
bytes[2] = (byte)(registers[1] & 0xFF);
bytes[3] = (byte)(registers[1] >> 8);
bytes[4] = (byte)(registers[2] & 0xFF);
bytes[5] = (byte)(registers[2] >> 8);
bytes[6] = (byte)(registers[3] & 0xFF);
bytes[7] = (byte)(registers[3] >> 8);
byte[] orderedBytes = OrderBytes(bytes, byteOrder);
return BitConverter.ToDouble(orderedBytes, 0);
}
/// <summary>
/// 将寄存器转换为32位整数
/// </summary>
private static int ConvertRegistersToInt32(ushort[] registers, ByteOrder byteOrder)
{
if (registers.Length < 2)
{
thrownew ArgumentException("需要至少2个寄存器来转换为32位整数");
}
byte[] bytes = new byte[4];
bytes[0] = (byte)(registers[0] & 0xFF);
bytes[1] = (byte)(registers[0] >> 8);
bytes[2] = (byte)(registers[1] & 0xFF);
bytes[3] = (byte)(registers[1] >> 8);
byte[] orderedBytes = OrderBytes(bytes, byteOrder);
return BitConverter.ToInt32(orderedBytes, 0);
}
/// <summary>
/// 将寄存器转换为32位无符号整数
/// </summary>
private static uint ConvertRegistersToUInt32(ushort[] registers, ByteOrder byteOrder)
{
if (registers.Length < 2)
{
thrownew ArgumentException("需要至少2个寄存器来转换为32位无符号整数");
}
byte[] bytes = new byte[4];
bytes[0] = (byte)(registers[0] & 0xFF);
bytes[1] = (byte)(registers[0] >> 8);
bytes[2] = (byte)(registers[1] & 0xFF);
bytes[3] = (byte)(registers[1] >> 8);
byte[] orderedBytes = OrderBytes(bytes, byteOrder);
return BitConverter.ToUInt32(orderedBytes, 0);
}
/// <summary>
/// 将寄存器转换为64位整数
/// </summary>
private static long ConvertRegistersToInt64(ushort[] registers, ByteOrder byteOrder)
{
if (registers.Length < 4)
{
thrownew ArgumentException("需要至少4个寄存器来转换为64位整数");
}
byte[] bytes = new byte[8];
bytes[0] = (byte)(registers[0] & 0xFF);
bytes[1] = (byte)(registers[0] >> 8);
bytes[2] = (byte)(registers[1] & 0xFF);
bytes[3] = (byte)(registers[1] >> 8);
bytes[4] = (byte)(registers[2] & 0xFF);
bytes[5] = (byte)(registers[2] >> 8);
bytes[6] = (byte)(registers[3] & 0xFF);
bytes[7] = (byte)(registers[3] >> 8);
byte[] orderedBytes = OrderBytes(bytes, byteOrder);
return BitConverter.ToInt64(orderedBytes, 0);
}
/// <summary>
/// 将寄存器转换为64位无符号整数
/// </summary>
private static ulong ConvertRegistersToUInt64(ushort[] registers, ByteOrder byteOrder)
{
if (registers.Length < 4)
{
thrownew ArgumentException("需要至少4个寄存器来转换为64位无符号整数");
}
byte[] bytes = new byte[8];
bytes[0] = (byte)(registers[0] & 0xFF);
bytes[1] = (byte)(registers[0] >> 8);
bytes[2] = (byte)(registers[1] & 0xFF);
bytes[3] = (byte)(registers[1] >> 8);
bytes[4] = (byte)(registers[2] & 0xFF);
bytes[5] = (byte)(registers[2] >> 8);
bytes[6] = (byte)(registers[3] & 0xFF);
bytes[7] = (byte)(registers[3] >> 8);
byte[] orderedBytes = OrderBytes(bytes, byteOrder);
return BitConverter.ToUInt64(orderedBytes, 0);
}
/// <summary>
/// 将寄存器转换为字符串
/// </summary>
private static string ConvertRegistersToString(ushort[] registers, int maxLength)
{
// 计算字节数组长度(每个寄存器2字节)
byte[] bytes = new byte[registers.Length * 2];
// 从寄存器提取字节
for (int i = 0; i < registers.Length; i++)
{
bytes[i * 2] = (byte)(registers[i] & 0xFF);
bytes[i * 2 + 1] = (byte)(registers[i] >> 8);
}
// 截断到指定长度
int length = Math.Min(maxLength, bytes.Length);
// 查找第一个0字节(字符串结束符)
int zeroIndex = -1;
for (int i = 0; i < length; i++)
{
if (bytes[i] == 0)
{
zeroIndex = i;
break;
}
}
// 如果找到了0字节,则截断到该位置
if (zeroIndex >= 0)
{
length = zeroIndex;
}
// 转换为字符串并返回
return System.Text.Encoding.ASCII.GetString(bytes, 0, length);
}
#endregion
}
}
使用案例
假设我们有一个控制设备的IP地址为 "192.168.1.10",端口为 502,我们可以使用如下代码实现 Modbus 读写操作:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Rick.Core.Services.Modbus;
namespace Rick.Test
{
publicstaticclass ModbusTest
{
privatestaticstring ServerIp = "127.0.0.1"; // 服务器IP地址
privatestaticint ServerPort = 502; // 服务器端口
privatestatic byte SlaveId = 1; // 从站ID
public static async Task Call()
{
Console.WriteLine("ModbusTcpClient 测试程序");
Console.WriteLine("=====================\n");
// 显示连接配置
Console.WriteLine($"服务器IP: {ServerIp}");
Console.WriteLine($"服务器端口: {ServerPort}");
Console.WriteLine($"从站ID: {SlaveId}");
// 允许用户修改连接配置
AdjustConnectionSettings();
// 声明客户端变量
ModbusTcpClient client = null;
try
{
// 1. 创建Modbus客户端
Console.WriteLine("\n创建Modbus TCP客户端...");
client = new ModbusTcpClient(ServerIp, ServerPort);
// 2. 连接到Modbus服务器
Console.WriteLine($"正在连接到服务器 {ServerIp}:{ServerPort}...");
client.Connect();
if (!client.IsConnected)
{
thrownew Exception("无法连接到Modbus服务器,请确认服务器是否已启动");
}
Console.WriteLine("✓ 成功连接到服务器");
// 3. 执行测试菜单
bool exitRequested = false;
while (!exitRequested && client.IsConnected)
{
exitRequested = await ShowMenuAndRunTest(client);
}
}
catch (Exception ex)
{
Console.WriteLine($"\n❌ 错误: {ex.Message}");
Console.WriteLine($"异常类型: {ex.GetType().Name}");
Console.WriteLine($"堆栈跟踪:\n{ex.StackTrace}");
}
finally
{
// 4. 断开连接并释放资源
if (client != null)
{
if (client.IsConnected)
{
Console.WriteLine("\n断开连接...");
client.Disconnect();
}
client.Dispose();
Console.WriteLine("客户端资源已释放");
}
Console.WriteLine("\n按任意键退出...");
Console.ReadKey();
}
}
// 允许用户调整连接设置
private static void AdjustConnectionSettings()
{
Console.WriteLine("\n是否需要修改连接设置? (Y/N)");
var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.Y)
{
Console.Write($"\n服务器IP ({ServerIp}): ");
string input = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(input))
{
ServerIp = input;
}
Console.Write($"服务器端口 ({ServerPort}): ");
input = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(input) && int.TryParse(input, out int port))
{
ServerPort = port;
}
Console.Write($"从站ID ({SlaveId}): ");
input = Console.ReadLine();
if (!string.IsNullOrWhiteSpace(input) && byte.TryParse(input, out byte id))
{
SlaveId = id;
}
Console.WriteLine($"\n已更新连接设置: {ServerIp}:{ServerPort}, 从站ID={SlaveId}");
}
}
// 显示测试菜单并执行选择的测试
privatestatic async Task<bool> ShowMenuAndRunTest(ModbusTcpClient client)
{
Console.WriteLine("\n== 测试菜单 ==");
Console.WriteLine("1. 读写线圈测试");
Console.WriteLine("2. 读取离散输入测试");
Console.WriteLine("3. 读写保持寄存器测试");
Console.WriteLine("4. 读取输入寄存器测试");
Console.WriteLine("5. 浮点数读写测试");
Console.WriteLine("6. 双精度浮点数读写测试");
Console.WriteLine("7. 32位整数读写测试");
Console.WriteLine("8. 字符串读写测试");
Console.WriteLine("9. 批量操作测试");
Console.WriteLine("10. 字节顺序测试");
Console.WriteLine("0. 退出");
Console.Write("\n请选择测试 (0-10): ");
var key = Console.ReadKey(true);
Console.WriteLine(key.KeyChar);
switch (key.KeyChar)
{
case'1':
await TestCoils(client);
break;
case'2':
await TestDiscreteInputs(client);
break;
case'3':
await TestHoldingRegisters(client);
break;
case'4':
await TestInputRegisters(client);
break;
case'5':
await TestFloats(client);
break;
case'6':
await TestDoubles(client);
break;
case'7':
await TestInt32(client);
break;
case'8':
await TestStrings(client);
break;
case'9':
await TestBatchOperations(client);
break;
case'0':
returntrue; // 退出
default:
Console.WriteLine("无效选择,请重试");
break;
}
returnfalse; // 继续显示菜单
}
// 1. 测试线圈读写
private static async Task TestCoils(ModbusTcpClient client)
{
Console.WriteLine("\n== 线圈读写测试 ==");
try
{
// 询问用户输入线圈地址
Console.Write("请输入线圈起始地址 (0-65535): ");
if (!ushort.TryParse(Console.ReadLine(), out ushort coilAddress))
{
Console.WriteLine("无效地址,使用默认地址 100");
coilAddress = 100;
}
// 1.1 读取当前线圈状态
Console.WriteLine($"\n读取线圈 {coilAddress} 的当前状态...");
bool[] initialState = await client.ReadCoilsAsync(SlaveId, coilAddress, 1);
Console.WriteLine($"当前状态: {initialState[0]}");
// 1.2 写入新状态(取反)
bool newState = !initialState[0];
Console.WriteLine($"\n将线圈 {coilAddress} 写入新状态: {newState}");
await client.WriteSingleCoilAsync(SlaveId, coilAddress, newState);
// 1.3 再次读取验证
bool[] updatedState = await client.ReadCoilsAsync(SlaveId, coilAddress, 1);
Console.WriteLine($"更新后状态: {updatedState[0]}");
Console.WriteLine($"验证结果: {(updatedState[0] == newState ? "✓ 成功" : "❌ 失败")}");
// 1.4 测试批量线圈
Console.WriteLine("\n测试批量线圈操作:");
ushort batchStartAddress = (ushort)(coilAddress + 10);
int batchSize = 5;
// 创建测试数据
bool[] coilValues = newbool[batchSize];
for (int i = 0; i < batchSize; i++)
{
coilValues[i] = i % 2 == 0; // 交替设置true/false
}
// 写入批量线圈
Console.WriteLine($"写入 {batchSize} 个线圈,起始地址: {batchStartAddress}");
await client.WriteMultipleCoilsAsync(SlaveId, batchStartAddress, coilValues);
// 读取验证
bool[] readCoilValues = await client.ReadCoilsAsync(SlaveId, batchStartAddress, (ushort)batchSize);
// 验证并显示结果
bool batchSuccess = true;
Console.WriteLine("\n读取结果:");
for (int i = 0; i < batchSize; i++)
{
Console.WriteLine($" 线圈[{batchStartAddress + i}] = {readCoilValues[i]} (期望值: {coilValues[i]})");
if (readCoilValues[i] != coilValues[i])
{
batchSuccess = false;
}
}
Console.WriteLine($"\n批量验证结果: {(batchSuccess ? "✓ 成功" : "❌ 失败")}");
}
catch (Exception ex)
{
Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}");
}
Console.WriteLine("\n按任意键继续...");
Console.ReadKey(true);
}
// 2. 测试离散输入读取
private static async Task TestDiscreteInputs(ModbusTcpClient client)
{
Console.WriteLine("\n== 离散输入读取测试 ==");
try
{
// 询问用户输入离散输入地址
Console.Write("请输入离散输入起始地址 (0-65535): ");
if (!ushort.TryParse(Console.ReadLine(), out ushort inputAddress))
{
Console.WriteLine("无效地址,使用默认地址 100");
inputAddress = 100;
}
// 询问用户输入读取数量
Console.Write("请输入要读取的离散输入数量 (1-100): ");
ushort count;
if (!ushort.TryParse(Console.ReadLine(), out count) || count < 1 || count > 100)
{
Console.WriteLine("无效数量,使用默认值 10");
count = 10;
}
// 读取离散输入
Console.WriteLine($"\n读取 {count} 个离散输入,起始地址: {inputAddress}");
bool[] inputs = await client.ReadDiscreteInputsAsync(SlaveId, inputAddress, count);
// 显示结果
Console.WriteLine("\n读取结果:");
for (int i = 0; i < inputs.Length; i++)
{
Console.WriteLine($" 离散输入[{inputAddress + i}] = {inputs[i]}");
}
Console.WriteLine($"\n成功读取 {inputs.Length} 个离散输入");
}
catch (Exception ex)
{
Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}");
}
Console.WriteLine("\n按任意键继续...");
Console.ReadKey(true);
}
// 3. 测试保持寄存器读写
private static async Task TestHoldingRegisters(ModbusTcpClient client)
{
Console.WriteLine("\n== 保持寄存器读写测试 ==");
try
{
// 询问用户输入寄存器地址
Console.Write("请输入保持寄存器起始地址 (0-65535): ");
if (!ushort.TryParse(Console.ReadLine(), out ushort registerAddress))
{
Console.WriteLine("无效地址,使用默认地址 100");
registerAddress = 100;
}
// 3.1 读取当前寄存器值
Console.WriteLine($"\n读取寄存器 {registerAddress} 的当前值...");
ushort[] initialValue = await client.ReadHoldingRegistersAsync(SlaveId, registerAddress, 1);
Console.WriteLine($"当前值: {initialValue[0]}");
// 3.2 写入新值
ushort newValue = (ushort)(initialValue[0] < 32768 ? initialValue[0] + 1000 : initialValue[0] - 1000);
Console.WriteLine($"\n将寄存器 {registerAddress} 写入新值: {newValue}");
await client.WriteSingleRegisterAsync(SlaveId, registerAddress, newValue);
// 3.3 再次读取验证
ushort[] updatedValue = await client.ReadHoldingRegistersAsync(SlaveId, registerAddress, 1);
Console.WriteLine($"更新后值: {updatedValue[0]}");
Console.WriteLine($"验证结果: {(updatedValue[0] == newValue ? "✓ 成功" : "❌ 失败")}");
// 3.4 测试批量寄存器
Console.WriteLine("\n测试批量寄存器操作:");
ushort batchStartAddress = (ushort)(registerAddress + 10);
int batchSize = 5;
// 创建测试数据
ushort[] registerValues = new ushort[batchSize];
for (int i = 0; i < batchSize; i++)
{
registerValues[i] = (ushort)(1000 + i * 100);
}
// 写入批量寄存器
Console.WriteLine($"写入 {batchSize} 个寄存器,起始地址: {batchStartAddress}");
await client.WriteMultipleRegistersAsync(SlaveId, batchStartAddress, registerValues);
// 读取验证
ushort[] readRegisterValues = await client.ReadHoldingRegistersAsync(SlaveId, batchStartAddress, (ushort)batchSize);
// 验证并显示结果
bool batchSuccess = true;
Console.WriteLine("\n读取结果:");
for (int i = 0; i < batchSize; i++)
{
Console.WriteLine($" 寄存器[{batchStartAddress + i}] = {readRegisterValues[i]} (期望值: {registerValues[i]})");
if (readRegisterValues[i] != registerValues[i])
{
batchSuccess = false;
}
}
Console.WriteLine($"\n批量验证结果: {(batchSuccess ? "✓ 成功" : "❌ 失败")}");
}
catch (Exception ex)
{
Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}");
}
Console.WriteLine("\n按任意键继续...");
Console.ReadKey(true);
}
// 4. 测试输入寄存器读取
private static async Task TestInputRegisters(ModbusTcpClient client)
{
Console.WriteLine("\n== 输入寄存器读取测试 ==");
try
{
// 询问用户输入寄存器地址
Console.Write("请输入输入寄存器起始地址 (0-65535): ");
if (!ushort.TryParse(Console.ReadLine(), out ushort inputRegisterAddress))
{
Console.WriteLine("无效地址,使用默认地址 100");
inputRegisterAddress = 100;
}
// 询问用户输入读取数量
Console.Write("请输入要读取的输入寄存器数量 (1-100): ");
ushort count;
if (!ushort.TryParse(Console.ReadLine(), out count) || count < 1 || count > 100)
{
Console.WriteLine("无效数量,使用默认值 10");
count = 10;
}
// 读取输入寄存器
Console.WriteLine($"\n读取 {count} 个输入寄存器,起始地址: {inputRegisterAddress}");
ushort[] inputRegisters = await client.ReadInputRegistersAsync(SlaveId, inputRegisterAddress, count);
// 显示结果
Console.WriteLine("\n读取结果:");
for (int i = 0; i < inputRegisters.Length; i++)
{
Console.WriteLine($" 输入寄存器[{inputRegisterAddress + i}] = {inputRegisters[i]}");
}
Console.WriteLine($"\n成功读取 {inputRegisters.Length} 个输入寄存器");
}
catch (Exception ex)
{
Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}");
}
Console.WriteLine("\n按任意键继续...");
Console.ReadKey(true);
}
// 5. 测试浮点数读写
private static async Task TestFloats(ModbusTcpClient client)
{
Console.WriteLine("\n== 浮点数读写测试 ==");
try
{
// 询问用户输入起始地址
Console.Write("请输入浮点数起始地址 (0-65534): ");
if (!ushort.TryParse(Console.ReadLine(), out ushort floatAddress))
{
Console.WriteLine("无效地址,使用默认地址 200");
floatAddress = 200;
}
// 询问用户输入浮点数值
Console.Write("请输入要写入的浮点数值: ");
if (!float.TryParse(Console.ReadLine(), out float floatValue))
{
Console.WriteLine("无效值,使用默认值 123.456");
floatValue = 123.456f;
}
// 显示字节顺序选项
Console.WriteLine("\n字节顺序选项:");
Console.WriteLine("1. ABCD (标准顺序)");
Console.WriteLine("2. DCBA (完全倒序)");
Console.WriteLine("3. BADC (交换字顺序)");
Console.WriteLine("4. CDAB (交换字节顺序)");
// 询问用户选择字节顺序
Console.Write("请选择字节顺序 (1-4): ");
ByteOrder byteOrder = ByteOrder.ABCD;
if (int.TryParse(Console.ReadLine(), out int orderChoice) && orderChoice >= 1 && orderChoice <= 4)
{
byteOrder = (ByteOrder)(orderChoice - 1);
}
else
{
Console.WriteLine("无效选择,使用默认字节顺序 ABCD");
}
// 写入浮点数
Console.WriteLine($"\n写入浮点数: 地址={floatAddress}, 值={floatValue}, 字节顺序={byteOrder}");
await ModbusDataHelper.WriteFloatAsync(client, SlaveId, floatAddress, floatValue, byteOrder);
// 读取浮点数
float readValue = await ModbusDataHelper.ReadFloatAsync(client, SlaveId, floatAddress, byteOrder);
Console.WriteLine($"读取的浮点数: {readValue}");
// 验证结果
// 由于浮点数精度问题,使用近似比较
bool success = Math.Abs(floatValue - readValue) < 0.0001;
Console.WriteLine($"验证结果: {(success ? "✓ 成功" : "❌ 失败")}");
Console.WriteLine($"差值: {Math.Abs(floatValue - readValue)}");
// 显示原始寄存器值
ushort[] registers = await client.ReadHoldingRegistersAsync(SlaveId, floatAddress, 2);
Console.WriteLine($"\n原始寄存器值: [{registers[0]}, {registers[1]}]");
}
catch (Exception ex)
{
Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}");
}
Console.WriteLine("\n按任意键继续...");
Console.ReadKey(true);
}
// 6. 测试双精度浮点数读写
private static async Task TestDoubles(ModbusTcpClient client)
{
Console.WriteLine("\n== 双精度浮点数读写测试 ==");
try
{
// 询问用户输入起始地址
Console.Write("请输入双精度浮点数起始地址 (0-65532): ");
if (!ushort.TryParse(Console.ReadLine(), out ushort doubleAddress))
{
Console.WriteLine("无效地址,使用默认地址 300");
doubleAddress = 300;
}
// 询问用户输入双精度浮点数值
Console.Write("请输入要写入的双精度浮点数值: ");
if (!double.TryParse(Console.ReadLine(), out double doubleValue))
{
Console.WriteLine("无效值,使用默认值 123456.789012");
doubleValue = 123456.789012;
}
// 显示字节顺序选项
Console.WriteLine("\n字节顺序选项:");
Console.WriteLine("1. ABCD (标准顺序)");
Console.WriteLine("2. DCBA (完全倒序)");
Console.WriteLine("3. BADC (交换字顺序)");
Console.WriteLine("4. CDAB (交换字节顺序)");
// 询问用户选择字节顺序
Console.Write("请选择字节顺序 (1-4): ");
ByteOrder byteOrder = ByteOrder.ABCD;
if (int.TryParse(Console.ReadLine(), out int orderChoice) && orderChoice >= 1 && orderChoice <= 4)
{
byteOrder = (ByteOrder)(orderChoice - 1);
}
else
{
Console.WriteLine("无效选择,使用默认字节顺序 ABCD");
}
// 写入双精度浮点数
Console.WriteLine($"\n写入双精度浮点数: 地址={doubleAddress}, 值={doubleValue}, 字节顺序={byteOrder}");
await ModbusDataHelper.WriteDoubleAsync(client, SlaveId, doubleAddress, doubleValue, byteOrder);
// 读取双精度浮点数
double readValue = await ModbusDataHelper.ReadDoubleAsync(client, SlaveId, doubleAddress, byteOrder);
Console.WriteLine($"读取的双精度浮点数: {readValue}");
// 验证结果
// 由于浮点数精度问题,使用近似比较
bool success = Math.Abs(doubleValue - readValue) < 0.000001;
Console.WriteLine($"验证结果: {(success ? "✓ 成功" : "❌ 失败")}");
Console.WriteLine($"差值: {Math.Abs(doubleValue - readValue)}");
// 显示原始寄存器值
ushort[] registers = await client.ReadHoldingRegistersAsync(SlaveId, doubleAddress, 4);
Console.WriteLine($"\n原始寄存器值: [{registers[0]}, {registers[1]}, {registers[2]}, {registers[3]}]");
}
catch (Exception ex)
{
Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}");
}
Console.WriteLine("\n按任意键继续...");
Console.ReadKey(true);
}
// 7. 测试32位整数读写
private static async Task TestInt32(ModbusTcpClient client)
{
Console.WriteLine("\n== 32位整数读写测试 ==");
try
{
// 询问用户输入起始地址
Console.Write("请输入32位整数起始地址 (0-65534): ");
if (!ushort.TryParse(Console.ReadLine(), out ushort int32Address))
{
Console.WriteLine("无效地址,使用默认地址 400");
int32Address = 400;
}
// 询问用户输入整数值
Console.Write("请输入要写入的32位整数值: ");
if (!int.TryParse(Console.ReadLine(), out int int32Value))
{
Console.WriteLine("无效值,使用默认值 12345678");
int32Value = 12345678;
}
// 显示字节顺序选项
Console.WriteLine("\n字节顺序选项:");
Console.WriteLine("1. ABCD (标准顺序)");
Console.WriteLine("2. DCBA (完全倒序)");
Console.WriteLine("3. BADC (交换字顺序)");
Console.WriteLine("4. CDAB (交换字节顺序)");
// 询问用户选择字节顺序
Console.Write("请选择字节顺序 (1-4): ");
ByteOrder byteOrder = ByteOrder.ABCD;
if (int.TryParse(Console.ReadLine(), out int orderChoice) && orderChoice >= 1 && orderChoice <= 4)
{
byteOrder = (ByteOrder)(orderChoice - 1);
}
else
{
Console.WriteLine("无效选择,使用默认字节顺序 ABCD");
}
// 写入32位整数
Console.WriteLine($"\n写入32位整数: 地址={int32Address}, 值={int32Value}, 字节顺序={byteOrder}");
await ModbusDataHelper.WriteInt32Async(client, SlaveId, int32Address, int32Value, byteOrder);
// 读取32位整数
int readValue = await ModbusDataHelper.ReadInt32Async(client, SlaveId, int32Address, byteOrder);
Console.WriteLine($"读取的32位整数: {readValue}");
// 验证结果
bool success = int32Value == readValue;
Console.WriteLine($"验证结果: {(success ? "✓ 成功" : "❌ 失败")}");
// 显示原始寄存器值
ushort[] registers = await client.ReadHoldingRegistersAsync(SlaveId, int32Address, 2);
Console.WriteLine($"\n原始寄存器值: [{registers[0]}, {registers[1]}]");
// 测试无符号32位整数
Console.WriteLine("\n\n测试无符号32位整数:");
ushort uint32Address = (ushort)(int32Address + 10);
uint uint32Value = 3000000000; // 大于有符号整数最大值
// 写入无符号32位整数
Console.WriteLine($"写入无符号32位整数: 地址={uint32Address}, 值={uint32Value}, 字节顺序={byteOrder}");
await ModbusDataHelper.WriteUInt32Async(client, SlaveId, uint32Address, uint32Value, byteOrder);
// 读取无符号32位整数
uint readUintValue = await ModbusDataHelper.ReadUInt32Async(client, SlaveId, uint32Address, byteOrder);
Console.WriteLine($"读取的无符号32位整数: {readUintValue}");
// 验证结果
bool uintSuccess = uint32Value == readUintValue;
Console.WriteLine($"验证结果: {(uintSuccess ? "✓ 成功" : "❌ 失败")}");
// 显示原始寄存器值
ushort[] uintRegisters = await client.ReadHoldingRegistersAsync(SlaveId, uint32Address, 2);
Console.WriteLine($"\n原始寄存器值: [{uintRegisters[0]}, {uintRegisters[1]}]");
}
catch (Exception ex)
{
Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}");
}
Console.WriteLine("\n按任意键继续...");
Console.ReadKey(true);
}
// 8. 测试字符串读写
private static async Task TestStrings(ModbusTcpClient client)
{
Console.WriteLine("\n== 字符串读写测试 ==");
try
{
// 询问用户输入起始地址
Console.Write("请输入字符串起始地址 (0-65534): ");
if (!ushort.TryParse(Console.ReadLine(), out ushort stringAddress))
{
Console.WriteLine("无效地址,使用默认地址 500");
stringAddress = 500;
}
// 询问用户输入字符串
Console.Write("请输入要写入的字符串: ");
string stringValue = Console.ReadLine();
if (string.IsNullOrEmpty(stringValue))
{
Console.WriteLine("无效字符串,使用默认值 'Hello Modbus!'");
stringValue = "Hello Modbus!";
}
// 写入字符串
Console.WriteLine($"\n写入字符串: 地址={stringAddress}, 值=\"{stringValue}\"");
await ModbusDataHelper.WriteStringAsync(client, SlaveId, stringAddress, stringValue);
// 读取字符串
string readValue = await ModbusDataHelper.ReadStringAsync(client, SlaveId, stringAddress, stringValue.Length);
Console.WriteLine($"读取的字符串: \"{readValue}\"");
// 验证结果
bool success = stringValue == readValue;
Console.WriteLine($"验证结果: {(success ? "✓ 成功" : "❌ 失败")}");
// 显示原始寄存器值
int registerCount = (int)Math.Ceiling((double)stringValue.Length / 2);
ushort[] registers = await client.ReadHoldingRegistersAsync(SlaveId, stringAddress, (ushort)registerCount);
Console.WriteLine("\n原始寄存器值:");
for (int i = 0; i < registers.Length; i++)
{
Console.WriteLine($" [{i}] = {registers[i]} (十六进制: 0x{registers[i]:X4})");
}
// 显示ASCII值
Console.WriteLine("\nASCII字符表示:");
for (int i = 0; i < registers.Length; i++)
{
char char1 = (char)(registers[i] & 0xFF);
char char2 = (char)(registers[i] >> 8);
if (char1 < 32 || char1 > 126) char1 = '.'; // 非可打印字符显示为.
if (char2 < 32 || char2 > 126) char2 = '.'; // 非可打印字符显示为.
Console.WriteLine($" [{i}] = '{char1}' '{char2}'");
}
}
catch (Exception ex)
{
Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}");
}
Console.WriteLine("\n按任意键继续...");
Console.ReadKey(true);
}
// 9. 测试批量操作
private static async Task TestBatchOperations(ModbusTcpClient client)
{
Console.WriteLine("\n== 批量操作测试 ==");
try
{
// 询问用户输入起始地址
Console.Write("请输入批量操作起始地址 (0-65534): ");
if (!ushort.TryParse(Console.ReadLine(), out ushort startAddress))
{
Console.WriteLine("无效地址,使用默认地址 1000");
startAddress = 1000;
}
// 询问用户输入批量大小
Console.Write("请输入批量大小 (1-100): ");
if (!int.TryParse(Console.ReadLine(), out int batchSize) || batchSize < 1 || batchSize > 100)
{
Console.WriteLine("无效大小,使用默认值 50");
batchSize = 50;
}
// 创建批量数据
ushort[] values = new ushort[batchSize];
for (int i = 0; i < batchSize; i++)
{
values[i] = (ushort)(i * 100 + 1);
}
// 批量写入
Console.WriteLine($"\n批量写入 {batchSize} 个寄存器,起始地址: {startAddress}");
Console.WriteLine("写入值示例: " + string.Join(", ", values.Take(5)) + (batchSize > 5 ? "..." : ""));
// 记录开始时间
var startTime = DateTime.Now;
await client.WriteMultipleRegistersAsync(SlaveId, startAddress, values);
// 计算写入耗时
var writeTime = DateTime.Now - startTime;
Console.WriteLine($"批量写入完成,耗时: {writeTime.TotalMilliseconds:F1} 毫秒");
// 批量读取
Console.WriteLine($"\n批量读取 {batchSize} 个寄存器,起始地址: {startAddress}");
// 记录开始时间
startTime = DateTime.Now;
ushort[] readValues = await client.ReadHoldingRegistersAsync(SlaveId, startAddress, (ushort)batchSize);
// 计算读取耗时
var readTime = DateTime.Now - startTime;
Console.WriteLine($"批量读取完成,耗时: {readTime.TotalMilliseconds:F1} 毫秒");
// 验证结果
bool allMatch = true;
int mismatchCount = 0;
for (int i = 0; i < batchSize; i++)
{
if (values[i] != readValues[i])
{
allMatch = false;
mismatchCount++;
// 只显示前5个不匹配项
if (mismatchCount <= 5)
{
Console.WriteLine($" 不匹配: 索引 {i}, 写入值 {values[i]}, 读取值 {readValues[i]}");
}
}
}
if (mismatchCount > 5)
{
Console.WriteLine($" ... 以及其他 {mismatchCount - 5} 个不匹配项");
}
Console.WriteLine($"\n批量验证结果: {(allMatch ? "✓ 所有数据匹配" : $"❌ 有 {mismatchCount} 个数据不匹配")}");
Console.WriteLine($"读写速率: 写入 {batchSize / writeTime.TotalSeconds:F1} 个/秒,读取 {batchSize / readTime.TotalSeconds:F1} 个/秒");
}
catch (Exception ex)
{
Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}");
}
Console.WriteLine("\n按任意键继续...");
Console.ReadKey(true);
}
// 10. 字节顺序测试
private static async Task TestByteOrder(ModbusTcpClient client)
{
Console.WriteLine("\n== 字节顺序测试 ==");
try
{
// 询问用户输入起始地址
Console.Write("请输入测试起始地址 (0-65534): ");
if (!ushort.TryParse(Console.ReadLine(), out ushort startAddress))
{
Console.WriteLine("无效地址,使用默认地址 600");
startAddress = 600;
}
// 测试值
float testValue = 12345.6789f;
Console.WriteLine($"\n使用测试值 {testValue} 测试所有字节顺序");
// 测试所有字节顺序
foreach (ByteOrder order in Enum.GetValues(typeof(ByteOrder)))
{
ushort address = (ushort)(startAddress + (int)order * 10);
Console.WriteLine($"\n测试字节顺序 {order} (地址: {address}):");
// 写入指定字节顺序的浮点数
await ModbusDataHelper.WriteFloatAsync(client, SlaveId, address, testValue, order);
// 读取相同字节顺序的浮点数
float readValue = await ModbusDataHelper.ReadFloatAsync(client, SlaveId, address, order);
// 读取原始寄存器值
ushort[] registers = await client.ReadHoldingRegistersAsync(SlaveId, address, 2);
// 验证结果
bool success = Math.Abs(testValue - readValue) < 0.0001;
Console.WriteLine($"写入值: {testValue}");
Console.WriteLine($"读取值: {readValue}");
Console.WriteLine($"原始寄存器: [{registers[0]}, {registers[1]}]");
Console.WriteLine($"验证结果: {(success ? "✓ 成功" : "❌ 失败")}");
// 显示不同字节顺序读取的结果
Console.WriteLine("\n使用不同字节顺序读取同一数据:");
foreach (ByteOrder readOrder in Enum.GetValues(typeof(ByteOrder)))
{
if (readOrder != order)
{
float crossValue = await ModbusDataHelper.ReadFloatAsync(client, SlaveId, address, readOrder);
Console.WriteLine($" 使用 {readOrder} 读取: {crossValue}");
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"\n❌ 测试过程中出现错误: {ex.Message}");
}
Console.WriteLine("\n按任意键继续...");
Console.ReadKey(true);
}
}
}
总结
通过以上代码,我们实现了一个简易的 Modbus TCP 客户端,能够与设备进行通信,实现数据的读写