java android 讀寫三菱PLC 使用TCP/IP 協議


本文將使用一個Github開源的組件庫技術來讀寫三菱PLC和西門子plc數據,使用的是基於以太網的TCP/IP實現,不需要額外的組件,讀取操作只要放到后台線程就不會卡死線程,本組件支持超級方便的高性能讀寫操作

本項目目前支持C#語言和java語言,C#語言的功能比較齊全,java版本的庫還在開發及完善中。

C# 版本nuget地址:https://www.nuget.org/packages/HslCommunication/       nuget     下載

C# 版本github地址:https://github.com/dathlin/HslCommunication      fork      star                     如果喜歡可以star或是fork,還可以打賞支持。

聯系作者及加群方式:http://www.hslcommunication.cn/Cooperation

 

Java版本的Demo代碼 https://github.com/dathlin/HslCommunicationJavaDemo

在Maven里搜索 HslCommunication 即可。

com.github.dathlin:HslCommunication

  

本文將展示如何配置網絡參數及怎樣使用代碼來訪問PLC數據,希望給有需要的人解決一些實際問題。主要對三菱Q系列PLC的X,Y,M,L,B,V,F,S,D,W,R區域的數據讀寫,對西門子PLC的M,Q,I,DB塊的數據讀寫,親測有效。

此處使用了網線直接的方式,如果PLC接進了局域網,就可以進行遠程讀寫了^_^

此處使用到了2個命名空間:

import HslCommunication.Core.Types.OperateResultExOne;
import HslCommunication.Profinet.Melsec.MelsecMcNet;

 

訪問測試項目


下載JAVA版本的Demo源代碼,然后運行即可,當然你也可以使用C#版本的來測試。

https://github.com/dathlin/HslCommunicationJavaDemo

 

 

 

隨便聊聊


當我們一個上位機需要讀取100台西門子PLC設備(此處只是舉個例子,凡是都是使用Modbus tcp的都是一樣的)的時候,你采用服務器主動去請求100台設備的機制對性能來說是個極大的考驗,如果開100個線程去輪詢100台設備,那么性能損失將是非常大的,更不用說再增加設備,如果搭建Modbus tcp服務器,就可以完美的解決性能問題,因為連接的壓力將會平均分攤給每一台PLC,服務器端只要新增一個時間戳就可以知道客戶端有沒有連接上。

我們在100台PLC里都增加發送Modbus tcp方法,將數據發送到服務器的ip和端口上去,服務器根據站號來區分設備。這樣就可以搭建一個高性能總站。 本組件支持快速搭建一個高性能的Modbus tcp總站。

http://www.cnblogs.com/dathlin/p/7782315.html

 

關於兩種模式


在PLC端,包括三菱,西門子,歐姆龍以及Modbus Tcp客戶端的訪問器上,都支持兩種模式,短連接模式和長連接模式,現在就來解釋下什么原理。

短連接:每次讀寫都是一個單獨的請求,請求完畢也就關閉了,如果服務器的端口僅僅支持單連接,那么關閉后這個端口可以被其他連接復用,但是在頻繁的網絡請求下,容易發生異常,會有其他的請求不成功,尤其是多線程的情況下。

長連接:創建一個公用的連接通道,所有的讀寫請求都利用這個通道來完成,這樣的話,讀寫性能更快速,即時多線程調用也不會影響,內部有同步機制。如果服務器的端口僅僅支持單連接,那么這個端口就被占用了,比如三菱的端口機制,西門子的Modbus tcp端口機制也是這樣的。以下代碼默認使用長連接,性能更高,還支持多線程同步。

在短連接的模式下,每次請求都是單獨的訪問,所以沒有重連的困擾,在長連接的模式下,如果本次請求失敗了,在下次請求的時候,會自動重新連接服務器,直到請求成功為止。另外,盡量所有的讀寫都對結果的成功進行判斷。

 

關於日志記錄


暫時不支持

 

PLC的配置


 

環境1:此處以GX Works3為示例,fx5u的配置如下:(感謝 山楂 提供的圖片)

 

環境2:此處以GX Works2為示例,測試PLC為L02CPU,內置了以太網協議

 


環境3:此處以GX Works2為示例,添加以太網模塊,型號為QJ71E71-100,組態里添加完成后進行以太網的參數配置,此處需要注意的是:參數的配置對接下來的代碼中配置參數要一一對應




注意:在PLC的以太網模塊的配置中,無法設置網絡號為0,也無法設置站號為0, 所以此處均設置為1,在C#程序中也使用上述的配置,在代碼中均配置為0,如果您自定義設置為網絡2, 站號8,那么在代碼中就要寫對應的數據。如果仍然通信失敗,重新測試0,0。

打開設置:在上圖中的打開設置選項,進行其他參數的配置,下圖只是舉了一個例子,開通了4個端口來支持讀寫操作:

 

 

 



端口號設置規則:

  • 為了不與原先存在的系統發生沖突,您在添加自己的端口時盡量使用您自己的端口。
  • 如果讀寫都需要,盡可能的將讀取端口和寫入端口區分開來,這樣做比較高性能。
  • 如果您的網絡狀態不是特別穩定,讀取端口使用2個,一個受阻切換另一個讀取可以提升系統的穩定性。


本文檔僅作組件的測試,所以只用了一個端口作為讀寫。如果你的程序也使用了一個端口,那么你在讀取數據時候, 剛好也在寫入(異步操作可能發生這樣的情況),那么寫入會失敗!)(在長連接模式下沒有這個問題)

三菱PLC的數據主要由兩類數據組成,位數據和字數據,在位數據中,例如X,Y,M,L都是位數據,字數據例如D,W。 兩類的數據在讀取解碼上存在一點小差別。(事實上也可以先將16個M先賦值給一個D,讀取D數據再進行解析, 在讀取M的數量比較多的時候,這樣操作效率更高)

初始化訪問PLC對象

如果想使用本組件的數據讀取功能,必須先初始化數據訪問對象,根據實際情況進行數據的填入。 下面僅僅是測試中的數據:

MelsecMcNet melsec_net = new MelsecMcNet("192.168.1.192",6001);

  

 

然后你可以指定一些參數,網絡號,網絡站號之類的,通常的情況都是不需要指定的

        melsec_net.setNetworkNumber((byte) 0x00);
        melsec_net.setNetworkStationNumber((byte) 0x00);
        melsec_net.setConnectTimeOut(1000);

  

  

打開連接

melsec_net.ConnectServer();

如果想知道有沒有連接上去

        OperateResult connectResult = melsec_net.ConnectServer();
        if(connectResult.IsSuccess){
            System.out.print("連接成功");
        }
        else {
            System.out.print("連接失敗:"+connectResult.Message);
        }

  

  

關於地址的表示方式

使用字符串表示,這個組件里所有的讀寫操作提供字符串表示的重載方法,所有的支持訪問的類型對應如下,字符串的表示方式存在十進制和十六進制的區別:

  • 輸入繼電器:"X100","X1A0"            // 字符串為十六進制機制
  • 輸出繼電器:"Y100" ,"Y1A0"           // 字符串為十六進制機制
  • 內部繼電器:"M100","M200"           // 字符串為十進制
  • 鎖存繼電器:"L100"  ,"L200"           // 字符串為十進制
  • 報警器:       "F100", "F200"            // 字符串為十進制
  • 邊沿繼電器:"V100" , "V200"          // 字符串為十進制
  • 鏈接繼電器:"B100" , "B1A0"          // 字符串為十六進制
  • 步進繼電器:"S100" , "S200"          // 字符串為十進制
  • 數據寄存器:"D100", "D200"           // 字符串為十進制
  • 鏈接寄存器:"W100" ,"W1A0"         // 字符串為十六進制
  • 文件寄存器:"R100","R200"            // 字符串為十進制

關於數據分類

以上地址的數據是分為位數據和字數據的,位數據只能調用ReadBool,字數據用Read及其擴展的方法

 

簡單讀寫的示例

        boolean[] M100 = melsec_net.ReadBool("M100",(short) 1).Content;            // 讀取M100是否通,十進制地址
        boolean[] X1A0 = melsec_net.ReadBool("X1A0",(short) 1).Content;            // 讀取X1A0是否通,十六進制地址
        boolean[] Y1A0 = melsec_net.ReadBool("Y1A0",(short) 1).Content;            // 讀取Y1A0是否通,十六進制地址
        boolean[] B1A0 = melsec_net.ReadBool("B1A0",(short) 1).Content;            // 讀取B1A0是否通,十六進制地址
        short short_D1000 = melsec_net.ReadInt16("D1000").Content;                 // 讀取D1000的short值  ,W3C0,R3C0 效果是一樣的
        int int_D1000 = melsec_net.ReadInt32("D1000").Content;                     // 讀取D1000-D1001組成的int數據
        float float_D1000 = melsec_net.ReadFloat("D1000").Content;                 // 讀取D1000-D1001組成的float數據
        long long_D1000 = melsec_net.ReadInt64("D1000").Content;                   // 讀取D1000-D1003組成的long數據
        double double_D1000 = melsec_net.ReadDouble("D1000").Content;              // 讀取D1000-D1003組成的double數據
        String str_D1000 = melsec_net.ReadString("D1000", (short) 10).Content;     // 讀取D1000-D1009組成的條碼數據



        melsec_net.Write("M100", new boolean[] { true} );                          // 寫入M100為通
        melsec_net.Write( "Y1A0", new boolean[] { true } );                        // 寫入Y1A0為通
        melsec_net.Write( "X1A0", new boolean[] { true } );                        // 寫入X1A0為通
        melsec_net.Write( "B1A0", new boolean[] { true } );                        // 寫入B1A0為通
        melsec_net.Write( "D1000", (short)1234);                                   // 寫入D1000  short值  ,W3C0,R3C0 效果是一樣的
        melsec_net.Write( "D1000", 1234566);                                // 寫入D1000  int值
        melsec_net.Write( "D1000", 123.456f);                               // 寫入D1000  float值
        melsec_net.Write( "D1000", 123.456d);                               // 寫入D1000  double值
        melsec_net.Write( "D1000", 123456661235123534L);                    // 寫入D1000  long值
        melsec_net.Write( "D1000", "K123456789");                           // 寫入D1000  string值

  

X,Y,M,L,F,V,B,S位數據的讀寫說明

  • X 輸入繼電器
  • Y 輸出繼電器
  • M 內部繼電器
  • L 鎖存繼電器
  • F 報警器
  • V 邊沿繼電器
  • B 鏈接繼電器
  • S 步進繼電器

 

本小節將展示八種位數據的讀取,雖然更多的時候只是讀取D數據即可,或者是將位數據批量挪到D數據中, 但是在此處仍然進行介紹單獨的讀取X,Y,M,L,F,V,B,S,由於這八種讀取手法一致,故針對M數據進行介紹,其他的您可以自己測試。

如下方法演示讀取了M200-M209這10個M的值,注意:讀取長度必須為偶數,即時寫了奇數,也會補齊至偶數,讀取和寫入的最大長度為7168,否則報錯。如需實際需求確實大於7168的,請分批次讀取。
返回值解析:如果讀取正常則共返回10個字節的數據,以下示例數據進行批量化的讀取

 OperateResultExOne<boolean[]> read = melsec_net.ReadBool("M100",(short)10);
        if(read.IsSuccess){
            boolean m100 = read.Content[0];
            boolean m101 = read.Content[1];
            boolean m102 = read.Content[2];
            boolean m103 = read.Content[3];
            boolean m104 = read.Content[4];
            boolean m105 = read.Content[5];
            boolean m106 = read.Content[6];
            boolean m107 = read.Content[7];
            boolean m108 = read.Content[8];
            boolean m109 = read.Content[9];
        }
        else {
            System.out.print("讀取失敗:"+read.Message);
        }

  

        // 寫入測試,M100-M104 寫入測試 此處寫入后M100:通 M101:斷 M102:斷 M103:通 M104:通
        boolean[] values = new boolean[]{true,false,false,true,true};
        OperateResult write = melsec_net.Write("M100",values);
        if(write.IsSuccess){
            System.out.print("寫入成功");
        }
        else {
            System.out.print("寫入失敗:"+write.Message);
        }

  

 

D,W,R字數據的讀寫操作

此處讀取針對中間存在整數數據的情況,因為兩者讀取方式相同,故而只演示一種數據讀取, 使用該組件讀取數據,一次最多讀取或寫入960個字,超出則失敗。 如果讀取的長度確實超過限制,請考慮分批讀取。

 

        OperateResultExOne<byte[]> read1 = melsec_net.Read("D100",(short)5);
        if(read1.IsSuccess){
            short D100 = melsec_net.getByteTransform().TransByte(read1.Content,0);
            short D101 = melsec_net.getByteTransform().TransByte(read1.Content,2);
            short D102 = melsec_net.getByteTransform().TransByte(read1.Content,4);
            short D103 = melsec_net.getByteTransform().TransByte(read1.Content,6);
            short D104 = melsec_net.getByteTransform().TransByte(read1.Content,8);
        }
        else {
            System.out.print("讀取失敗:"+read1.Message);
        }

  

        // D100為1234,D101為8765,D102為1234,D103為4567,D104為-2563
        short[] values2 = new short[]{1335, 8765, 1234, 4567, -2563 };
        OperateResult write = melsec_net.Write("M100",values2);
        if(write.IsSuccess){
            System.out.print("寫入成功");
        }
        else {
            System.out.print("寫入失敗:"+write.Message);
        }

 

一個實際中復雜的例子演示

實際中可能碰到的情況會很復雜,一台設備中需要上傳的數據包含了溫度,壓力,產量,規格等等信息,在一串數據中 會包含各種各樣的不同的數據,上述的讀取D,讀取M,讀取條碼的方式不太好用,所以此處做一個完整示例的演示,假設我們需要讀取 D4000-D4009的數據,假設D4000存放了溫度數據,55.1℃在D中為551,D4001存放了壓力數據,1.23MPa在D中存放為123,D4002存放了 設備狀態,0為停止,1為運行,D4003存放了產量,1000就是指1000個,D4004備用,D4005-D4009存放了規格,以下代碼演示如何去解析數據:

 

        //解析復雜數據
        OperateResultExOne<byte[]> read3 = melsec_net.Read("D4000", (short) 10);
        if (read3.IsSuccess)
        {
            double 溫度 = melsec_net.getByteTransform().TransInt16(read3.Content, 0) / 10d;//索引很重要
            double 壓力 = melsec_net.getByteTransform().TransInt16(read3.Content, 2) / 100d;
            boolean IsRun = melsec_net.getByteTransform().TransInt16(read3.Content, 4) == 1;
            int 產量 =melsec_net.getByteTransform().TransInt32(read3.Content, 6);
            String 規格 = melsec_net.getByteTransform().TransString(read3.Content, 10, 10,"ascii");
        }
        else
        {
            System.out.print("讀取失敗:"+read3.Message);
        }

  

更詳細的信息,可以參照源代碼里面的測試項目。

 


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM