用c# 自己封装的Modbus工具类库源码

前言

Modbus通讯协议在工控行业的应用是很多的,并且也是上位机开发的基本技能之一。相关的类库也很多也很好用。以前只负责用,对其并没有深入学习和了解。前段时间有点空就在这块挖了挖。想做到知其然还要知其所以然。所以就有了自己封装的Modbus工具类库的想法。一来是练练手,二来是自己封装的用的更顺手。

Modbus通讯协议我在工作中目前只用到了两种一个是串口通讯ModbusRTU,还有一个是网络通讯ModbusTcp。所以本文只有这两种通讯的实现。

设计思想

C#是高级语言有很多好用的东西,如面像对像,设计模式等。但我在工作中还是经常看到面像过程的编程。如有多个串口设备就有多个代码类似的工具类。代码重复非常严重。我认为这种事还是要发点时间总结和代码一下代码,把它封装工具类库。以便后继在其他上的使用。

本次的封装用了一点面像对像的方法,设计了一个多个Modbus 基类将一些公共方法放在基类中,子类就可以继续使用。不同的子类有不同的功能。可以按需调用。使用简单方便。

调用示例

cs 复制代码
var _serialPort = new ModbusRTUCoil(portName, baudRate, parity, dataBits, stopBits);
var isOk = false;
var resultModel = _serialPort.ReadDataCoil(1, 1, ModbusFunctionCode.ReadInputCoil, (ushort)type.GetHashCode());
if (resultModel.ResultList != null && resultModel.ResultList.Count > 0)
{
    isOk = resultModel.ResultList.FirstOrDefault();
}

类库项目结构

代码

Modbus结果实体

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CJH.ModbusTool
{
    /// <summary>
    /// Modbus结果实体
    /// </summary>
    /// <typeparam name="DateType"></typeparam>
    public class ModbusResultModel
    {
        public ModbusResultModel()
        {
            IsSucceed = false;
            Msg = "失败(默认)";
        }

        private bool _isSucceed = false;
        /// <summary>
        /// 是否成功
        /// </summary>
        public bool IsSucceed
        {
            get
            {
                return _isSucceed;
            }
            set
            {
                _isSucceed = value;
                if (IsSucceed)
                {
                    Msg = "成功";
                }
            }
        }

        /// <summary>
        /// 返回消息
        /// </summary>
        public string Msg { get; set; }

        /// <summary>
        /// 发送报文
        /// </summary>
        public string SendDataStr { get; set; }

        /// <summary>
        /// 原始数据
        /// </summary>
        public byte[] Datas { get; set; }
    }

    /// <summary>
    /// Modbus结果实体
    /// </summary>
    /// <typeparam name="DateType"></typeparam>
    public class ModbusResultModel<DateType> : ModbusResultModel
    {
        public ModbusResultModel() : base()
        {
            ResultList = new List<DateType>();
        }

        /// <summary>
        /// 解析后的数据
        /// </summary>
        public List<DateType> ResultList { get; set; }
    }
}

Modbus 基类

cs 复制代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CJH.ModbusTool
{
    /// <summary>
    /// Modbus 基类
    /// </summary>
    public abstract class ModbusBase
    {
        /// <summary>
        /// 生成读取报文的 公共方法
        /// </summary>
        /// <param name="devAddr">从站地址</param>
        /// <param name="length">寄存器数量</param>
        /// <param name="functionCode">功能码</param>
        /// <param name="startAddr">起始寄存器地址</param>
        /// <returns>返回报文(协议格式:站地址+功能码+起始寄存器地址+寄存器数量)</returns>
        protected byte[] GenerateReadCommandBytes(byte devAddr, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0)
        {
            //1.拼接报文:
            var sendCommand = new List<byte>();

            //协议格式:站地址+功能码+起始寄存器地址+寄存器数量
            //站地址
            sendCommand.Add(devAddr);
            //功能码
            sendCommand.Add((byte)functionCode.GetHashCode());
            //起始寄存器地址
            sendCommand.Add((byte)(startAddr / 256));
            sendCommand.Add((byte)(startAddr % 256));
            //寄存器数量
            sendCommand.Add((byte)(length / 256));
            sendCommand.Add((byte)(length % 256));
            //CRC
            //byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);
            //sendCommand.AddRange(crc);

            return sendCommand.ToArray();
        }

        /// <summary>
        /// 生成读取报文的 公共方法
        /// </summary>
        /// <param name="devAddr">从站地址</param>
        /// <param name="data">定入数据</param>
        /// <param name="functionCode">功能码</param>
        /// <param name="startAddr">写入地址</param>
        /// <returns>返回报文(协议格式:站地址+功能码+起始寄存器地址+寄存器数量)</returns>
        protected byte[] GenerateWriteCommandBytes(byte devAddr, ushort data, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0)
        {
            //1.拼接报文:
            var sendCommand = new List<byte>();

            //协议格式:站地址+功能码+起始寄存器地址+寄存器数量
            //站地址
            sendCommand.Add(devAddr);
            //功能码
            sendCommand.Add((byte)functionCode.GetHashCode());
            //写入地址
            sendCommand.Add((byte)(startAddr / 256));
            sendCommand.Add((byte)(startAddr % 256));
            //写入数据
            var temp_bytes = BitConverter.GetBytes(data);
            if (BitConverter.IsLittleEndian)
            {
                //temp_bytes.Reverse();
                Array.Reverse(temp_bytes);
            }
            sendCommand.AddRange(temp_bytes);
            //CRC
            //byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);
            //sendCommand.AddRange(crc);

            return sendCommand.ToArray();
        }

        /// <summary>
        /// 生成发送命令报文
        /// </summary>
        /// <param name="sendCommand"></param>
        /// <returns></returns>
        protected string generateSendCommandStr(byte[] sendCommand)
        {
            var sendCommandStr = string.Empty;
            foreach (var item in sendCommand)
            {
                sendCommandStr += Convert.ToString(item, 16) + " ";
            }
            return sendCommandStr;
        }

        /// <summary>
        /// 验证CRC
        /// </summary>
        /// <param name="value">要验证的数据</param>
        /// <returns></returns>
        protected bool CheckCRC(byte[] value)
        {
            var isOk = false;
            if (value != null && value.Length >= 2)
            {
                int length = value.Length;
                byte[] buf = new byte[length - 2];
                Array.Copy(value, 0, buf, 0, buf.Length);

                //自己验证的结果
                byte[] CRCbuf = Crc16(buf, buf.Length);
                //把上面验证的结果和串口返回的校验码(最后两个)进行比较
                if (CRCbuf[0] == value[length - 2] && CRCbuf[1] == value[length - 1])
                {
                    isOk = true;
                }
            }
            return isOk;
        }

        protected byte[] Crc16(byte[] pucFrame, int usLen)
        {
            int i = 0;
            byte[] res = new byte[2] { 0xFF, 0xFF };
            ushort iIndex;
            while (usLen-- > 0)
            {
                iIndex = (ushort)(res[0] ^ pucFrame[i++]);
                res[0] = (byte)(res[1] ^ aucCRCHi[iIndex]);
                res[1] = aucCRCLo[iIndex];
            }
            return res;
        }

        protected readonly byte[] aucCRCHi = {
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
             0x00, 0xC1, 0x81, 0x40
         };

        protected readonly byte[] aucCRCLo = {
             0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
             0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
             0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
             0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
             0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
             0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
             0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
             0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
             0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
             0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
             0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
             0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
             0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
             0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
             0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
             0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
             0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
             0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
             0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
             0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
             0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
             0x41, 0x81, 0x80, 0x40
         };

        /// <summary>
        /// CRC校验
        /// </summary>
        /// <param name="pucFrame">字节数组</param>
        /// <param name="usLen">验证长度</param>
        /// <returns>2个字节</returns>
        protected byte[] CalculateCRC(byte[] pucFrame, int usLen)
        {
            int i = 0;
            byte[] res = new byte[2] { 0xFF, 0xFF };
            ushort iIndex;
            while (usLen-- > 0)
            {
                iIndex = (ushort)(res[0] ^ pucFrame[i++]);
                res[0] = (byte)(res[1] ^ aucCRCHi[iIndex]);
                res[1] = aucCRCLo[iIndex];
            }
            return res;
        }
    }

    /// <summary>
    /// Modbus 功能码
    /// </summary>
    public enum ModbusFunctionCode
    {
        /// <summary>
        /// 读取输出线圈
        /// </summary>
        [Description("读取输出线圈")]
        ReadOutCoil = 1,

        /// <summary>
        /// 读取输入线圈
        /// </summary>
        [Description("读取输入线圈")]
        ReadInputCoil = 2,

        /// <summary>
        /// 读取保持寄存器
        /// </summary>
        [Description("读取保持寄存器")]
        ReadRegister = 3,

        /// <summary>
        /// 读取输入寄存器
        /// </summary>
        [Description("读取输入寄存器")]
        ReadInputRegister = 4,

        /// <summary>
        /// (写入)预置单线圈
        /// </summary>
        [Description("(写入)预置单线圈")]
        WriteCoil = 5,

        /// <summary>
        /// (写入)预置单个寄存器
        /// </summary>
        [Description("(写入)预置单个寄存器")]
        WriteRegister = 6,

        /// <summary>
        /// (写入)预置多寄存器
        /// </summary>
        [Description("(写入)预置多寄存器")]
        WriteRegisterMultiple = 16,
    }
}

RTU

串口基类 SerialPortBase

cs 复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CJH.ModbusTool.RTU
{
    //Modbus 规定4个存储区
    // 区号     名称     读写        范围
    // 0区     输出线圈  可读可写    00001-09999
    // 1区     输入线圈  只读        10001-19999
    // 2区     输入寄存器  只读      30001-39999
    // 4区     保存寄存器  可读可写  40001-19999

    //功能码
    //01H     读取输出线圈
    //02H     读取输入线圈
    //03H     读取保持寄存器
    //04H     读取输入寄存器
    //05H     (写入)预置单线圈
    //06H     (写入)预置寄存器
    //0FH     (写入)预置多线圈
    //10H     (写入)预置多寄存器

    /// <summary>
    /// 串口基类
    /// </summary>
    public abstract class SerialPortBase : ModbusBase
    {
        protected SerialPort SerialPortObj;

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="portName">COM口名称</param>
        /// <param name="baudRate">波特率</param>
        /// <param name="parity">检验位</param>
        /// <param name="dataBits">数据位</param>
        /// <param name="stopBits">停止位</param>
        protected void Init(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
        {
            SerialPortObj = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
            if (SerialPortObj.IsOpen)
            {
                SerialPortObj.Close();
            }
            SerialPortObj.Open();
        }        

        /// <summary>
        /// 关闭
        /// </summary>
        public void Close()
        {
            if (SerialPortObj.IsOpen)
            {
                SerialPortObj.Close();
                SerialPortObj.Dispose();
                SerialPortObj = null;
            }
        }
    }

    //功能码
    //01H     读取输出线圈
    //02H     读取输入线圈
    //03H     读取保持寄存器
    //04H     读取输入寄存器
    //05H     (写入)预置单线圈
    //06H     (写入)预置寄存器
    //0FH     (写入)预置多线圈
    //10H     (写入)预置多寄存器

    
}

Modbus 串口通讯

(串口操作的所有功能这个类都能做)

cs 复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CJH.ModbusTool.RTU
{
    /// <summary>
    /// Modbus 串口通讯
    /// </summary>
    public class ModbusRTU : SerialPortBase
    {
        private string _className = "ModbusRTU";

        /// <summary>
        /// Modbus 串口通讯
        /// </summary>
        /// <param name="portName">COM口名称</param>
        /// <param name="baudRate">波特率</param>
        /// <param name="parity">检验位</param>
        /// <param name="dataBits">数据位</param>
        /// <param name="stopBits">停止位</param>
        public ModbusRTU(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
        {
            Init(portName, baudRate, parity, dataBits, stopBits);
            //SerialPortObj.DataReceived += new SerialDataReceivedEventHandler(ComDataReceived);
        }

        /// <summary>
        /// 读取线圈数据 ok
        /// </summary>
        /// <param name="devAddr">从站地址</param>
        /// <param name="length">寄存器数量</param>
        /// <param name="functionCode">功能码</param>
        /// <param name="startAddr">起始寄存器地址</param>
        /// <returns>线圈数据</returns>
        public ModbusResultModel ReadData(byte devAddr, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0)
        {
            return ReadData(devAddr, length, (byte)functionCode, startAddr);
        }

        /// <summary>
        /// 读取数据 ok
        /// </summary>
        /// <param name="devAddr">从站地址</param>
        /// <param name="length">寄存器数量</param>
        /// <param name="functionCode">功能码</param>
        /// <param name="startAddr">起始寄存器地址</param>
        /// <returns>线圈数据</returns>
        public ModbusResultModel ReadData(byte devAddr, ushort length, byte functionCode = 2, ushort startAddr = 0)
        {
            var resultModel = new ModbusResultModel();
            //byte[] datas = null;
            if (functionCode >= 1 && functionCode <= 4)
            {
                try
                {
                    //1.拼接报文:
                    var sendCommand = new List<byte>();

                    //协议格式:站地址+功能码+起始寄存器地址+寄存器数量+CRC
                    //站地址
                    sendCommand.Add(devAddr);
                    //功能码
                    sendCommand.Add(functionCode);
                    //起始寄存器地址
                    sendCommand.Add((byte)(startAddr / 256));
                    sendCommand.Add((byte)(startAddr % 256));
                    //寄存器数量
                    sendCommand.Add((byte)(length / 256));
                    sendCommand.Add((byte)(length % 256));
                    //CRC
                    byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);
                    sendCommand.AddRange(crc);

                    resultModel.SendDataStr = generateSendCommandStr(sendCommand.ToArray());
                    //2.发送报文
                    SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Count);
                    //3.接收报文
                    Thread.Sleep(50);//要延时一下,才能读到数据

                    //读取响应报文
                    byte[] respBytes = new byte[SerialPortObj.BytesToRead];
                    SerialPortObj.Read(respBytes, 0, respBytes.Length);
                    // respBytes -> 01 01 02 00 00 B9 FC
                    resultModel.Datas = respBytes;
                    // 检查一个校验位
                    //if (CheckCRC(respBytes) && (respBytes.Length == 5 + length * 2) 
                    //    && respBytes[0] == devAdd && respBytes[1] == functionCode && respBytes[1] == length * 2)
                    if (CheckCRC(respBytes) && respBytes[0] == devAddr && respBytes[1] == functionCode)
                    {
                        //datas = respBytes;
                        resultModel.IsSucceed = true;
                    }
                    else
                    {
                        resultModel.Msg = "响应报文校验失败";
                    }
                }
                catch (Exception ex)
                {
                    resultModel.Msg = "异常:" + ex.Message;
                }
            }
            else
            {
                //throw new Exception("功能码不正确[1-4]");
                resultModel.Msg = "功能码不正确[1-4]";
            }
            //SerialPortObj.Close();
            return resultModel;
        }

        /// <summary>
        /// 写入单个寄存器 ok
        /// 数据示例: 200
        /// 功能码 6
        /// </summary>
        /// <param name="devAddr">从站地址</param>
        /// <param name="value">写入的数据</param>
        /// <param name="startAddr">写入地址</param>
        /// <returns>是否成功</returns>
        public ModbusResultModel WriteDataShort(int devAddr, short value, short startAddr = 0)
        {
            var resultModel = new ModbusResultModel();
            try
            {
                //bool isOk = false;
                //1.拼接报文:
                //var sendCommand = GetSingleDataWriteMessage(devAdd, startAddr, value); //ok
                var sendCommand = GetSingleDataWriteMessageList(devAddr, startAddr, value); //ok
                                                                                           //var sendCommandStr = string.Join(' ', sendCommand.ToArray());
                resultModel.SendDataStr = generateSendCommandStr(sendCommand);

                //2.发送报文
                SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Length);
                //3.接收报文
                Thread.Sleep(50);//要延时一下,才能读到数据

                //读取响应报文
                byte[] respBytes = new byte[SerialPortObj.BytesToRead];
                SerialPortObj.Read(respBytes, 0, respBytes.Length);
                // respBytes -> 01 01 02 00 00 B9 FC
                // 检查一个校验位
                if (CheckCRC(respBytes) && respBytes[0] == devAddr && respBytes[1] == 0x06)
                {
                    //isOk = true;
                    resultModel.IsSucceed = true;
                }
                else
                {
                    resultModel.Msg = "响应报文校验失败";
                }
            }
            catch (Exception ex)
            {
                resultModel.Msg = "异常:" + ex.Message;
            }
            //SerialPortObj.Close();
            return resultModel;
        }

        /// <summary>
        /// 写入单个寄存器 ok
        /// 数据示例: 200
        /// 功能码 6
        /// </summary>
        /// <param name="devAddr">站地址</param>
        /// <param name="dataList">写入的数据集合</param>
        /// <param name="startAddr">写入地址</param>
        /// <returns>是否成功</returns>
        public ModbusResultModel WriteDataShort(int devAddr, List<short> dataList, short startAddr = 0)
        {
            var resultModel = new ModbusResultModel();
            if (dataList != null && dataList.Count > 0)
            {
                foreach (var item in dataList)
                {
                    resultModel = WriteDataShort(devAddr, item, startAddr);
                    startAddr++;
                }
            }
            return resultModel;
        }

        /// <summary>
        /// 获取写入单个寄存器的报文
        /// </summary>
        /// <param name="slaveStation">从站地址</param>
        /// <param name="startAddr">寄存器地址</param>
        /// <param name="value">写入值</param>
        /// <returns>写入单个寄存器的报文</returns>
        private byte[] GetSingleDataWriteMessage(int slaveStation, short startAddr, short value)
        {
            //从站地址
            byte station = (byte)slaveStation;
            //功能码
            byte type = 0x06;//06H     (写入)预置寄存器
            //寄存器地址
            byte[] start = BitConverter.GetBytes(startAddr);
            //值
            byte[] valueBytes = BitConverter.GetBytes(value);
            //根据计算机大小端存储方式进行高低字节转换
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(start);
                Array.Reverse(valueBytes);
            }
            //拼接报文
            byte[] result = new byte[] { station, type };
            result = result.Concat(start.Concat(valueBytes).ToArray()).ToArray();

            //计算校验码并拼接,返回最后的报文结果
            return result.Concat(Crc16(result, result.Length)).ToArray();
        }

        /// <summary>
        /// 获取写入单个寄存器的报文
        /// </summary>
        /// <param name="slaveStation">从站地址</param>
        /// <param name="startAddr">寄存器地址</param>
        /// <param name="data">写入值</param>
        /// <returns>写入单个寄存器的报文</returns>
        private byte[] GetSingleDataWriteMessageList(int slaveStation, short startAddr, short data)
        {
            //1.拼接报文:
            var sendCommand = new List<byte>();
            //从站地址
            byte station = (byte)slaveStation;
            //功能码
            byte type = 0x06;//06H     (写入)预置寄存器            
            //寄存器地址
            byte[] start = BitConverter.GetBytes(startAddr);
            //值
            byte[] valueBytes = BitConverter.GetBytes(data);
            //根据计算机大小端存储方式进行高低字节转换
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(start);
                Array.Reverse(valueBytes);
            }
            sendCommand.Add((byte)slaveStation);
            sendCommand.Add(type);
            sendCommand.AddRange(start);
            sendCommand.AddRange(valueBytes);

            byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);
            sendCommand.AddRange(crc);
            return sendCommand.ToArray();
        }

        /// <summary>
        /// 写入多个寄存器 ok
        /// 数据示例: 123.45f, 14.3f
        /// 功能码 10 
        /// </summary>
        /// <param name="devAddr">从站地址</param>
        /// <param name="data">写入的数据</param>
        /// <param name="startAddr">写入地址</param>
        /// <returns>是否成功</returns>
        public ModbusResultModel WriteDataFloat(byte devAddr, float data, ushort startAddr = 0)
        {
            return WriteDataFloat(devAddr, new List<float>() { data }, startAddr);
        }

        /// <summary>
        /// 写入多个寄存器 ok
        /// 数据示例: 123.45f, 14.3f
        /// 功能码 10 
        /// </summary>
        /// <param name="devAdd">从站地址</param>
        /// <param name="dataList">写入的数据</param>
        /// <param name="startAddr">写入地址</param>
        /// <returns>是否成功</returns>
        public ModbusResultModel WriteDataFloat(byte devAddr, List<float> dataList, ushort startAddr = 0)
        {
            var resultModel = new ModbusResultModel();
            if (dataList != null && dataList.Count > 0)
            {
                try
                {
                    byte functionCode = (byte)ModbusFunctionCode.WriteRegisterMultiple.GetHashCode();
                    int length = dataList.Count * 2;
                    //1.拼接报文:
                    var sendCommand = new List<byte>();

                    //协议格式:站地址+功能码+起始寄存器地址+寄存器数量+CRC
                    //站地址
                    sendCommand.Add(devAddr);
                    //功能码
                    sendCommand.Add(functionCode);
                    //写入地址
                    sendCommand.Add((byte)(startAddr / 256));
                    sendCommand.Add((byte)(startAddr % 256));
                    //寄存器数量
                    sendCommand.Add((byte)(length / 256));
                    sendCommand.Add((byte)(length % 256));
                    // 获取数值的byte[]
                    List<byte> valueBytes = new List<byte>();
                    foreach (var data in dataList)
                    {
                        List<byte> temp = new List<byte>(BitConverter.GetBytes(data));
                        temp.Reverse();// 调整字节序
                        valueBytes.AddRange(temp);
                    }
                    // 字节数
                    sendCommand.Add((byte)valueBytes.Count);
                    sendCommand.AddRange(valueBytes);
                    //CRC
                    byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);
                    sendCommand.AddRange(crc);

                    //000004 - Rx:01 10 00 02 00 04 08 42 F6 E6 66 41 64 CC CD 83 23
                    //000005 - Tx:01 10 00 02 00 04 60 0A
                    //000006 - Rx:01 0A 00 02 00 04 08 42 F6 E6 66 41 64 CC CD 98 F9
                    //000007 - Tx:01 8A 01 86 A0 //报错了

                    //2.发送报文
                    SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Count);
                    //3.接收报文
                    Thread.Sleep(50);//要延时一下,才能读到数据

                    //读取响应报文
                    byte[] respBytes = new byte[SerialPortObj.BytesToRead];
                    SerialPortObj.Read(respBytes, 0, respBytes.Length);
                    // respBytes -> 01 01 02 00 00 B9 FC

                    // 检查一个校验位
                    if (CheckCRC(respBytes) && respBytes[0] == devAddr && respBytes[1] == functionCode)
                    {
                        resultModel.IsSucceed = true;
                    }
                    else
                    {
                        resultModel.Msg = "响应报文校验失败";
                    }
                }
                catch (Exception ex)
                {
                    resultModel.Msg = "异常:" + ex.Message;
                }
                //SerialPortObj.Close();
            }
            else
            {
                resultModel.Msg = "dataLis参数不能为NULL 且 Count 要大于0";
            }
            return resultModel;
        }

        /// <summary>
        /// 写单个线圈输出 ok
        /// </summary>
        /// <param name="on">开关</param>
        /// <param name="devAddr">从站地址</param>
        /// <param name="startAddr">写入地址</param>
        /// <returns></returns>
        public ModbusResultModel WriteSingleOutOnOff(bool on, byte devAddr = 1, ushort startAddr = 0)
        {
            var resultModel = new ModbusResultModel();
            try
            {
                //var isOk = false;
                //1.拼接报文:
                var sendCommand = new List<byte>();
                //协议格式:站地址+功能码+起始寄存器地址+寄存器数量+CRC
                //站地址
                sendCommand.Add(devAddr);
                //功能码
                byte functionCode = 0x05;
                sendCommand.Add(functionCode);
                //写入地址
                sendCommand.Add((byte)(startAddr / 256));
                sendCommand.Add((byte)(startAddr % 256));
                //写入数据
                sendCommand.Add((byte)(on ? 0xFF : 0x00));//true : 0xFF 开,false : 0x00 关
                sendCommand.Add(0x00);
                //CRC
                byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);
                sendCommand.AddRange(crc);
                //2.发送报文
                SerialPortObj.Write(sendCommand.ToArray(), 0, sendCommand.Count);
                //isOk = true;
                resultModel.IsSucceed = true;
            }
            catch (Exception ex)
            {
                resultModel.Msg = "异常:" + ex.Message;
            }
            return resultModel;
        }
    }
}

Modbus 串口通讯 读线圈状态

(这个类是针对线圈的 突出读取数据)

cs 复制代码
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CJH.ModbusTool.RTU
{
    /// <summary>
    /// Modbus 串口通讯 读线圈状态
    /// </summary>
    public class ModbusRTUCoil : ModbusRTU
    {
        //ModbusRTU rtu = new ModbusRTU(portName);
        //var resultModel = rtu.ReadData(1, readLen, ModbusFunctionCode.ReadOutCoil);

        /// <summary>
        /// Modbus 串口通讯
        /// </summary>
        /// <param name="portName">COM口名称</param>
        /// <param name="baudRate">波特率</param>
        /// <param name="parity">检验位</param>
        /// <param name="dataBits">数据位</param>
        /// <param name="stopBits">停止位</param>
        public ModbusRTUCoil(string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
            : base(portName, baudRate, parity, dataBits, stopBits)
        {
            //Init(portName, baudRate, parity, dataBits, stopBits);
            //SerialPortObj.DataReceived += new SerialDataReceivedEventHandler(ComDataReceived);
        }

        /// <summary>
        /// 读取线圈数据 ok
        /// </summary>
        /// <param name="devAdd">从站地址</param>
        /// <param name="length">寄存器数量</param>
        /// <param name="functionCode">功能码</param>
        /// <param name="startAddr">起始寄存器地址</param>
        /// <returns>线圈数据</returns>
        public ModbusResultModel<bool> ReadDataCoil(byte devAdd, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0)
        {
            var resultModel = new ModbusResultModel<bool>();
            var model = ReadData(devAdd, length, (byte)functionCode, startAddr);
            if (model != null && model.Datas != null && model.Datas.Length > 5)
            {
                resultModel.IsSucceed = model.IsSucceed;
                //报文解析
                // 检查一个校验位
                List<byte> respList = new List<byte>(model.Datas);
                respList.RemoveRange(0, 3);
                respList.RemoveRange(respList.Count - 2, 2);
                // 00 00
                //集合反转
                respList.Reverse();
                //转换成2进制
                var respStrList = respList.Select(r => Convert.ToString(r, 2)).ToList();
                var values = string.Join("", respStrList).ToList();
                values.Reverse();
                //values.ForEach(c => Console.WriteLine(Convert.ToBoolean(int.Parse(c.ToString()))));
                foreach (var v in values)
                {
                    resultModel.ResultList.Add(v.ToString() == "1");
                }
            }
            return resultModel;
        }
    }
}

TCP

ModbusTCP 基类

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace CJH.ModbusTool.TCP
{
    /// <summary>
    /// ModbusTCP 基类
    /// </summary>
    public abstract class ModbusTCPBase : ModbusBase
    {
        private Socket _socket = null;
        ushort _tid = 0;//TransactionId 最大 65535

        /// <summary>
        /// 异常码 字典
        /// </summary>
        protected Dictionary<int, string> Errors = new Dictionary<int, string>() {
            { 0x01 , "非法功能码"},
            { 0x02 , "非法数据地址"},
            { 0x03 , "非法数据值"},
            { 0x04 , "从站设备故障"},
            { 0x05 , "确认,从站需要一个耗时操作"},
            { 0x06 , "从站忙"},
            { 0x08 , "存储奇偶性差错"},
            { 0x0A , "不可用网关路径"},
            { 0x0B , "网关目标设备响应失败"},
        };

        /// <summary>
        /// Modbus TCP 通讯 初始化
        /// </summary>
        /// <param name="host">主机地址</param>
        /// <param name="port">端口</param>
        protected void Init(string host, int port)
        {
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _socket.Connect(host, port);
        }

        /// <summary>
        /// 读取报文的 公共方法
        /// </summary>
        /// <param name="devAddr">从站地址</param>
        /// <param name="length">寄存器数量</param>
        /// <param name="functionCode">功能码</param>
        /// <param name="startAddr">起始寄存器地址</param>
        /// <returns>返回报文(协议格式:TransactionId+协议标识+后续字节数+站地址+功能码+起始寄存器地址+寄存器数量)</returns>
        protected byte[] GenerateTcpCommandReadBytes(byte devAddr, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0)
        {
            var baseCommand = GenerateReadCommandBytes(devAddr, length, functionCode, startAddr);

            var sendCommand = new List<byte>();
            //TransactionId
            sendCommand.Add((byte)(_tid / 256));
            sendCommand.Add((byte)(_tid % 256));
            //Modbus 协议标识
            sendCommand.Add(0x00);
            sendCommand.Add(0x00);
            //后续字节数
            sendCommand.Add((byte)(baseCommand.Length / 256));
            sendCommand.Add((byte)(baseCommand.Length % 256));
            _tid++;
            _tid %= 65535;
            sendCommand.AddRange(baseCommand);
            return sendCommand.ToArray();
        }

        /// <summary>
        /// 读取报文的 公共方法
        /// </summary>
        /// <param name="devAddr">从站地址</param>
        /// <param name="data">输入数据</param>
        /// <param name="functionCode">功能码</param>
        /// <param name="startAddr">起始寄存器地址</param>
        /// <returns>返回报文(协议格式:TransactionId+协议标识+后续字节数+站地址+功能码+起始寄存器地址+寄存器数量)</returns>
        protected byte[] GenerateTcpCommandWriteBytes(byte devAddr, ushort data, ModbusFunctionCode functionCode = ModbusFunctionCode.WriteRegister, ushort startAddr = 0)
        {
            var baseCommand = GenerateWriteCommandBytes(devAddr, data, functionCode, startAddr);

            var sendCommand = new List<byte>();
            //TransactionId
            sendCommand.Add((byte)(_tid / 256));
            sendCommand.Add((byte)(_tid % 256));
            //Modbus 协议标识
            sendCommand.Add(0x00);
            sendCommand.Add(0x00);
            //后续字节数
            sendCommand.Add((byte)(baseCommand.Length / 256));
            sendCommand.Add((byte)(baseCommand.Length % 256));
            _tid++;
            _tid %= 65535;
            sendCommand.AddRange(baseCommand);
            return sendCommand.ToArray();
        }

        protected ModbusResultModel SendCommand(byte[] sendCommand)
        {
            var resultModel = new ModbusResultModel();
            try
            {
                //报文
                //TransactionId Modbus 协议标识 后续字节数  从站地址  功能码         起始寄存器地址  寄存器数量  CRC
                //0x00 0x01     0x00   0x00     0x00  0x06  devAddr   functionCode   0x00  0x0A      

                resultModel.SendDataStr = generateSendCommandStr(sendCommand.ToArray());
                _socket.Send(sendCommand.ToArray());
                //000002-Rx:00 00 00 00 00 06 01 03 00 01 00 05
                //000003-Tx:00 00 00 00 00 0D 01 03 0A 00 00 00 00 00 00 00 00 00 00
                // 00 00 00 00 00 0D 前6位
                // 01 03 0A (0,1,2)
                // 0A 数据长度 (0A=10)

                //先取前6位,固定返回
                var resp_bytes = new byte[6];// 00 00 00 00 00 0D 前6位
                _socket.Receive(resp_bytes, 0, resp_bytes.Length, SocketFlags.None);

                //取出下标为 :4和5的数据[00 0D]
                var len_bytes = resp_bytes.ToList().GetRange(4, 2);
                //起始寄存器地址 的反向操作
                //将下标为4和5 两个字节转成10进制数
                int len = len_bytes[0] * 256 + len_bytes[1];

                //获取数据的长度
                //01 03 0A 00 00 00 00 00 00 00 00 00 00 [正常]
                //01 83 02 [异常,83, 异常代码 :02]
                resp_bytes = new byte[len];
                _socket.Receive(resp_bytes, 0, len, SocketFlags.None);

                //检查响应报文是否正常
                //0x83 1000 0011
                //01 83 02 [异常,83, 异常代码 :02]
                if (resp_bytes[1] > 0x08)//判断是否异常
                {
                    //resp_bytes[2] = 异常代码 :02
                    //说明响应是异常报文
                    //返回异常信息,根据resp_bytes字节进行异常关联
                    if (Errors.ContainsKey(resp_bytes[2]))
                    {
                        resultModel.Msg = Errors[resp_bytes[2]];//获取异常码对应的异常说明
                    }
                }
                else
                {
                    //resp_bytes[2] = 0A 数据长度 (0A=10)
                    //正常
                    resultModel.Datas = resp_bytes.ToList().GetRange(3, resp_bytes[2]).ToArray();
                    resultModel.IsSucceed = true;
                }
            }
            catch (Exception ex)
            {
                resultModel.Msg = ex.Message;
            }
            return resultModel;
        }

        /// <summary>
        /// 解析数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="datas"></param>
        /// <returns></returns>
        public List<T> AnalysisDatas<T>(byte[] datas)
        {
            //data_bytes 每两个字节转成一个数字, float 4个字节转成一个数字,double 8个字节转成一个数字
            //2  ushort short int16 uint32 float
            //4  int uint int32 uint32 float
            //8  double
            //16 decimal
            var resultValue = new List<T>();
            try
            {
                var type_len = Marshal.SizeOf(typeof(T));
                for (int i = 0; i < datas.Length; i += type_len)
                {
                    var temp_bytes = datas.ToList().GetRange(i, type_len);
                    if (BitConverter.IsLittleEndian)
                    {
                        temp_bytes.Reverse();
                    }
                    //反射 方法
                    Type bitConverter_type = typeof(BitConverter);
                    var typeMethodList = bitConverter_type.GetMethods().ToList();
                    //找到返回类型和传入的类型一至,且方法的参数是2个的方法
                    var method = typeMethodList.FirstOrDefault(mi => mi.ReturnType == typeof(T)
                                        && mi.GetParameters().Length == 2);
                    if (method == null)
                    {
                        throw new Exception("数据转换类型出错!");
                    }
                    else
                    {
                        //由 bitConverter_type 执行找到的 method方法,注意参数数量,上面找是的两个参数的方法
                        var value = method.Invoke(bitConverter_type, new object[] { temp_bytes.ToArray(), 0 });
                        resultValue.Add((T)value);
                    }
                }
            }
            catch (Exception ex)
            {

            }
            return resultValue;
        }
    }
}

Modbus TCP 通讯

cs 复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;

namespace CJH.ModbusTool.TCP
{
    /// <summary>
    /// Modbus TCP 通讯
    /// </summary>
    public class ModbusTCP : ModbusTCPBase
    {

        /// <summary>
        /// Modbus TCP 通讯
        /// </summary>
        /// <param name="host">主机地址</param>
        /// <param name="port">端口</param>
        public ModbusTCP(string host, int port)
        {
            //_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //_socket.Connect(host, port);
            Init(host, port);
        }

        /// <summary>
        /// 读取保持型寄存器 03
        /// </summary>
        /// <param name="devAddr">从站地址</param>
        /// <param name="count">数量</param>
        /// <param name="functionCode">功能码</param>
        /// <param name="startAddr">起始地址</param>
        //public ModbusResultModel ReadHoldingRegister(byte devAddr, ushort length, byte functionCode = 3, ushort startAddr = 0)
        //{
        //    var resultModel = new ModbusResultModel();
        //    //报文
        //    //TransactionId Modbus 协议标识 后续字节数  从站地址  功能码         起始寄存器地址  寄存器数量  CRC
        //    //0x00 0x01     0x00   0x00     0x00  0x06  devAddr   functionCode   0x00  0x0A      
        //    try
        //    {
        //        ushort tid = 0;//TransactionId 最大 65535

        //        var sendCommand = new List<byte>();
        //        //TransactionId
        //        sendCommand.Add((byte)(tid / 256));
        //        sendCommand.Add((byte)(tid % 256));
        //        //Modbus 协议标识
        //        sendCommand.Add(0x00);
        //        sendCommand.Add(0x00);
        //        //后续字节数
        //        sendCommand.Add(0x00);
        //        sendCommand.Add(0x06);
        //        //从站地址
        //        sendCommand.Add(devAddr);
        //        //功能码
        //        sendCommand.Add(functionCode);
        //        //起始寄存器地址
        //        sendCommand.Add((byte)(startAddr / 256));
        //        sendCommand.Add((byte)(startAddr % 256));
        //        //寄存器数量
        //        sendCommand.Add((byte)(length / 256));
        //        sendCommand.Add((byte)(length % 256));
        //        //CRC
        //        //byte[] crc = Crc16(sendCommand.ToArray(), sendCommand.Count);
        //        //sendCommand.AddRange(crc);
        //        tid++;
        //        tid %= 65535;
        //        resultModel.SendDataStr = generateSendCommandStr(sendCommand.ToArray());
        //        _socket.Send(sendCommand.ToArray());
        //        //000002-Rx:00 00 00 00 00 06 01 03 00 01 00 05
        //        //000003-Tx:00 00 00 00 00 0D 01 03 0A 00 00 00 00 00 00 00 00 00 00
        //        // 00 00 00 00 00 0D 前6位
        //        // 01 03 0A (0,1,2)
        //        // 0A 数据长度 (0A=10)

        //        //先取前6位,固定返回
        //        var resp_bytes = new byte[6];// 00 00 00 00 00 0D 前6位
        //        _socket.Receive(resp_bytes, 0, resp_bytes.Length, SocketFlags.None);

        //        //取出下标为 :4和5的数据[00 0D]
        //        var len_bytes = resp_bytes.ToList().GetRange(4, 2);
        //        //起始寄存器地址 的反向操作
        //        //将下标为4和5 两个字节转成10进制数
        //        int len = resp_bytes[4] * 256 + resp_bytes[5];

        //        //获取数据的长度
        //        //01 03 0A 00 00 00 00 00 00 00 00 00 00 [正常]
        //        //01 83 02 [异常,83, 异常代码 :02]
        //        resp_bytes = new byte[len];
        //        _socket.Receive(resp_bytes, 0, len, SocketFlags.None);

        //        //检查响应报文是否正常
        //        //0x83 1000 0011
        //        //01 83 02 [异常,83, 异常代码 :02]
        //        if (resp_bytes[1] > 0x08)//判断是否异常
        //        {
        //            //resp_bytes[2] = 异常代码 :02
        //            //说明响应是异常报文
        //            //返回异常信息,根据resp_bytes字节进行异常关联
        //            if (Errors.ContainsKey(resp_bytes[2]))
        //            {
        //                resultModel.Msg = Errors[resp_bytes[2]];//获取异常码对应的异常说明
        //            }
        //        }
        //        else
        //        {
        //            //resp_bytes[2] = 0A 数据长度 (0A=10)
        //            //正常
        //            resultModel.Datas = resp_bytes.ToList().GetRange(3, resp_bytes[2]).ToArray();
        //            resultModel.IsSucceed = true;
        //        }
        //    }
        //    catch (Exception ex)
        //    {
        //        resultModel.Msg = ex.Message;
        //    }
        //    return resultModel;
        //}

        /// <summary>
        /// 读取保持型寄存器 03
        /// </summary>
        /// <param name="devAddr">从站地址</param>
        /// <param name="length">数量</param>
        /// <param name="functionCode">功能码</param>
        /// <param name="startAddr">起始地址</param>
        /// <returns>返回对象</returns>
        public ModbusResultModel<T> ReadHoldingRegister<T>(byte devAddr, ushort length, ModbusFunctionCode functionCode = ModbusFunctionCode.ReadRegister, ushort startAddr = 0)
        {
            var resultModel = new ModbusResultModel<T>();
            try
            {
                var command = GenerateTcpCommandReadBytes(devAddr, length, functionCode, startAddr);

                resultModel.SendDataStr = generateSendCommandStr(command.ToArray());
                var receptionModel = SendCommand(command.ToArray());
                if (receptionModel.IsSucceed
                    && receptionModel.Datas != null && receptionModel.Datas.Length > 0)
                {
                    resultModel.Datas = receptionModel.Datas;
                    resultModel.ResultList = AnalysisDatas<T>(receptionModel.Datas);
                    resultModel.IsSucceed = true;
                }
            }
            catch (Exception ex)
            {
                resultModel.Msg = ex.Message;
            }
            return resultModel;
        }

        /// <summary>
        /// 写入保持型寄存器 03
        /// </summary>
        /// <param name="devAddr">从站地址</param>
        /// <param name="datas">写入数据</param>
        /// <param name="functionCode">功能码</param>
        /// <param name="startAddr">起始地址</param>
        /// <returns>返回对象</returns>
        public ModbusResultModel WriteHoldingRegister(byte devAddr, ushort data, ModbusFunctionCode functionCode = ModbusFunctionCode.WriteRegister, ushort startAddr = 0)
        {
            var resultModel = new ModbusResultModel();
            try
            {
                var command = GenerateTcpCommandWriteBytes(devAddr, data, functionCode, startAddr);

                resultModel.SendDataStr = generateSendCommandStr(command.ToArray());
                var receptionModel = SendCommand(command.ToArray());
                if (receptionModel.IsSucceed)
                {
                    resultModel.IsSucceed = true;
                }
            }
            catch (Exception ex)
            {
                resultModel.Msg = ex.Message;
            }
            return resultModel;
        }
    }
}

这就是全部的代码。如有使用的问题可以给我发评论。

相关推荐
console.log('npc')4 分钟前
vue2中子组件父组件的修改参数
开发语言·前端·javascript
码点5 分钟前
【无标题】日文字库Japan.ini
开发语言
IT=>小脑虎9 分钟前
2026版 Python零基础小白学习知识点【基础版详解】
开发语言·python·学习
wjs202419 分钟前
抽象工厂模式
开发语言
lly20240620 分钟前
SVG 模糊效果详解
开发语言
期待のcode22 分钟前
Java虚拟机类加载机制
java·开发语言
看见繁华29 分钟前
Linux 交叉编译实践笔记
linux·运维·笔记
Learner29 分钟前
Python运算符
开发语言·python
一晌小贪欢33 分钟前
Python 精确计算:告别浮点数陷阱,decimal 模块实战指南
开发语言·python·python入门·python3·python小数·python浮点数
superman超哥34 分钟前
Rust 范围模式(Range Patterns):边界检查的优雅表达
开发语言·后端·rust·编程语言·rust范围模式·range patterns·边界检查