1. 程式人生 > >工業通信的開源項目 HslCommunication 介紹

工業通信的開源項目 HslCommunication 介紹

角色 就是 ss7 三方 ODB dev targe 過程 ble

前言:


本項目的孵化說來也是機緣巧合的事,本人於13年大學畢業後去了一家大型的國企工作,慢慢的走上了工業軟件,上位機軟件開發的道路。於14年正式開發基於windows的軟件,當時可選的技術棧就是MFC和C#的winform,後來就發現C#的更為簡單一些,那就直接幹,先做再說。工廠的基本主要需求就是工藝參數的歷史追述,附帶一些實時監控設備的功能,那麽第一道攔路虎就是如何將三菱的PLC(邏輯控制器,通常作為設備的核心控制單元)的數據給拿到我的軟件中來呢?這真是一個棘手的問題啊,首先就是百度,搜索到了MX component組件,初步試了試,真的比較麻煩。然後就去看看有沒有其他的方式實現,後來就搜索到了三菱的以太網模塊,下載到了支持的通訊說明,長篇大論。邊測試邊開發,勉勉強強讀到了我想要讀的數據(當然,這時候的代碼基本都是寫死的),在接下來的兩三年裏,接觸並開發了好幾個類似的項目,通常工業軟件的需求是采集,分析,存數據庫,顯示。後來對通信的理解深入,由單機軟件發展成了CS架構的軟件。後來在17年趁著換工作和考駕照的間隙,梳理了上份工作積累的經驗,和實際的需求,整理成了HslCommunication,並將之開源出來,初步的功能是三菱PLC的數據讀寫,C#軟件之間的數據通信。後來又集成了modbus協議,西門子,歐姆龍,ab plc,三菱串口等等,都是後話的事了。

做這個項目的目標和開源的初衷是方便廣大的像我這種的在工廠一線的軟件工程師,我一直覺得我們不應該把自己看做是程序員,程序員的角色更像是碼農,主要工作就是敲代碼,而軟件工程師應該是更大的定義,設計軟件的整體架構和開發的。這幾年大多數工作都開始意識到工業軟件,上位機軟件,數據追述系統,SCADA軟件,MES軟件開發的重要性,所以像我這樣的有通信需求的人應該不在少數,況且開源有助於別人來一起改進,和代碼測試。所以在開源之後,在博客園就陸陸續續的寫了一些文章,比如如何使用C#和三菱PLC通訊,C#和西門子通訊等等。從博客園的點擊量來看,確實有大量的工廠的程序員有這方便的需求,而直接采用socket來開發,比較晦澀難懂,坑又比較多,事實上確實有很多人來報告了bug。幫助我修復了這個組件,提高了穩定性。

HslCommunication 能幹什麽?


相比大多數人比較關心這個問題,簡單的說,這個組件主要是用於工業通信的,也有兩個程序之間的通訊,還有其他雜七雜八的功能,更像是我的工具插件。各種小功能,擴展的小功能等等。直接上圖:

技術分享圖片

這是這個開源項目的demo程序,基本上將80%的功能列舉出來了,當然還有一些小功能沒有列舉。大多數支持的設備都在上面進行顯示了,可以方便的進行測試,看看是不是可以實現讀寫的操作(對現場實際在生產的設備應當註意寫入不正確的數據會導致意外事故發生)。比如我們來看看三菱的PLC的demo程序:

技術分享圖片

其他的截圖畫面就不一一舉例了,都是類似或是基本類似的。可以方便的使用demo進行測試。

特別註意,本組件實現的所有的通訊都是基於socket直接實現的,通信部分不依賴任何第三方通訊庫或是組件安裝,也就是說,你拿個dll可以直接和PLC通訊,這對於部署,開發調試,升級都是非常方便的。

通訊核心說明


整個網絡類的核心在於 NetworkBase

技術分享圖片

這個類實現了基礎的字節收發功能和連接斷開功能。接下來就是 NetworkDoubleBase 類的實現,實現了長短連接的操作,在我們實際讀寫設備的過程中,網絡狀況往往是差別很大,所以本項目的初衷就是同時支持長連接和短連接。

技術分享圖片

實現了長短的連接後,還要實現設備的BCL類型的讀寫,本質是基於byte數組和C#基礎類型的轉換,但是這裏有個問題,不同的PLC,modbus協議對於轉換的格式不是固定的,有可能是一樣的,有可能不是一樣的,所以又抽象出來一個 IByteTransform 接口

技術分享圖片

這個接口集成到了下面的設備交互的基類 NetworkDeviceBase 裏,這個基類實現了一些基礎的類型的數據讀寫。

技術分享圖片

所以到這裏可以看到,從NetworkDeviceBase類繼承出去的設備類(大部分的設備通信協議都是從這個繼承出去的),其基本的讀寫代碼都是一致的,關於解析協議,通信的底層都是封裝完畢,

通訊舉例說明


先舉例說明三菱PLC的讀寫操作:

            // 實例化對象,指定PLC的ip地址和端口號
            MelsecMcNet melsecMc = new MelsecMcNet( "192.168.1.110", 6000 );

            // 連接對象
            OperateResult connect = melsecMc.ConnectServer( );
            if (!connect.IsSuccess)
            {
                Console.WriteLine( "connect failed:" + connect.Message );
                return;
            }

            // 舉例讀取D100的值
            short D100 = melsecMc.ReadInt16( "D100" ).Content;

            melsecMc.ConnectClose( );

經過層層封裝後,讀寫的邏輯精簡為,實例化,連接,讀寫,關閉。無論是三菱的PLC,還是西門子的PLC,都是一致的,因為基類的模型都是一致的。

            // 實例化對象,指定PLC的ip地址和端口號
            SiemensS7Net siemens = new SiemensS7Net( SiemensPLCS.S1200, " 192.168.1.110" );

            // 連接對象
            OperateResult connect = siemens.ConnectServer( );
            if (!connect.IsSuccess)
            {
                Console.WriteLine( "connect failed:" + connect.Message );
                return;
            }

            // 舉例讀取M100的值
            short M100 = siemens.ReadInt16( "M100" ).Content;

            siemens.ConnectClose( );

當然,支持大多數的C#類型數據讀寫

            MelsecMcNet melsec_net = new MelsecMcNet( "192.168.0.100", 6000 );

            // 此處以D寄存器作為示例
            short short_D1000 = melsec_net.ReadInt16( "D1000" ).Content;         // 讀取D1000的short值 
            ushort ushort_D1000 = melsec_net.ReadUInt16( "D1000" ).Content;      // 讀取D1000的ushort值
            int int_D1000 = melsec_net.ReadInt32( "D1000" ).Content;             // 讀取D1000-D1001組成的int數據
            uint uint_D1000 = melsec_net.ReadUInt32( "D1000" ).Content;          // 讀取D1000-D1001組成的uint數據
            float float_D1000 = melsec_net.ReadFloat( "D1000" ).Content;         // 讀取D1000-D1001組成的float數據
            long long_D1000 = melsec_net.ReadInt64( "D1000" ).Content;           // 讀取D1000-D1003組成的long數據
            ulong ulong_D1000 = melsec_net.ReadUInt64( "D1000" ).Content;        // 讀取D1000-D1003組成的long數據
            double double_D1000 = melsec_net.ReadDouble( "D1000" ).Content;      // 讀取D1000-D1003組成的double數據
            string str_D1000 = melsec_net.ReadString( "D1000", 10 ).Content;     // 讀取D1000-D1009組成的條碼數據

            // 讀取數組
            short[] short_D1000_array = melsec_net.ReadInt16( "D1000", 10 ).Content;         // 讀取D1000的short值 
            ushort[] ushort_D1000_array = melsec_net.ReadUInt16( "D1000", 10 ).Content;      // 讀取D1000的ushort值
            int[] int_D1000_array = melsec_net.ReadInt32( "D1000", 10 ).Content;             // 讀取D1000-D1001組成的int數據
            uint[] uint_D1000_array = melsec_net.ReadUInt32( "D1000", 10 ).Content;          // 讀取D1000-D1001組成的uint數據
            float[] float_D1000_array = melsec_net.ReadFloat( "D1000", 10 ).Content;         // 讀取D1000-D1001組成的float數據
            long[] long_D1000_array = melsec_net.ReadInt64( "D1000", 10 ).Content;           // 讀取D1000-D1003組成的long數據
            ulong[] ulong_D1000_array = melsec_net.ReadUInt64( "D1000", 10 ).Content;        // 讀取D1000-D1003組成的long數據
            double[] double_D1000_array = melsec_net.ReadDouble( "D1000", 10 ).Content;      // 讀取D1000-D1003組成的double數據

寫入的操作:

            MelsecMcNet melsec_net = new MelsecMcNet( "192.168.0.100", 6000 );

            // 此處以D寄存器作為示例
            melsec_net.Write( "D1000", (short)1234 );                // 寫入D1000  short值  ,W3C0,R3C0 效果是一樣的
            melsec_net.Write( "D1000", (ushort)45678 );              // 寫入D1000  ushort值
            melsec_net.Write( "D1000", 1234566 );                    // 寫入D1000  int值
            melsec_net.Write( "D1000", (uint)1234566 );               // 寫入D1000  uint值
            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", 523456661235123534UL );          // 寫入D1000  ulong值
            melsec_net.Write( "D1000", "K123456789" );                // 寫入D1000  string值

            // 讀取數組
            melsec_net.Write( "D1000", new short[] { 123, 3566, -123 } );                // 寫入D1000  short值  ,W3C0,R3C0 效果是一樣的
            melsec_net.Write( "D1000", new ushort[] { 12242, 42321, 12323 } );              // 寫入D1000  ushort值
            melsec_net.Write( "D1000", new int[] { 1234312312, 12312312, -1237213 } );                    // 寫入D1000  int值
            melsec_net.Write( "D1000", new uint[] { 523123212, 213,13123 } );               // 寫入D1000  uint值
            melsec_net.Write( "D1000", new float[] { 123.456f, 35.3f, -675.2f } );                    // 寫入D1000  float值
            melsec_net.Write( "D1000", new double[] { 12343.542312d, 213123.123d, -231232.53432d } );                    // 寫入D1000  double值
            melsec_net.Write( "D1000", new long[] { 1231231242312,34312312323214,-1283862312631823 } );          // 寫入D1000  long值
            melsec_net.Write( "D1000", new ulong[] { 1231231242312, 34312312323214, 9731283862312631823 } );          // 寫入D1000  ulong值

這裏舉例了三菱的PLC,實際上各種PLC的操作都是類似的。

Redis實現


除了上述的基本的設備通信,還實現了redis數據庫讀寫操作,分了兩個類實現,下圖為一般的通信功能

技術分享圖片

同時demo中實現了一個瀏覽redis服務器的界面功能

技術分享圖片

最後的總結


本通信庫實現了.net 3.5 和 .net 4.5的框架,還附帶了一些簡單的控件,此外還實現了.net standard版本,已在linux測試成功,由於官方在.net core2.2中還未實現串口類,所以暫時沒有實現串口相關的。

未來的方向,希望繼續優化代碼,架構,集成實現更多設備通信,方便廣大的網友直接開發測試。

開源地址:https://github.com/dathlin/HslCommunication

更多詳細的內容請查看源代碼的readme文件。

工業通信的開源項目 HslCommunication 介紹