一文读懂Modbus数据处理实战:C#代码实例解析与完整方案

什么是 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 客户端,能够与设备进行通信,实现数据的读写

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值