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

  前言

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

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

设计思想

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

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

调用示例

        

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结果实体

        

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 基类

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

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 串口通讯

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

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 串口通讯 读线圈状态

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

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 基类

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 通讯

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/410773.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue+SpringBoot打造在线课程教学系统

目录 一、摘要1.1 系统介绍1.2 项目录屏 二、研究内容2.1 课程类型管理模块2.2 课程管理模块2.3 课时管理模块2.4 课程交互模块2.5 系统基础模块 三、系统设计3.1 用例设计3.2 数据库设计 四、系统展示4.1 管理后台4.2 用户网页 五、样例代码5.1 新增课程类型5.2 网站登录5.3 课…

WampServer环境下载安装并结合内网穿透实现远程访问管理界面

文章目录 前言1.WampServer下载安装2.WampServer启动3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4.固定公网地址访问 前言 Wamp 是一个 Windows系统下的 Apache PHP Mysql 集成安装环境&#xff0c;是一组常用来…

华为配置WDS背靠背业务示例

配置WDS背靠背业务示例 组网图形 图1 配置WDS背靠背组网示意图 业务需求组网需求数据规划配置思路配置注意事项操作步骤配置文件 业务需求 在某些企业网络中&#xff0c;有线网络部署受施工条件的限制&#xff0c;需要连接的网络之间有障碍物或传输距离较远&#xff0c;AP无法全…

这家宠物品牌的内容运营怎么做的?太好玩儿了吧

养宠的朋友应该多多少少对“诚实一口”这个牌子有所耳闻&#xff0c;2018年诚实一口品牌正式立项&#xff0c;虽然不算经典品牌&#xff0c;但在国内也是小有名气的宠物品牌。今天媒介盒子想和大家聊的不是产品&#xff0c;而是想聊聊作为成立时间不长的国产宠粮品牌是如何凭借…

5.4 内容管理模块 - 课程搜索

5.4 内容管理模块 - 课程搜索 文章目录 5.4 内容管理模块 - 课程搜索一、快速入门1.1 需求分析1.2 业务流程1.3 准备环境1.3.1 搭建 elasticsearch1.3.2 索引 概念 1.4 课程信息索引同步1.4.1 技术方案 一、快速入门 本项目使用elasticsearch作为索引及搜索服务 课程如果发布之…

Sora抢饭碗!好莱坞大亨停止,8亿美元投资

好莱坞消息&#xff0c;著名演员、影视投资人Tyler Perry在看到OpenAI最新发布的文生视频模型Sora后&#xff0c;停止了8亿&#xff08;约57亿元&#xff09;美元的投资。 该投资项目位于亚特兰大&#xff0c;本来要扩展十几个摄影棚用于影视剧的拍摄&#xff08;类似横店影视…

大学开学季,创业入局做校园外卖,这些新的运营思路可以参考

在大学生的学习、生活和消费中&#xff0c;高校已经形成了具有独特属性的市场。这个市场既具有一般市场的特点&#xff0c;同时也有学校特有的一些特点。校园生活服务平台的商业模式就是在互联网发展的背景下出现的&#xff0c;并且已经变得非常成熟和稳定。 就校园外卖而言&a…

Java+SpringBoot+Vue+MySQL:疫情隔离酒店管理的全面技术解决方案

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

【云原生】Spring Cloud Gateway的底层原理与实践方法探究

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《Spring 狂野之旅&#xff1a;从入门到入魔》 &#x1f680; 本…

切换Linux中的gcc版本

一、允许在系统中并行安装多个软件版本 命令1&#xff1a; yum -y install centos-release-scl 二、安装GCC 8及相关工具 命令2&#xff1a; yum -y install devtoolset-8-gcc devtoolset-8-gcc-c devtoolset-8-binutils 三、将系统中的默认shell环境切换到与devtool…

如何在Win系统搭建Oracle数据库并实现远程访问【内网穿透】

文章目录 前言1. 数据库搭建2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射 3. 公网远程访问4. 配置固定TCP端口地址4.1 保留一个固定的公网TCP端口地址4.2 配置固定公网TCP端口地址4.3 测试使用固定TCP端口地址远程Oracle 前言 Oracle&#xff0c;是甲骨文公司的一款关系…

Flink代码单词统计 ---批处理

flatMap&#xff1a;一对多转换操作&#xff0c;输入句子&#xff0c;输出分词后的每个词groupBy&#xff1a;按Key分组&#xff0c;0代表选择第1列作为Keysum&#xff1a;求和&#xff0c;1代表按照第2列进行累加print&#xff1a;打印最终结果 1.WordCount代码编写 需求&am…

js设计模式:路由模式

作用: 业务开发时,路由这个概念无论对于前后端来说肯定是不可缺少的 前端的vue-router就是很经典的路由模式 示例: //这里模拟实现一个vueclass Vue{constructor(options){Object.keys(options).forEach(item>{this[item] options[item]})}$mount(node){console.log(挂载…

基础知识常见算法识别

特征值识别 很多常见的算法&#xff0c;如AES、DES等&#xff0c;在运算过程中会使用一些常量&#xff0c;而为了提高运算效率&#xff0c;这些常量往往被硬编码在程序中 通过识别这些特征值&#xff0c;可以对算法进行一个大致判断 算法特征值&#xff08;无特殊说明为十六…

Web3的奇迹:数字世界的新篇章

在数字化时代的潮流中&#xff0c;Web3正以其令人振奋的潜力和前景引领着我们进入一个全新的数字时代。作为互联网的下一代&#xff0c;Web3将重新定义我们对数字世界的认知和体验&#xff0c;为我们带来无限的可能性和奇迹。本文将深入探讨Web3的重要性、核心特征以及未来展望…

智能美颜引领短视频创作风潮:探秘美颜SDK技术背后的创新

美颜技术不仅改善了用户的拍摄体验&#xff0c;还推动了短视频创作的风潮。本文将深入探讨智能美颜在短视频创作中的应用&#xff0c;以及美颜SDK技术背后的创新。 一、短视频时代的美颜潮流 随着短视频应用的普及&#xff0c;用户对于视频质量的要求也越来越高。然而&#…

精品springboot科研项目工作量管理系统的设计与实现

《[含文档PPT源码等]精品基于springboot科研工作量管理系统的设计与实现[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; Java——涉及技术&#xff1a; 前端使用技术&…

Wagtail安装运行并结合内网穿透实现公网访问本地网站界面

文章目录 前言1. 安装并运行Wagtail1.1 创建并激活虚拟环境 2. 安装cpolar内网穿透工具3. 实现Wagtail公网访问4. 固定的Wagtail公网地址 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&#xff0c;风趣幽默&#xf…

c# 异常处理

异常类 .NET Framework 类库中的所有异常都派生于 Exception 类&#xff0c;异常包括系统异常和应用异常。 默认所有系统异常派生于 System.SystemException&#xff0c;所有的应用程序异常派生于 System.ApplicationException。 系统异常一般不可预测&#xff0c;比如内存堆…

RabbitMQ 面试八股题整理

前言&#xff1a;本文是博主网络自行收集的一些RabbitMQ相关八股文&#xff0c;还在准备暑期实习&#xff0c;后续应该会持续更新...... 参考&#xff1a;三天吃透RabbitMQ面试八股文_牛客网 目录 RabbitMQ概述 什么是 RabbitMQ&#xff1f; 说一说RabbitMQ中的AMQP 为什么…