java android 读写三菱PLC 使用TCP/IP 协议
本文將使用一個Github開源的組件庫技術來讀寫三菱PLC和西門子plc數據,使用的是基于以太網的TCP/IP實現,不需要額外的組件,讀取操作只要放到后臺線程就不會卡死線程,本組件支持超級方便的高性能讀寫操作
?github地址:https://github.com/dathlin/HslCommunication?如果喜歡可以star或是fork,還可以打賞支持,打賞請認準源代碼項目。
本項目目前支持C#語言和java語言,C#語言的功能比較齊全,java版本的庫還在開發及完善中。
?
nuget地址:https://www.nuget.org/packages/HslCommunication/????????????
github地址:https://github.com/dathlin/HslCommunication????????? ?????????????????????? 如果喜歡可以star或是fork,還可以打賞支持。
?
點擊下載本組件的jar包。HslCommunication.jar
代碼使用支持的例子可以參照C#版本的,兩者幾乎是一模一樣的,支持的數據類型也是一致的。
?
?
組件的完整信息和API介紹參照:http://www.cnblogs.com/dathlin/p/7703805.html?? 組件的使用限制,更新日志,都在該頁面里面。
?
?
本文將展示如何配置網絡參數及怎樣使用代碼來訪問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;
?
隨便聊聊
當我們一個上位機需要讀取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個字節的數據,以下示例數據進行批量化的讀取
// 寫入測試,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為-2563short[] 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);}
更詳細的信息,可以參照源代碼里面的測試項目。
?
?
轉載于:https://www.cnblogs.com/dathlin/p/9176069.html
總結
以上是生活随笔為你收集整理的java android 读写三菱PLC 使用TCP/IP 协议的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RISC-V MCU将常量定义到指定的F
- 下一篇: STM32的Flash地址空间的数据读取