当前位置:网站首页>编写ModbusTCP通信库

编写ModbusTCP通信库

2022-01-15 02:02:32 罗迪尼亚的熔岩

using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using MS_Entity;
using thinger.cn.DataConvertHelper;

namespace MS_UI
{
    
    public class ModbusTCPTask1
    {
    
        //定义串口类对象
        private Socket tcpclient = null; //定义串口类对象

        private CancellationTokenSource cts = new CancellationTokenSource();//开启多线程标志位

        public static int a;
        //起始地址
        int StartAddress = 0;
        //错误次数
        int ErrorTimer = 0;
        //0x区域的变量列表
        public List<Variable_Modbus> List_0x = new List<Variable_Modbus>();
        //1x区域的变量列表
        public List<Variable_Modbus> List_1x = new List<Variable_Modbus>();
        //3x区域的变量列表
        public List<Variable_Modbus> List_3x = new List<Variable_Modbus>();
        //4x区域的变量列表
        public List<Variable_Modbus> List_4x = new List<Variable_Modbus>();//用来存储实时数据


        #region 打开与关闭Socket
        /// <summary>
        /// 打开TCP
        /// </summary>
        /// <param name="ip">IP地址</param>
        /// <param name="port">端口号</param>
        /// <returns></returns>

        public bool Connect(string ip, int port)
        {
    
            tcpclient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
    
                //设置Socket
                IPEndPoint ie = new IPEndPoint(IPAddress.Parse(ip), Convert.ToInt32(port));
                tcpclient.Connect(ie);//开启网口
            }
            catch (Exception)
            {
    
                return false;
            }
            Task.Run(() =>
            {
    
                Communication();
            }, cts.Token);
            return true;
        }

        #region 实时通信
        private void Communication()
        {
    
            while (true)
            {
    
               foreach (StoreArea item in CommonMethods.StoreAreaList)//item:StoreType,StartReg,Length
                {
    
                    byte[] Res = null;
                    int Start = item.StartReg;//偏移量 StartReg="0" 
                    List<byte> ByteList = null;
                    //int Start = Convert.ToInt32(this.txt_Address.Text.Trim());
                    //int Length = Convert.ToInt32(this.txt_Length.Text.Trim());
                    switch (item.StoreType)
                    {
    
                        case "01 Coil Status(0x)":
                            Res = ReadOutputStatus(Start, item.Length);
                            if (Res != null)
                            {
    
                                ErrorTimer = 0;
                                ByteList.AddRange(Res);

                            }
                            //解析
                            int length = item.Length % 8 == 0 ? item.Length / 8 : item.Length / 8 + 1;
                            if (ByteList.Count == length)
                            {
    
                                AnalyseData_0x(ByteList);
                            }
                            break;


                        case "03 Holding Register(4x)":
                            ByteList = new List<byte>();
                            Res = ReadKeepReg(Start, item.Length);
                            if (Res != null)
                            {
    
                                ErrorTimer = 0;
                                ByteList.AddRange(Res);//232个字节
                            }
                            else
                            {
    
                                ErrorTimer += 1;
                            }

                            a = ByteList.Count;
                            if (ByteList.Count == item.Length * 2)//断点
                            {
    
                                AnalyseData_4x(ByteList);//ByteList:232个字节
                            }
                            break;
                        default:
                            break;
                    }
                }
            }
 
        }
        #endregion

        

        /// <summary>
        /// 关闭Socket
        /// </summary>
        /// <returns></returns>
        public bool Disconnect()
        {
    
            if (cts!=null)
            {
    
                cts.Cancel();
            }
            if (tcpclient != null)
            {
    
                tcpclient.Close();
                return true;
            }
            else
            {
    
                return false;
            }

        }
        #endregion

        #region 分区解析成整型和浮点型


        private void AnalyseData_0x(List<byte> ByteList)
        {
    
            if (ByteList != null && ByteList.Count > 0)
            {
    
                foreach (Variable_Modbus item in this.List_0x)
                {
    
                    int totalIndex = int.Parse(item.Address) - StartAddress;
                    int ByteIndex = totalIndex / 8;
                    int BitIndex = totalIndex % 8;
                    switch (item.DataType)
                    {
    
                        case "Bool":
                            string ByteStr = Convert.ToInt32(Convert.ToString(Convert.ToInt32(ByteList[ByteIndex]), 2)).ToString("0#######");
                            CommonMethods.CurrentValue[item.VarName] = ByteStr.Substring(7 - BitIndex, 1);
                            break;
                        default:
                            break;
                    }
                }
            }

        }

        private void AnalyseData_4x(List<byte> ByteList)
        {
    
            int StartByte;
            byte[] Res = null;
            if (ByteList != null && ByteList.Count > 0)//ByteList为所有变量的232个字节
            {
    
                //List_4x 为StoreArea为"04 Input Register(3x)",相当于筛选
                foreach (Variable_Modbus item in this.List_4x)
                {
    
                    switch (item.DataType)
                    {
    
                        case "Float":
                            //Address偏移量在Variable_Modbus中,经过筛选,4区变量存入List_4x
                            StartByte = int.Parse(item.Address) * 2;
                            //Res = new byte[4] { ByteList[StartByte], ByteList[StartByte + 1], ByteList[StartByte + 2], ByteList[StartByte + 3] };
                            //CommonMethods.CurrentValue[item.VarName] = Convert.ToDouble(MS_Entity.Double.BytetoFloatByPoint(Res)).ToString("f1");

                            byte[] bytelib = ByteList.ToArray();
                            float floatlib = FloatLib.GetFloatFromByteArray(bytelib, StartByte);
                            CommonMethods.CurrentValue[item.VarName] = Convert.ToDouble(floatlib).ToString("f1"); //floatlib.ToString();
                            break;
                        case "Unsigned":
                            StartByte = int.Parse(item.Address) * 2;
                            Res = new byte[2] {
     ByteList[StartByte], ByteList[StartByte + 1] };
                            CommonMethods.CurrentValue[item.VarName] = MS_Entity.Int.FromByteArray(Res).ToString();
                            break;
                        default:
                            break;
                    }
                }
            }
        }
        #endregion


        #region TCP报文详解



        //TCP没有接收事件
        /* TCP的报文格式: 请求: byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] 12个字节 byte[0] byte[1]: 消息号---------随便指定,服务器返回的数据的前两个字和这个一样 byte[2] byte[3]:modbus标识,强制为0即可 byte[4] byte[5]:指示排在byte[5]后面所有字节的个数,也就是总长度 6 byte[6]:站号,对于TCP协议来说,某些情况不重要,可以随便指定,对于rtu及ascii来说,就需要选择设备的站号信息。 byte[7]:功能码 byte[8] byte[9]:起始地址 byte[10] byte[11]:指定想读取的数据长度 发送: 前面是接收到数据的时间,自动忽略,那么返回的数据就是 00 00 00 00 00 04 FF 01 01 00 共计10个字节的数据 byte[0] byte[1] : 消息号,我们之前写发送指令的时候,是多少,这里就是多少。 byte[2] byte[3]: 必须都为0,代表这是modbus 通信 byte[4] byte[5]:指示byte[5]后面的所有字节数,你数数看是不是4个?所以这里是00 04,如果后面共有100个,那么这里就是 00 64 byte[6]:站号,之前我们写了FF,那么这里也就是FF byte[7]:功能码,我们之前写了01的功能码,这里也是01,和我们发送的指令是一致的 byte[8]: 指示byte[8]后面跟随的字节数量,因为跟在byte[8]后面的就是真实的数据,我们最终想要的结果就在byte[8]后面 byte[9]:真实的数据,这肯定就是我们真正想要的东西了,我们知道一个byte有8位, 但是我们只读取了一个位数据,所有这里的有效值只是byte[9]的最低位,二进制为 0000 0000 我们看到最低位为0,所以最终我们读取的地址0的线圈为断。 */
        #endregion

        #region 读输出线圈 功能0x01
        /// <summary>
        /// 读输出线圈 功能0x01
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="iLength"></param>
        /// <returns></returns>
        public byte[] ReadOutputStatus(int iAddress, int iLength)
        {
    
            byte[] Result = null;
            //拼接发送报文
            MS_Entity.ByteArray byteArray = new MS_Entity.ByteArray(12);
            byteArray.Add(new byte[]
            {
    
            0,0,0,0,0,6,1,1
            });
            byteArray.Add((byte)((iAddress - iAddress % 256) / 256));
            byteArray.Add((byte)(iAddress % 256));
            byteArray.Add((byte)((iLength - iLength % 256) / 256));
            byteArray.Add((byte)(iLength % 256));
            //发送报文
            tcpclient.Send(byteArray.array, byteArray.array.Length, SocketFlags.None);
            //接受报文
            byte[] data = new byte[512];
            tcpclient.Receive(data, 512, SocketFlags.None);

            //判断报文
            int length = 0;
            if (iLength % 8 == 0)
            {
    
                length = iLength / 8;
            }
            else
            {
    
                length = iLength / 8 + 1;
            }
            if (length == data[8])
            {
    
                //解析报文
                Result = ByteMsgToRes(data, 9, length);
            }
            return Result;
        }
        #endregion

        #region 强制输出线圈 功能码0x05
        /// <summary>
        /// 强制输出线圈 功能码0x05
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="Value"></param>
        /// <returns></returns>
        public bool ForceOnOff(int iAddress, string Value)
        {
    
            byte[] Result = null;
            //拼接发送报文
            MS_Entity.ByteArray byteArray = new MS_Entity.ByteArray(12);
            byteArray.Add(new byte[]
            {
    
            0,0,0,0,0,6,1,5
            });
            byteArray.Add((byte)((iAddress - iAddress % 256) / 256));
            byteArray.Add((byte)(iAddress % 256));
            if (Value == "1" | Value.ToUpper() == "TRUE")
            {
    
                byteArray.Add(0xFF);
            }
            else
            {
    
                byteArray.Add(0);
            }
            byteArray.Add(0);
            //发送报文
            tcpclient.Send(byteArray.array, byteArray.array.Length, SocketFlags.None);
            //接受报文
            byte[] data = new byte[512];
            tcpclient.Receive(data, 512, SocketFlags.None);
            //解析报文
            Result = ByteMsgToRes(data, 0, 12);
            //判断报文
            if (Result.ToString() == byteArray.array.ToString())
            {
    
                return true;
            }
            else
            {
    
                return false;
            }
        }
        #endregion

        #region 读保持型寄存器 功能码0x03
        /// <summary>
        /// 读保持型寄存器 功能码0x03
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="iLength"></param>
        /// <returns></returns>
        public byte[] ReadKeepReg(int iAddress, int iLength)
        {
    
            //byte[] Result = null;
            拼接发送报文 12个字节
            //ByteArray byteArray = new ByteArray(12);
            //byteArray.Add(new byte[]
            //{
    
            //0,0,0,0,0,6,1,3 //站号是1, 功能码是3
            //});
            //byteArray.Add((byte)((iAddress - iAddress % 256) / 256));
            //byteArray.Add((byte)(iAddress % 256));
            //byteArray.Add((byte)((iLength - iLength % 256) / 256));
            //byteArray.Add((byte)(iLength % 256));
            发送报文
            //tcpclient.Send(byteArray.array, byteArray.array.Length, SocketFlags.None);
            接受报文 9个字节
            //byte[] data = new byte[512];
            //tcpclient.Receive(data, 512, SocketFlags.None);

            判断报文
            //int length = iLength * 2;
            //if (length == data[8])
            //{
    
            // //解析报文
            // Result = ByteMsgToRes(data, 9, length);
            //}
            //return Result;

            //拼接报文
            List<byte> SendCommand = new List<byte>();
            SendCommand.AddRange(new byte[] {
     0, 0, 0, 0 });//标识符 ,4字节
            SendCommand.AddRange(new byte[] {
     0, 6 });//长度, 2字节
            SendCommand.Add(1);//从站号,1字节
            SendCommand.Add(0x03);//功能码,1字节
            SendCommand.Add((byte)((iAddress - iAddress % 256) / 256));//1字节
            SendCommand.Add((byte)(iAddress % 256));//1字节
            SendCommand.Add((byte)((iLength - iLength % 256) / 256));//1字节
            SendCommand.Add((byte)(iLength % 256));//1字节 总共12字节
            //发送报文
            tcpclient.Send(SendCommand.ToArray(), SocketFlags.None);
            //接收报文
            byte[] buffer = new byte[512];
            int count = tcpclient.Receive(buffer, SocketFlags.None);
            //判断报文
            if (count == 2 * iLength + 9)
            {
    
                //获取真正的报文, 字节数组的截取
                byte[] des = new byte[count];
                byte[] res = new byte[2 * iLength];
                Array.Copy(buffer, 0, des, 0, count);//从buffer复制到des, 从0开始, 复制count个
                //二次验证
                if (des[7] == 0x03 && des[8] == 2 * iLength)//iLength 寄存器数量,2*iLength字节数,一个寄存器2个字节
                {
    
                    //字节数组截取
                    Array.Copy(des, 9, res, 0, 2 * iLength);

                    //调转顺序
                    //List<ushort> result = new List<ushort>(); //ushort 16位无符号整数
                    //for (int i = 0; i < res.Length; i+=2)
                    //{
    
                    // result.Add(Convert.ToUInt16(res[i] * 256 + res[i + 1]));//调转高低位
                    //} //Res[i] = Convert.ToByte(Result[i], 16) 借鉴方法
                    正常情况下,从站应答 格式为 字节数,寄存器高位,寄存器地位,。。。寄存器高位,寄存器地位,
                    //ushort[] resu = new ushort[res.Length];
                    //for (int i = 0; i < res.Length; i++)
                    //{
    
                    // resu[i] = result[i];
                    //}

                    //byte[] res1 = Int.ToByteArray(Convert.ToInt16(resu));
                    //return res1;
                    //byte[] res1 = BitConverter.GetBytes(res.Length).Reverse().ToArray();
                }
                return res;
            }
            else
                return null;
        }
        #endregion

        #region 预置单个寄存器 功能码0x06
        /// <summary>
        /// 预置单个寄存器 功能码0x06
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="SetValue"></param>
        /// <returns></returns>
        public bool PreSetKeepReg(int iAddress, UInt16 SetValue)
        {
    
            byte[] Result = null;
            //拼接发送报文
            MS_Entity.ByteArray byteArray = new MS_Entity.ByteArray(12);
            byteArray.Add(new byte[]
            {
    
            0,0,0,0,0,6,1,6
            });
            byteArray.Add((byte)((iAddress - iAddress % 256) / 256));
            byteArray.Add((byte)(iAddress % 256));
            byteArray.Add((byte)((SetValue - SetValue % 256) / 256));
            byteArray.Add((byte)(SetValue % 256));

            //发送报文
            tcpclient.Send(byteArray.array, byteArray.array.Length, SocketFlags.None);
            //接受报文
            byte[] data = new byte[512];
            tcpclient.Receive(data, 512, SocketFlags.None);
            //解析报文
            Result = ByteMsgToRes(data, 0, 12);
            //判断报文
            if (Result.ToString() == byteArray.array.ToString())
            {
    
                return true;
            }
            else
            {
    
                return false;
            }
        }
        #endregion

        #region 预置双字寄存器 功能码0x10
        /// <summary>
        /// 预置双字寄存器 功能码0x10
        /// </summary>
        /// <param name="iAddress"></param>
        /// <param name="SetValue"></param>
        /// <returns></returns>
        public bool PreSetFloatReg(int iAddress, float SetValue)
        {
    
            byte[] Result = null;
            //拼接发送报文
            MS_Entity.ByteArray byteArray = new MS_Entity.ByteArray(12);
            byteArray.Add(new byte[]
            {
    
            0,0,0,0,0,11,1,16
            });
            byteArray.Add((byte)((iAddress - iAddress % 256) / 256));
            byteArray.Add((byte)(iAddress % 256));
            byteArray.Add(new byte[]
            {
    
            0,2,4
            });
            byte[] bSetValue = new byte[4];
            bSetValue = BitConverter.GetBytes(SetValue);
            byteArray.Add(bSetValue[1]);
            byteArray.Add(bSetValue[0]);
            byteArray.Add(bSetValue[3]);
            byteArray.Add(bSetValue[2]);
            //发送报文
            tcpclient.Send(byteArray.array, byteArray.array.Length, SocketFlags.None);
            //接受报文
            byte[] data = new byte[512];
            tcpclient.Receive(data, 512, SocketFlags.None);
            //解析报文
            Result = ByteMsgToRes(data, 0, 12);
            byte[] SendRes = ByteMsgToRes(byteArray.array, 0, 12);
            SendRes[6] = 6;
            //判断报文
            if (Result.ToString() == SendRes.ToString())
            {
    
                return true;
            }
            else
            {
    
                return false;
            }

        }
        #endregion

        #region 字节数组截取
        /// <summary>
        /// 字节数组截取
        /// </summary>
        /// <param name="MsgByte"></param>
        /// <param name="Start"></param>
        /// <param name="Length"></param>
        /// <returns></returns>
        public byte[] ByteMsgToRes(byte[] MsgByte, int Start, int Length)
        {
    
            byte[] Result = null;
            if (MsgByte != null && MsgByte.Length > 0)
            {
    
                Result = new byte[Length];
                for (int i = 0; i < Length; i++)
                {
    
                    Result[i] = MsgByte[i + Start];
                }
            }
            return Result;
        }
        #endregion


    }
}

版权声明
本文为[罗迪尼亚的熔岩]所创,转载请带上原文链接,感谢
https://blog.csdn.net/helldoger/article/details/122228139

随机推荐