C#读取Modbus数据

前面连续写了关于SOCKET编程的东西,似乎有点高大上,为了学习而学习。因此这里我们来整点实际应用的东西。C#如何读取Modbus数据,Modbus很多人可能一点都不知道,也正常,隔行如隔山嘛。Modbus在自动化行业就不一样,属于路人皆知的东西,很多设备、程序都与Modbus息息相关。

Modbus这个东西,本人也是个二把刀,只有半瓶水,所以在这里晃荡,写点Modbus东西,也是让自己能理解得更深一点,入门级别的东西,希望能帮助到那些像一些不太了解Modbus,但是又想了解Modbus的同志。至于高手,可以吐槽,当然也可以绕过。

Modbus通讯协议,ModBus网络是一个工业通信系统,由带智能终端的可编程序控制器和计算机通过公用线路或局部专用线路连接而成。其系统结构既包括硬件、亦包括软件。它可应用于各种数据采集和过程监控。

Modbus 协议是应用于电子控制器上的一种通用语言。通过此协议,控制器相互之间、控制器经由网络(例如以太网)和其它设备之间可以通信。它已经成为一通用工业标准。有了它,不同厂商生产的控制设备可以连成工业网络,进行集中监控......

这些都是百度来的,呵呵,别吐槽。网上资料很多,有兴趣的可以自己撸,看多了感觉好像都差不多。个人认为所谓的通讯协议,机器或者设备之间通讯的一种语言。我们人与人交流不也有语言或者文字嘛,机器之间当然可以相互发送信息,只要定好规则即可。Modbus就是与自动化设备用来交流的语言。

关于Modbus对于我们编程的人来说,我们可以大大的缩小理解范围,我们只需要关心与编码有关系东西,对于硬件的那些什么针脚、电缆、信号位都不用太关心,当然如果想成为为一个Modbus方面的专家,那就不同了。

Modbus分两种模式,一种串口模式,一种是TCP/IP模式。串口模式感觉越来越少,现在大多都是TCP/IP模式,所以这里就暂时写TCP/IP

首先要做就是弄SOCKET客户端与设备建立连接,因为前面SOCKET我们已经写过了,那么下面的代码就非常easy了。说简单点,你不要认为是什么Modbus,就认为是一台电脑,开了SOCKET服务在这里。所以如下代码就水到渠成了。

 public bool Open(string ip,int port) {
            try {
                tcpClient = new TcpClient();

                tcpClient.Connect(IPAddress.Parse(ip), port);

                return true;
            }catch(SocketException e){
                string m = string.Format("modbus Client服务器连接错误:{0},ip:{1},port:{2}", e.Message, ip, port);
                LogHelper.WriteLog(m);
                return false;
            }
        }

其中LogHelper代码这里就不写,因为与主题无关,如果想运行上面代码的话,注释就行了,我个人推荐这样的代码也没必要去运行一下,看看能明白就行了。去运行这个事情,有事会耽误太多的时间。这里IP和端口号都是由设备方提供的。真实程序一般都把这两个参数写配置文件中。

设备连上以后,下一步当然就是读取数据。Modbus的基本原理就是程序向设备请求,需要读取哪个数据,设备就会返回相应的数据。我们知道机器或者说是电脑是只认识01001这样的字符串的。所以所谓的Modbus协议,说得简单一点,就是规定这样一个0101字符各代表什么含义。

/// <summary>
        /// 读取数据 Modbus
        /// </summary>
        /// <param name="rData">结果</param>
        /// <param name="id">设备号</param>
        /// <param name="address">设备地址</param>
        /// <param name="len">长度-多少个设备</param>
        /// <returns>数据读取结果 是否成功</returns>
        public bool ReceiveData(ref short[] rData, short id, short address, short len)
        {
            try
            {
                short m = Convert.ToInt16(new Random().Next(2, 20));
                rData = null;

                byte[] bs = Receive(m, id, address, len);
                byte[] b = TrimModbus(bs, m, id, len);

                if (b==null) { return false; }

                List<short> data = new List<short>(255);
                for (int i = 0; i < b.Length-1; i++)
                {
                   if (!Convert.ToBoolean(i & 1))
                   {
                        byte[] temp = new byte[] { b[i+1], b[i] };
                        data.Add(BitConverter.ToInt16(temp, 0));
                   }
                }
                rData = data.ToArray();

                return true;
            }
            catch (Exception e) {
                LogHelper.WriteLog("返回Modbus数据错误"+ e.Message);
                return false;
            }
        }

这个其实更多的是处理数据异常,LogHelper与前面一样,核心好像还不在了,就是那个Receive方法。

        /// <summary>
        /// 读取 Modbus
        ///00 00 00 00 00 0d  01  03  0A 14 00  14 00  14 00  14 00  14 00
        /// </summary>
        /// <param name="m">标示</param>
        /// <param name="id">设备码</param>
        /// <param name="address">开始地址</param>
        /// <param name="len">设备数量</param>
        /// <returns></returns>
        private byte[] Receive(short m, short id, short address, short len)
        {
            try
            {
                if (tcpClient == null || !tcpClient.Connected) { return null; }

                byte[] data = GetSrcData(m, id, address, len);

                //00 00 00 00 00 06 01 03 00 00 00 05
                tcpClient.Client.Send(data, data.Length, SocketFlags.None);

                int size = len * 2 + 9;

                byte[] rData = new byte[size];

                tcpClient.Client.Receive(rData, size, SocketFlags.None);

                //string t1 = TranBytes(rData);

                return rData;

            }catch(SocketException e){
                if (e.ErrorCode != 10004)
                {
                    LogHelper.WriteLog(e.Message);
                }

                if (tcpClient != null) {
                    tcpClient.Close();
                    tcpClient = null;
                }

                return null;
            }
        }
        #endregion

上面的代码可以说是Modbus协议核心,其实就是SOCKET发送数据和接受数据,发送是告诉主机需要取那些的数据。接受就是把主机返回来的数据接受过来。

//发送
        //00 00 00 00 00 06 01 03 00 00 00 05
        /// <summary>
        /// 发送字节数
        /// </summary>
        /// <param name="m"></param>
        /// <param name="len"></param>
        /// <param name="id"></param>
        /// <param name="address"></param>
        /// <returns></returns>
        private byte[] GetSrcData(short m, short id, short add, short len)
        {
            List<byte> data = new List<byte>(255);

            data.AddRange(ValueHelper.Instance.GetBytes(m));                     //             00 01
            data.AddRange(new byte[] { 0x00, 0x00 });                            //             00 00
            data.AddRange(ValueHelper.Instance.GetBytes(Convert.ToInt16(6)));    //字节数       00 06
            data.Add(Convert.ToByte(id));                                        //路由码       01
            data.Add(Convert.ToByte(3));                                         //功能码 3-读  03
            data.AddRange(ValueHelper.Instance.GetBytes(add));                   //开始地址     00 00
            data.AddRange(ValueHelper.Instance.GetBytes(len));                   //设备数量     00 05
            return data.ToArray();
        }

好,到这里基本上搞定了。其实很多逻辑都在代码中,说简单的就是某个ID设备,从哪个地址开始,读几个设备的值,这里需要注意是short 不要用32位的int去替换,结果会不一样的。仔细看看估计大家都能明白,也没有什么神秘的东西。

哦,对了还ValueHelper代码

using System;
using System.Collections.Generic;
using System.Text;

namespace Modbus {
    public class ValueHelper
    {
        #region 大小端判断

        public static bool LittleEndian = false;

        static ValueHelper()
        {
            unsafe
            {
                int tester = 1;
                LittleEndian = (*(byte*)(&tester)) == (byte)1;
            }
        }
        #endregion

        #region Factory
        public static ValueHelper _Instance = null;
        internal static ValueHelper Instance
        {
            get
            {
                if (_Instance == null)
                {
                    _Instance = LittleEndian ? new LittleEndianValueHelper() : new ValueHelper();
                    //_Instance = new ValueHelper();
                }
                return _Instance;
            }
        }
        #endregion

        protected ValueHelper()
        {

        }

        public virtual Byte[] GetBytes(short value)
        {
            return BitConverter.GetBytes(value);
        }

        public virtual Byte[] GetBytes(int value)
        {
            return BitConverter.GetBytes(value);
        }

        public virtual Byte[] GetBytes(float value)
        {
            return BitConverter.GetBytes(value);
        }

        public virtual Byte[] GetBytes(double value)
        {
            return BitConverter.GetBytes(value);
        }

        public virtual short GetShort(byte[] data)
        {
            return BitConverter.ToInt16(data, 0);
        }

        public virtual int GetInt(byte[] data)
        {
            return BitConverter.ToInt32(data, 0);
        }

        public virtual float GetFloat(byte[] data)
        {
            return BitConverter.ToSingle(data, 0);
        }

        public virtual double GetDouble(byte[] data)
        {
            return BitConverter.ToDouble(data, 0);
        }
    }

    internal class LittleEndianValueHelper : ValueHelper
    {
        public override Byte[] GetBytes(short value)
        {
            return this.Reverse(BitConverter.GetBytes(value));
        }

        public override Byte[] GetBytes(int value)
        {
            return this.Reverse(BitConverter.GetBytes(value));
        }

        public override Byte[] GetBytes(float value)
        {
            return this.Reverse(BitConverter.GetBytes(value));
        }

        public override Byte[] GetBytes(double value)
        {
            return this.Reverse(BitConverter.GetBytes(value));
        }

        public virtual short GetShort(byte[] data)
        {
            return BitConverter.ToInt16(this.Reverse(data), 0);
        }

        public virtual int GetInt(byte[] data)
        {
            return BitConverter.ToInt32(this.Reverse(data), 0);
        }

        public virtual float GetFloat(byte[] data)
        {
            return BitConverter.ToSingle(this.Reverse(data), 0);
        }

        public virtual double GetDouble(byte[] data)
        {
            return BitConverter.ToDouble(this.Reverse(data), 0);
        }

        private Byte[] Reverse(Byte[] data)
        {
            Array.Reverse(data);
            return data;
        }
    }
}

代码都贴大概都贴出来,但是如果实在想运行的,也是需要简单整理的。

时间: 2024-10-04 05:23:08

C#读取Modbus数据的相关文章

[渣译文] 使用 MVC 5 的 EF6 Code First 入门 系列:为ASP.NET MVC应用程序读取相关数据

这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译,这里是第六篇:为ASP.NET MVC应用程序读取相关数据 原文:Reading Related Data with the Entity Framework in an ASP.NET MVC Application 译文版权所有,谢绝全文转载--但您可以在您的网站上添加到该教程的链接. 在之前的教程中您已经完成了学校数据模型.在本教程中你将

读取数据库数据,并将数据整合成3D饼图在jsp中显示

首先我将生成饼图的方法独立写成一个PieChar.java类,详细代码如下:(数据库需要自己建,如有需要的话) 1 import java.io.IOException; 2 import java.sql.SQLException; 3 import org.jfree.chart.ChartFactory; 4 import org.jfree.chart.JFreeChart; 5 import org.jfree.data.general.DefaultPieDataset; 6 7 p

asp.net读取Excel数据

先通过控件FileUpload获取excel文件路径 protected void btnReadExcelFromFileUpload_Click(object sender, EventArgs e) { if (fupExcel.PostedFile.ContentLength > 0) { //获取全路径 string fullFileName = fupExcel.PostedFile.FileName.ToString(); //获取文件名 string fileName = fup

Jquery Mobile实例--利用优酷JSON接口读取视频数据

本文将介绍,如何利用JqueryMobile调用优酷API JSON接口显示视频数据. (1)注册用户接口. 首页,到 http://open.youku.com 注册一个账户,并通过验证.然后找到API接口 (http://open.youku.com/docs/tech_doc.html) 可以看到优酷提供不少API,本文将演示“通过视频关键词”接口. 点击进去后,会发现client_id和keyword是必填的,因此,未来构造的URL应该类似 https://openapi.youku.c

使用 Http 的 Get 方式读取网络数据

作为移动平台的应用,一定避免不了与网络交换数据,不论是读取网页数据,还是调用API接口,都必须掌握Http通信技术 代码如下: package zw1; import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.MalformedURLException;import java.net.UR

Qt通过odbc读取excel数据

传统的读取方式是通过Excel.Application,这种方式不仅操作繁琐,而且速度也不快. 通过odbc读取,可以使用select语句直接读取整个工作表,处理excel数据就跟数据库一样方便. 当然,这种方式也有不足: 1.excel表格必须只能有一行表头. 2.相对于Excel.Application,无法准确定位单元格. 3.工作表名相当于数据库表名,表头相当于字段名,所以excel格式必须的固定的,否则无法读取到数据 读取的代码如下: //文件路径 QString filePath;

猜想-未做 利用office组件读取excel数据

---未实际使用过 用SQL-Server访问Office的Access和Excel http://blog.sina.com.cn/s/blog_964237ea0101532x.html 2007 Office system 驱动程序:数据连接组件 http://www.microsoft.com/zh-CN/download/details.aspx?id=23734 2007 Office system 驱动程序:数据连接组件 详情 版本:All File Name:AccessData

Openxml入门---Openxm读取Excel数据

Openxml读取Excel数据: 有些问题,如果当Cell 里面是 日期和浮点型的话,对应的Cell.DataType==Null,对应的时间会转换为一个浮点型,对于这块可以通过DateTime.FromOADate(double d)转换为时间. 可是缺点的地方就是,如果Cell.DataType ==NULL, 根本无法确认这个数据到底是 浮点型还是[被转换为了日期的浮点数].查阅了很多国外资料,的确国外博客有一部分都反映了.有关Openxml读取Excel时Cell.DataType==

NPOI操作excel——利用反射机制,NPOI读取excel数据准确映射到数据库字段

> 其实需求很明确,就是一大堆不一样的excel,每张excel对应数据库的一张表,我们需要提供用户上传excel,我们解析数据入库的功能实现. 那么,这就涉及到一个问题:我们可以读出excel的表头,但是怎么知道每个表头具体对应数据库里面的字段呢? 博主经过一段时间的思考与构思,想到一法:现在的情况是我们有excel表A,对应数据库表B,但是A与B具体属性字段的映射关系我们不知.那我们是不是可以有一个A到B的映射文件C呢? 我想,说到这,大家就很明了了... 第一步:为每张excel创建一个与