標籤:c# socket 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; } }}代碼都貼大概都貼出來,但是如果實在想啟動並執行,也是需要簡單整理的。
C#讀取Modbus資料