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

news2024/11/20 0:28:29

  前言

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【鸿蒙开发】第十四章 Stage模型应用组件-任务Mission

1 任务(Mission)管理场景 任务&#xff08;Mission&#xff09;管理相关的基本概念如下&#xff1a; AbilityRecord&#xff1a;系统服务侧管理一个UIAbility实例的最小单元&#xff0c;对应一个应用侧的UIAbility组件实例。系统服务侧管理UIAbility实例数量上限为512个。 Mi…

C# winfroms使用socket客户端服务端代码详解

文章目录 1️⃣ 通信相关说明1.1服务端与客户端1.2 信息发送原理1.3 信息接收原理 2️⃣ socket代码2.1 客户端代码2.2 服务端代码 3️⃣ 定时任务处理报文3.1 Timers定时任务 优质资源分享 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_4315141…

共基课程学习

序言 教育教师 政治基础知识 马克思主义哲学 西方哲学史 三个阶段 西方哲学的起源 圈1 圈2 圈3 第一个哲学高峰 希腊三贤 圈4 圈5 是故格拉底的学生 圈6 是柏拉图的学生 圈7、圈8 这是一个政教合一的社会 圈7 圈8 圈9 圈10 圈11 圈12 文艺复兴、启蒙运动共…

BlackberryQ10 是可以安装 Android 4.3 应用的,Web UserAgent 版本信息

BlackberryQ10 是可以安装 Android 4.3 应用的 最近淘了个 Q10 手机&#xff0c;非常稀罕它&#xff0c;拿着手感一流。这么好的东西&#xff0c;就想给它装点东西&#xff0c;但目前所有的应用都已经抛弃这个安卓版本了。 一、开发环境介绍 BlackBerry Q10 的 安卓版本是 4.…

计算机体系架构初步入门

&#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;高性能&#xff08;HPC&#xff09;开发基础教程 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人生秘诀&#xff1a;学习的本质就是极致重复! 目录 1 计算机五大…

Spring 中 ApplicationContext 和 BeanFactory 的区别有哪些

先看一张类图&#xff1a; 区别&#xff1a; 1&#xff1a;包目录不同&#xff1a; spring-beans.jar 中 org.springframework.beans.factory.BeanFactory spring-context.jar 中 org.springframework.context.ApplicationContext 2&#xff1a;国际化&#xff1a; BeanFacto…

c++之ini配置文件的详细解析

文章目录 ini文件概要代码实例分析小结 ini文件概要 ini文件是一种系统配置文件&#xff0c;它有特定的格式组成。通常做法&#xff0c;我们读取ini文件并按照ini格式进行解析即可。在c语言中&#xff0c;提供了模板类的功能&#xff0c;所以我们可以提供一个更通用的模板类来解…

【线程池项目(二)】线程池FIXED模式的实现

在上一篇【线程池项目&#xff08;一&#xff09;】项目介绍和代码展示 中&#xff0c;我们展示了线程池的两个版本实现&#xff0c;它们的代码在具体的实现细节上是优化过了的。下文提供的代码并非完整&#xff0c;也有很多地方尚需改善&#xff0c;但这些差异对理解整个项目而…

IT廉连看——C语言——分支语句

IT廉连看—分支语句 一、什么是语句 C语句可分为以下五类&#xff1a; 表达式语句 函数调用语句 控制语句 复合语句 空语句 本周后面介绍的是控制语句。 控制语句用于控制程序的执行流程&#xff0c;以实现程序的各种结构方式&#xff0c;它们由特定的语句定义符组成&…

字符串(算法竞赛)--字典树Trie与最大异或对

1、B站视频链接&#xff1a;F06 字典树(Trie)_哔哩哔哩_bilibili 题目链接&#xff1a;【模板】字典树 - 洛谷 #include <bits/stdc.h> using namespace std; const int N100010; int n; char s[N]; int ch[N][26];//ch[0][2]1表示0号节点通过c边走到了节点1 int cnt[…

2024最新前端面试题

数组是属于Object类型的&#xff0c;也就是引用类型&#xff0c;所以不能使用 typeof 来判断其具体类型。下面这些方法是判断数组的几种方法&#xff1a; 1、instanceof运算符 主要是判断某个实例&#xff08;arr&#xff09;是否属于某个对象。 let arr [1,2,3]; console.l…

eclipse中open Type 、 open type in Hierachy、open Resource的区别

目录 场景&#xff1a; open Type open Resource open type in Hierachy 场景&#xff1a; 在项目中想要研究底层代码&#xff0c;经常要用eclipse看依赖jar包的类&#xff0c;比如spring的源码中AbstractApplicationContext类CTLSHIFTT用的少&#xff0c;经常用的CTLSHIR…

给大家分享一款小程序:AI一秒修图

AI一秒修图 照片修复的AI助手特点&#xff1a;Demo&#xff08;1.选择图片 2.涂抹遮罩 3.消除&#xff09;Product Roadmap (版本演进)Contact-联系我们Reference 照片修复的AI助手 照片修复小小助手是一款快速P图微信小程序&#xff0c;用来消除图片中指定的人和物&#xff…

[算法沉淀记录] 排序算法 —— 冒泡排序

排序算法 —— 冒泡排序 基本概念 冒泡排序是一种简单的排序算法。它重复地遍历要排序的列表&#xff0c;一次比较两个元素&#xff0c;并交换它们的位置&#xff0c;如果它们不是按照升序排列的。这步遍历是重复进行的&#xff0c;直到没有再需要交换&#xff0c;也就是说该…

【设计模式】策略模式及函数式编程的替代

本文介绍策略模式以及使用函数式编程替代简单的策略模式。 策略模式 在策略模式&#xff08;Strategy Pattern&#xff09;中一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。 在策略模式定义了一系列算法或策略&#xff0c;并将每个算法封装在独立…

介绍 PIL+IPython.display+mtcnn for 音视频读取、标注

1. nn.NLLLoss是如何计算误差的? nn.NLLLoss是负对数似然损失函数&#xff0c;用于多分类问题中。它的计算方式如下&#xff1a;首先&#xff0c;对于每个样本&#xff0c;我们需要将其预测结果通过softmax函数转换为概率分布。softmax函数可以将一个向量映射为一个概率分布&…

Three.js加载PLY文件

这是官方的例子 three.js webgl - PLY 我在Vue3中使用&#xff0c;测试了好久始终不显示点云数据。在网上查询后发现ply文件要放置在public目录下才行 <el-row><el-button type"primary" class"el-btn" click"IniThree1">PLY</…

【C++初阶】--类和对象(下)

目录 一.const成员 1.权限放大问题 2.权限的缩小 二.再谈构造函数 1.构造函数体赋值 2.初始化列表 (1)概念 (2)使用 ①在对象实例化过程中&#xff0c;成员变量先依次进行初始化 ②再进行函数体内二次赋值 3.explicit关键字 (1)C为什么要存在自动隐式类型转换…

Java之线程同步、synchronized用法及原理

线程的同步 场景1&#xff1a;两个线程同时访问一个变量&#xff0c;一个线程自增&#xff0c;一个线程自减 public class thread11 {public static void main(String[] args) throws InterruptedException {Thread thread1 new AddThread();Thread thread2 new DecThread(…

编曲学习:高叠和弦 挂留和弦 和弦实战应用

高叠和弦 挂留和弦 和弦实战应用小鹅通-专注内容付费的技术服务商https://app8epdhy0u9502.pc.xiaoe-tech.com/live_pc/l_65d4826fe4b04c10a1310517?course_id=course_2XLKtQnQx9GrQHac7OPmHD9tqbv 七和弦 以三和弦举例,三和弦上面叠一个三度的音,就变成了七和弦。 从下到…