[C#]MODBUS CLASS [0.1]

using System;
using System.IO;
using System.IO.Ports;
using System.Threading.Tasks;

// Modbus RTU 主站类,用于与 Modbus 从站设备进行通信
public class ModbusRTUMaster : IDisposable
{
    // 串口对象,用于实际的串行通信
    private SerialPort _serialPort;
    // 数据缓冲区,用于接收串口数据
    private byte[] _buffer = new byte[256];
    // 缓冲区索引,用于跟踪当前数据位置
    private int _bufferIndex = 0;
    // 锁对象,用于线程安全
    private readonly object _lock = new object();
    // 标记是否已释放资源
    private bool _isDisposed = false;
    //  默认超时时间
    private const int DefaultTimeoutMs = 1000;

    // Modbus 功能码枚举
    public enum FunctionCode : byte
    {
        ReadCoils = 0x01,
        ReadDiscreteInputs = 0x02,
        ReadHoldingRegisters = 0x03,
        ReadInputRegisters = 0x04,
        WriteSingleCoil = 0x05,
        WriteSingleRegister = 0x06,
        WriteMultipleCoils = 0x0F,
        WriteMultipleRegisters = 0x10,

        ExceptionOffset = 0x80
    }

    /// <summary>
    /// 初始化一个新的 Modbus RTU 主站实例
    /// </summary>
    /// <param name="portName">串口号(如 COM1)</param>
    /// <param name="baudRate">波特率</param>
    /// <param name="parity">校验方式</param>
    /// <param name="dataBits">数据位</param>
    /// <param name="stopBits">停止位</param>
    public ModbusRTUMaster(string portName, int baudRate = 9600,
                           Parity parity = Parity.None, int dataBits = 8,
                           StopBits stopBits = StopBits.One)
    {
        // 参数校验
        if (string.IsNullOrEmpty(portName))
        {
            throw new ArgumentException("串口号不能为空", nameof(portName));
        }

        try
        {
            // 初始化串口
            _serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits)
            {
                ReadTimeout = 1000,
                WriteTimeout = 1000,
                Handshake = Handshake.None,
                DtrEnable = false,
                RtsEnable = false
            };

            // 注册数据接收事件处理程序
            _serialPort.DataReceived += OnDataReceived;

            // 自动打开串口
            if (!_serialPort.IsOpen)
            {
                _serialPort.Open();
            }
        }
        catch (UnauthorizedAccessException ex)
        {
            _serialPort = null;
            throw new ArgumentException($"访问被拒绝:{ex.Message}");
        }
        catch (IOException ex)
        {
            _serialPort = null;
            throw new ArgumentException($"I/O 错误:{ex.Message}");
        }
        catch (Exception ex)
        {
            _serialPort = null;
            throw new ArgumentException($"未知错误:{ex.Message}");
        }
    }
    /// <summary>
    /// 获取串口是否已成功打开
    /// </summary>
    public bool IsOpen => _serialPort?.IsOpen ?? false;

    /// <summary>
    /// 关闭串口
    /// </summary>
    public void Close()
    {
        if (_serialPort.IsOpen)
        {
            _serialPort.Close();
        }
    }

    /// <summary>
    /// 清空接收缓冲区
    /// </summary>
    private void ClearBuffer()
    {
        lock (_lock)
        {
            _bufferIndex = 0;
        }
    }

    /// <summary>
    /// 串口数据接收事件处理程序
    /// </summary>
    /// <param name="sender">发送方</param>
    /// <param name="e">事件参数</param>
    private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        lock (_lock)
        {
            while (_serialPort.BytesToRead > 0)
            {
                if (_bufferIndex >= _buffer.Length)
                {
                    _bufferIndex = 0;
                }
                _buffer[_bufferIndex++] = (byte)_serialPort.ReadByte();
            }
        }
    }

    /// <summary>
    /// 构建 Modbus 帧
    /// </summary>
    /// <param name="slaveAddr">从站地址</param>
    /// <param name="funcCode">功能码</param>
    /// <param name="data">数据</param>
    /// <returns>完整的 Modbus 帧</returns>
    private byte[] BuildModbusFrame(byte slaveAddr, byte funcCode, byte[] data)
    {
        byte[] frame = new byte[data.Length + 4];
        frame[0] = slaveAddr;
        frame[1] = funcCode;
        Array.Copy(data, 0, frame, 2, data.Length);

        // 计算CRC
        ushort crc = CalculateCRC16(frame, 0, data.Length + 2);
        frame[frame.Length - 1] = (byte)(crc & 0xFF);
        frame[frame.Length - 2] = (byte)((crc >> 8) & 0xFF);

        return frame;
    }

    /// <summary>
    /// 计算 Modbus 协议使用的 CRC16 校验码(多项式为 0xA001)
    /// </summary>
    /// <param name="data">要计算校验的数据数组</param>
    /// <param name="offset">起始偏移量</param>
    /// <param name="length">数据长度</param>
    /// <returns>计算出的 CRC16 校验值</returns>
    private ushort CalculateCRC16(byte[] data, int offset, int length)
    {
        ushort crc = 0xFFFF;

        for (int i = offset; i < offset + length; i++)
        {
            crc = (ushort)((crc >> 8) ^ CRC16_TABLE[(crc ^ data[i]) & 0xFF]);
        }
        return (ushort)(((byte)(crc & 0xFF) << 8) | (byte)(crc >> 8));
    }
    // 预先生成的CRC16查表(多项式0xA001)
    private static readonly ushort[] CRC16_TABLE = new ushort[256]
    {
        0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
        0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
        0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
        0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
        0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
        0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
        0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
        0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
        0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
        0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
        0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
        0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
        0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
        0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
        0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
        0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
        0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
        0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
        0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
        0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
        0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
        0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
        0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
        0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
        0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
        0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
        0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
        0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
        0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
        0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
        0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
        0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
    };

    /// <summary>
    /// 读取响应数据
    /// </summary>
    /// <param name="expectedLength">预期的响应长度</param>
    /// <returns>响应数据数组</returns>
    private async Task<byte[]> ReadResponseAsync(int expectedLength)
    {
        if (expectedLength <= 0)
            throw new ArgumentException("期望的响应长度必须大于0");

        DateTime startTime = DateTime.Now;

        lock (_lock)
        {
            _bufferIndex = 0;
        }

        while (true)
        {
            lock (_lock)
            {
                if (_bufferIndex >= expectedLength + 2)
                {
                    // 检查CRC
                    ushort crcCalc = CalculateCRC16(_buffer, 0, expectedLength);
                    ushort crcRecv = (ushort)((_buffer[expectedLength] << 8) | _buffer[expectedLength + 1]);

                    if (crcCalc == crcRecv)
                    {
                        byte[] response = new byte[expectedLength + 2];
                        Array.Copy(_buffer, response, expectedLength + 2);
                        ClearBuffer();
                        return response;
                    }
                    else
                    {
                        ClearBuffer(); // CRC 错误,丢弃数据
                        throw new ArgumentException("CRC 校验失败");
                    }
                }
                else if ((DateTime.Now - startTime).TotalMilliseconds > DefaultTimeoutMs)
                {
                    ClearBuffer();
                    throw new ArgumentException("等待 Modbus 响应超时");
                }
            }
            await Task.Delay(1); // 避免CPU占用过高
        }
    }

    /// <summary>
    /// 发送请求并接收响应
    /// </summary>
    /// <param name="request">请求数据</param>
    /// <param name="expectedLength">预期的响应长度</param>
    /// <returns>响应数据数组</returns>
    private async Task<byte[]> SendRequestAsync(byte[] request, int expectedLength)
    {
        ClearBuffer();
        _serialPort.Write(request, 0, request.Length);

        return await ReadResponseAsync(expectedLength);
    }

    /// <summary>
    /// 读取线圈状态(功能码01)
    /// </summary>
    /// <param name="slaveAddr">从站地址</param>
    /// <param name="startAddr">起始线圈地址</param>
    /// <param name="numCoils">线圈数量</param>
    /// <returns>线圈状态数组</returns>
    public async Task<bool[]> ReadCoilsAsync(byte slaveAddr, ushort startAddr, ushort numCoils)
    {
        byte[] data = new byte[4];
        data[0] = (byte)(startAddr >> 8);
        data[1] = (byte)(startAddr & 0xFF);
        data[2] = (byte)(numCoils >> 8);
        data[3] = (byte)(numCoils & 0xFF);

        byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.ReadCoils, data);
        byte[] response = await SendRequestAsync(frame, 3 + (numCoils + 7) / 8);

        // 校验响应是否来自正确的从站
        if (response[0] != slaveAddr)
        {
            throw new InvalidOperationException($"响应来自错误的从站地址: {response[0]}, 期望地址: {slaveAddr}");
        }
        if (response[1] == ((byte)FunctionCode.ReadCoils | (byte)FunctionCode.ExceptionOffset))
        {
            throw new ArgumentException($"Modbus 异常码: {response[2]}");
        }

        int byteCount = response[2];
        bool[] coils = new bool[numCoils];

        for (int i = 0; i < numCoils; i++)
        {
            int byteIndex = i / 8;
            int bitIndex = i % 8;
            if (byteIndex < byteCount)
            {
                coils[i] = (response[3 + byteIndex] & (1 << bitIndex)) != 0;
            }
        }

        return coils;
    }

    /// <summary>
    /// 读取离散输入状态(功能码02)
    /// </summary>
    /// <param name="slaveAddr">从站地址</param>
    /// <param name="startAddr">起始输入地址</param>
    /// <param name="numInputs">输入数量</param>
    /// <returns>离散输入状态数组</returns>
    public async Task<bool[]> ReadDiscreteInputsAsync(byte slaveAddr, ushort startAddr, ushort numInputs)
    {
        byte[] data = new byte[4];
        data[0] = (byte)(startAddr >> 8);
        data[1] = (byte)(startAddr & 0xFF);
        data[2] = (byte)(numInputs >> 8);
        data[3] = (byte)(numInputs & 0xFF);

        byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.ReadDiscreteInputs, data);
        byte[] response = await SendRequestAsync(frame, 3 + (numInputs + 7) / 8);

        // 校验响应是否来自正确的从站
        if (response[0] != slaveAddr)
        {
            throw new InvalidOperationException($"响应来自错误的从站地址: {response[0]}, 期望地址: {slaveAddr}");
        }
        if (response[1] == ((byte)FunctionCode.ReadDiscreteInputs | (byte)FunctionCode.ExceptionOffset))
        {
            throw new ArgumentException($"Modbus 异常码: {response[2]}");
        }

        int byteCount = response[2];
        bool[] inputs = new bool[numInputs];

        for (int i = 0; i < numInputs; i++)
        {
            int byteIndex = i / 8;
            int bitIndex = i % 8;
            if (byteIndex < byteCount)
            {
                inputs[i] = (response[3 + byteIndex] & (1 << bitIndex)) != 0;
            }
        }

        return inputs;
    }

    /// <summary>
    /// 读取保持寄存器(功能码03)
    /// </summary>
    /// <param name="slaveAddr">从机地址</param>
    /// <param name="startAddr">起始寄存器地址</param>
    /// <param name="numRegs">要读取的寄存器数量</param>
    /// <returns>返回读取到的寄存器数据数组</returns>
    /// <exception cref="ArgumentException">如果读取寄存器数量为0,则抛出此异常</exception>
    /// <exception cref="InvalidOperationException">如果响应数据为空、长度不足、包含异常码或数据长度不符合预期,则抛出此异常</exception>
    public async Task<ushort[]> ReadHoldingRegistersAsync(byte slaveAddr, ushort startAddr, ushort numRegs)
    {
        if (numRegs == 0)
            throw new ArgumentException("读取寄存器数量不能为0", nameof(numRegs));

        byte[] requestPayload = new byte[4];
        requestPayload[0] = (byte)(startAddr >> 8);
        requestPayload[1] = (byte)(startAddr & 0xFF);
        requestPayload[2] = (byte)(numRegs >> 8);
        requestPayload[3] = (byte)(numRegs & 0xFF);

        byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.ReadHoldingRegisters, requestPayload);
        byte[] response = await SendRequestAsync(frame, 3 + numRegs * 2);
        // 校验响应是否来自正确的从站
        if (response[0] != slaveAddr)
        {
            throw new InvalidOperationException($"响应来自错误的从站地址: {response[0]}, 期望地址: {slaveAddr}");
        }
        if (response == null || response.Length < 3)
            throw new InvalidOperationException("Modbus响应为空或长度不足");

        if (response[1] == ((byte)FunctionCode.ReadHoldingRegisters | (byte)FunctionCode.ExceptionOffset))
        {
            if (response.Length < 3)
                throw new InvalidOperationException("Modbus异常响应缺少异常码");
            throw new InvalidOperationException($"Modbus 异常码: {response[2]}");
        }

        int expectedDataLength = 3 + numRegs * 2;
        if (response.Length < expectedDataLength)
            throw new InvalidOperationException($"Modbus响应数据长度不足,期望{expectedDataLength}字节,实际{response.Length}字节");

        ushort[] registers = new ushort[numRegs];
        for (int i = 0; i < numRegs; i++)
        {
            int offset = 3 + i * 2;
            registers[i] = (ushort)((response[offset] << 8) | response[offset + 1]);
        }

        return registers;
    }

    /// <summary>
    /// 读取输入寄存器(功能码04)
    /// </summary>
    /// <param name="slaveAttrs">从机地址</param>
    /// <param name="startAddr">起始寄存器地址</param>
    /// <param name="numRegs">要读取的寄存器数量</param>
    /// <returns>返回读取到的寄存器值数组</returns>
    public async Task<ushort[]> ReadInputRegistersAsync(byte slaveAddr, ushort startAddr, ushort numRegs)
    {
        // 参数合法性检查
        if (numRegs == 0 || numRegs > 0x7D)
            throw new ArgumentException("读取寄存器数量必须在 1 到 125 之间", nameof(numRegs));

        byte[] data = new byte[4];
        data[0] = (byte)(startAddr >> 8);
        data[1] = (byte)(startAddr & 0xFF);
        data[2] = (byte)(numRegs >> 8);
        data[3] = (byte)(numRegs & 0xFF);

        byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.ReadInputRegisters, data);
        byte[] response = await SendRequestAsync(frame, 3 + numRegs * 2);

        // 检查响应有效性
        if (response == null || response.Length < 3)
            throw new InvalidOperationException("无效的 Modbus 响应");
        // 校验响应是否来自正确的从站
        if (response[0] != slaveAddr)
        {
            throw new InvalidOperationException($"响应来自错误的从站地址: {response[0]}, 期望地址: {slaveAddr}");
        }
        // 异常码处理
        if (response[1] == ((byte)FunctionCode.ReadInputRegisters | (byte)FunctionCode.ExceptionOffset))
        {
            throw new ArgumentException($"Modbus 异常码: {response[2]}");
        }
        // 校验响应数据长度
        if (response.Length < 3 + response[2])
            throw new InvalidOperationException("响应数据长度不足");

        // 确保返回的数据长度匹配
        if (response[2] != numRegs * 2)
            throw new InvalidOperationException("响应寄存器数量不匹配");

        // 使用 BlockCopy 提高性能
        ushort[] registers = new ushort[numRegs];
        Buffer.BlockCopy(response, 3, registers, 0, numRegs * 2);

        return registers;
    }

    /// <summary>
    /// 写单个线圈(功能码05)
    /// </summary>
    /// <param name="slaveAddr">从站地址</param>
    /// <param name="coilAddr">线圈地址</param>
    /// <param name="value">要写入的值(true 表示 ON,false 表示 OFF)</param>
    /// <returns>如果写入成功返回 true</returns>
    /// <exception cref="ArgumentException">当从站地址非法时抛出</exception>
    /// <exception cref="InvalidOperationException">当 Modbus 帧构建失败或写入失败时抛出</exception>
    public async Task<bool> WriteSingleCoilAsync(byte slaveAddr, ushort coilAddr, bool value)
    {
        // 参数合法性检查
        if (slaveAddr < 1 || slaveAddr > 247)
            throw new ArgumentException("从站地址必须在 1~247 范围内", nameof(slaveAddr));

        // 构建请求数据部分:4字节
        byte[] data = new byte[4];
        data[0] = (byte)(coilAddr >> 8);
        data[1] = (byte)(coilAddr & 0xFF);
        data[2] = (byte)(value ? 0xFF : 0x00);
        data[3] = 0x00;

        // 构建 Modbus 请求帧
        byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.WriteSingleCoil, data);
        if (frame == null)
            throw new InvalidOperationException("构建 Modbus 帧失败");

        // 发送请求并获取响应
        byte[] response = await SendRequestAsync(frame, 6);

        // 校验响应是否匹配预期:
        // - 非空且长度足够
        // - 地址一致
        // - 功能码一致
        // - 返回的地址与请求一致
        if (response != null &&
            response.Length >= 6 &&
            response[0] == slaveAddr &&
            response[1] == (byte)FunctionCode.WriteSingleCoil &&
            response[2] == data[0] &&
            response[3] == data[1])
        {
            return true;
        }

        // 若未满足条件,抛出写入失败异常
        throw new InvalidOperationException($"写入线圈失败: 从站={slaveAddr}, 地址={coilAddr}, 值={value}");
    }

    /// <summary>
    /// 写单个寄存器(功能码06)
    /// </summary>
    /// <param name="slaveAddr">从设备地址</param>
    /// <param name="regAddr">寄存器地址</param>
    /// <param name="value">要写入的值</param>
    /// <returns>写入成功返回true,否则抛出异常</returns>
    /// <exception cref="InvalidOperationException">抛出当Modbus帧构建失败、响应为空或长度不足、或写入寄存器失败时</exception>
    public async Task<bool> WriteSingleRegisterAsync(byte slaveAddr, ushort regAddr, ushort value)
    {
        // 准备数据数组,包含寄存器地址和要写入的值
        byte[] data = new byte[4];
        data[0] = (byte)(regAddr >> 8);
        data[1] = (byte)(regAddr & 0xFF);
        data[2] = (byte)(value >> 8);
        data[3] = (byte)(value & 0xFF);

        // 构建Modbus帧
        byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.WriteSingleRegister, data);
        if (frame == null)
        {
            throw new InvalidOperationException("Modbus帧构建失败");
        }
        // 发送请求并接收响应
        byte[] response = await SendRequestAsync(frame, 6);
        if (response == null || response.Length < 6)
        {
            throw new InvalidOperationException("Modbus响应为空或长度不足");
        }
        // 可选:检查从站地址是否一致
        // 校验响应是否来自正确的从站
        if (response[0] != slaveAddr)
        {
            throw new InvalidOperationException($"响应来自错误的从站地址: {response[0]}, 期望地址: {slaveAddr}");
        }
        // 验证响应是否与请求一致
        if (response[1] == (byte)FunctionCode.WriteSingleRegister &&
            response[2] == data[0] &&
            response[3] == data[1])
        {
            return true;
        }

        // 如果写入失败,抛出异常
        throw new InvalidOperationException("写入寄存器失败");
    }

    /// <summary>
    /// 写多个线圈(功能码15)
    /// </summary>
    /// <param name="slaveAddr">从站地址</param>
    /// <param name="startAddr">起始线圈地址</param>
    /// <param name="values">要写入的布尔数组(true 表示 ON,false 表示 OFF)</param>
    /// <returns>是否写入成功</returns>
    public async Task<bool> WriteMultipleCoilsAsync(byte slaveAddr, ushort startAddr, bool[] values)
    {
        // 参数校验
        if (values == null)
            throw new ArgumentNullException(nameof(values));

        if (values.Length == 0 || values.Length > 255) // Modbus 最大支持线圈数
            throw new ArgumentException("线圈数量必须在1到255之间", nameof(values));

        int byteCount = (values.Length + 7) / 8;
        byte[] data = new byte[5 + byteCount];

        // 填充数据字段
        data[0] = (byte)(startAddr >> 8);
        data[1] = (byte)(startAddr & 0xFF);
        data[2] = (byte)(values.Length >> 8);
        data[3] = (byte)(values.Length & 0xFF);
        data[4] = (byte)byteCount;

        for (int i = 0; i < values.Length; i++)
        {
            if (values[i])
            {
                data[5 + i / 8] |= (byte)(1 << (i % 8));
            }
        }

        try
        {
            byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.WriteMultipleCoils, data);
            byte[] response = await SendRequestAsync(frame, 6);

            // 提前保存用于比较的字段
            byte expectedStartAddrHigh = data[0];
            byte expectedStartAddrLow = data[1];
            byte expectedQuantityHigh = data[2];

            // 验证响应
            if (response != null && response.Length >= 5 &&
                response[0] == slaveAddr && // 确保响应来自正确的从站
                response[1] == (byte)FunctionCode.WriteMultipleCoils &&
                response[2] == expectedStartAddrHigh &&
                response[3] == expectedStartAddrLow &&
                response[4] == expectedQuantityHigh)
            {
                return true;
            }

            throw new ArgumentException("写入多个线圈失败");
        }
        catch (Exception ex)
        {
            // 可选记录日志
            // Logger.Error("Modbus写入线圈异常: ", ex);
            throw new ArgumentException("写入多个线圈失败", ex);
        }
    }

    /// <summary>
    /// 写多个寄存器(功能码16)
    /// </summary>
    /// <param name="slaveAddr">从机地址。</param>
    /// <param name="startAddr">起始寄存器地址。</param>
    /// <param name="values">要写入的寄存器值数组。</param>
    /// <returns>如果写入成功,则返回true;否则抛出ArgumentException异常。</returns>
    public async Task<bool> WriteMultipleRegistersAsync(byte slaveAddr, ushort startAddr, ushort[] values)
    {
        // 准备数据数组,包括起始地址、寄存器数量和所有寄存器的值
        byte[] data = new byte[5 + values.Length * 2];
        // 设置起始地址的高字节和低字节
        data[0] = (byte)(startAddr >> 8);
        data[1] = (byte)(startAddr & 0xFF);
        // 设置寄存器数量的高字节和低字节
        data[2] = (byte)(values.Length >> 8);
        data[3] = (byte)(values.Length & 0xFF);
        // 设置所有寄存器值的总字节数
        data[4] = (byte)(values.Length * 2);

        // 遍历每个寄存器值,将其拆分为高字节和低字节,并存入数据数组
        for (int i = 0; i < values.Length; i++)
        {
            data[5 + i * 2] = (byte)(values[i] >> 8);
            data[6 + i * 2] = (byte)(values[i] & 0xFF);
        }

        // 构建Modbus帧并发送请求
        byte[] frame = BuildModbusFrame(slaveAddr, (byte)FunctionCode.WriteMultipleRegisters, data);
        byte[] response = await SendRequestAsync(frame, 6);

        // 验证响应数据,确保写入操作成功
        if (response != null &&
            response.Length >= 5 &&
            response[1] == (byte)FunctionCode.WriteMultipleRegisters &&
            response[2] == data[0] &&
            response[3] == data[1] &&
            response[4] == data[2])
        {
            return true;
        }
        // 如果写入失败,抛出异常
        throw new ArgumentException("写入多个寄存器失败");
    }

    #region IDisposable Support
    /// <summary>
    /// 释放类的资源。
    /// </summary>
    /// <param name="disposing">指示是否应释放托管资源。如果为 true,则释放;否则,不释放。</param>
    protected virtual void Dispose(bool disposing)
    {
        if (!_isDisposed)
        {
            if (disposing)
            {
                // 取消事件订阅并释放 SerialPort 资源
                _serialPort.DataReceived -= OnDataReceived;
                _serialPort.Dispose();
            }

            _isDisposed = true;
        }
    }

    /// <summary>
    /// 实现 IDisposable 接口,提供标准的释放资源方法。
    /// </summary>
    public void Dispose()
    {
        // 调用重载的 Dispose 方法,并指示应释放托管资源
        Dispose(true);
        // 告诉垃圾回收器不再需要调用终结器(析构器)
        GC.SuppressFinalize(this);
    }
    #endregion
}

我辅助AI完成,并验证单机通信通过。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值