前面连续写了关于SOCKET编程的东西,似乎有点高大上,为了学习而学习。因此这里我们来整点实际应用的东西。C#如何读取Modbus数据,Modbus很多人可能一点都不知道,也正常,隔行如隔山嘛。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
- //发送
- //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;
- }
- }
- }
不好意思,有个方法给忘记了
- private byte[] TrimModbus(byte[] d, short m, short id, short len)
- {
- int size = Convert.ToInt32(len) * 2;
- int dLen = size + 9;
- if (d == null || d.Length != dLen || m != Convert.ToInt16(d[1]) || id != Convert.ToInt16(d[6]))
- {
- return null;
- }
- byte[] n = new byte[size];
- Array.Copy(d, 9, n, 0, size);
- return n;
- }