本文將使用一個gitHub開源的組件技術來讀寫西門子plc數據,使用的是基於以太網的TCP/IP實現,不需要額外的組件,讀取操作只要放到后台線程就不會卡死線程,本組件支持超級方便的高性能讀寫操作
github地址:https://github.com/dathlin/HslCommunication 如果喜歡可以star或是fork,還可以打賞支持。
聯系作者及加群方式(激活碼在群里發放):http://www.hslcommunication.cn/Cooperation
在Visual Studio 中的NuGet管理器中可以下載安裝,也可以直接在NuGet控制台輸入下面的指令安裝
Install-Package HslCommunication
如果需要教程:Nuget安裝教程:http://www.cnblogs.com/dathlin/p/7705014.html
組件的完整信息和其他API介紹參照:http://www.cnblogs.com/dathlin/p/7703805.html 組件的授權協議,更新日志,都在該頁面里面。
本文將展示如何配置網絡參數及怎樣使用代碼來訪問PLC數據,希望給有需要的人解決一些實際問題。主要對西門子PLC的M,Q,I,DB塊的數據讀寫,親測有效。
此處使用了網線直接的方式,如果PLC接進了局域網,就可以進行遠程讀寫了^_^
此處使用到了2個命名空間:
using HslCommunication; using HslCommunication.Profinet.Omron;
隨便聊聊
當我們一個上位機需要讀取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
關於兩種模式
本組件所提供的所有客戶端類,包括三菱,西門子,歐姆龍,modbus-tcp,以及SimplifyNet都是繼承自雙模式基類,雙模式包含了短連接和長連接,下面就具體介紹下兩個模式的區別
短連接:每次讀寫都是一個單獨的請求,請求完畢也就關閉了,如果服務器的端口僅僅支持單連接,那么關閉后這個端口可以被其他連接復用,但是在頻繁的網絡請求下,容易發生異常,會有其他的請求不成功,尤其是多線程的情況下。
長連接:創建一個公用的連接通道,所有的讀寫請求都利用這個通道來完成,這樣的話,讀寫性能更快速,即時多線程調用也不會影響,內部有同步機制。如果服務器的端口僅僅支持單連接,那么這個端口就被占用了,比如三菱的端口機制,西門子的Modbus tcp端口機制也是這樣的。以下代碼默認使用長連接,性能更高,還支持多線程同步。
在短連接的模式下,每次請求都是單獨的訪問,所以沒有重連的困擾,在長連接的模式下,如果本次請求失敗了,在下次請求的時候,會自動重新連接服務器,直到請求成功為止。另外,盡量所有的讀寫都對結果的成功進行判斷。
關於日志記錄
不管是三菱的數據訪問類,還是西門子的,還是Modbus tcp訪問類,都有一個LogNet屬性用來記錄日志,該屬性是一個接口類,ILogNet,凡事繼承該接口的都可以用來記錄日志,該日志會在訪問失敗時,尤其是因為網絡的原因導致訪問失敗時會進行日志記錄(如果你為這個 LogNet 屬性配置了真實的日志記錄器的話):如果你想使用該記錄日志的功能,請參照如下的博客進行實例化:
http://www.cnblogs.com/dathlin/p/7691693.html
舉個例子:
omronFinsNet.LogNet = new HslCommunication.LogNet.LogNetSingle( "omron.log.txt" );
關於通訊的說明
使用FINS-TCP協議實現數據交互,如果在測試的過程中,發現寫入失敗的話,有可能是因為PLC側進行了寫保護操作。
訪問測試項目
在上述的github源代碼里有個測試項目,HslCommunicationDemo,里面包含了各種客戶端的Demo項目,不需要編寫任何的代碼就可以測試數據的訪問了。
下載地址為:HslCommunicationDemo.zip

演示項目
下面的三篇演示了具體如何去訪問PLC的數據,我們在訪問完成后,通常需要進行處理,以下的示例項目就演示了后台從PLC讀取數據后,前台顯示並推送給所有在線客戶端的功能,客戶端並進行圖形化顯示,具有一定的參考意義,項目地址為:
https://github.com/dathlin/RemoteMonitor
下面的圖片示例中的左邊程序就是服務器程序,它應該和PLC直接連接並接入局域網,然后把數據推送給客戶端顯示。注意:一個復雜高級的程序就應該把處理邏輯程序和界面程序分開,比如這里的服務器程序實現數據采集,推送,存儲。讓客戶端程序去實現數據的整理,分析,顯示,這樣即使客戶端程序因為BUG奔潰,服務器端仍然可以正常的工作。
演示項目使用的西門子訪問,可以方便的改成歐姆龍的項目
地址支持
支持地址及示例如下:
- DM區 D100
- CIO區 C100
- WR區 W100
- HR區 H100
- AR區 A100
特別感謝
- 感謝 酒罷舞 對本組件的測試
- 感謝 一貝水 對本組件的測試
實例化
private OmronFinsNet omronFinsNet = new OmronFinsNet( "192.168.0.100", 6000 );
在連接服務器前需要設置三個參數,PLC的單元號,PLC的網絡節點,PC的網絡節點,如下是舉例,你需要根據實際情況來填寫。
omronFinsNet.SA1 = 0x20; // PC網絡號,PC的IP地址的最后一個數
omronFinsNet.DA1 = 0x10; // PLC網絡號,PLC的IP地址的最后一個數
omronFinsNet.DA2 = 0x00; // PLC單元號,通常為0
連接服務器,也可以放在窗口的Load方法中,一般建議使用長連接,速度更快,又是線程安全的(調用下面的方法就是使用了長連接,如果不連接直接讀取數據,那就是短連接):
try
{
OperateResult connect = omronFinsNet.ConnectServer( );
if (connect.IsSuccess)
{
MessageBox.Show( "連接成功!" );
}
else
{
MessageBox.Show( "連接失敗!" );
}
}
catch (Exception ex)
{
MessageBox.Show( ex.Message );
}
斷開連接,也就是關閉了長連接,如果再去請求數據,就變成了短連接
omronFinsNet.ConnectClose( );
下面就演示一些簡單的數據操作,省去了對結果是否成功的驗證,所有的讀寫結果都是OperateResult類型及派生類型,都有一個IsSuccess屬性來判斷成功與否
// 讀取操作,這里的D100可以替換成C100,A100,W100,H100效果時一樣的
bool D100_7 = omronFinsNet.ReadBool( "D100.7" ).Content; // 讀取D100.7是否通斷,注意D100.0等同於D100
short short_D100 = omronFinsNet.ReadInt16( "D100" ).Content; // 讀取D100組成的字
ushort ushort_D100 = omronFinsNet.ReadUInt16( "D100" ).Content; // 讀取D100組成的無符號的值
int int_D100 = omronFinsNet.ReadInt32( "D100" ).Content; // 讀取D100-D101組成的有符號的數據
uint uint_D100 = omronFinsNet.ReadUInt32( "D100" ).Content; // 讀取D100-D101組成的無符號的值
float float_D100 = omronFinsNet.ReadFloat( "D100" ).Content; // 讀取D100-D101組成的單精度值
long long_D100 = omronFinsNet.ReadInt64( "D100" ).Content; // 讀取D100-D103組成的大數據值
ulong ulong_D100 = omronFinsNet.ReadUInt64( "D100" ).Content; // 讀取D100-D103組成的無符號大數據
double double_D100 = omronFinsNet.ReadDouble( "D100" ).Content; // 讀取D100-D103組成的雙精度值
string str_D100 = omronFinsNet.ReadString( "D100", 5 ).Content;// 讀取D100-D104組成的ASCII字符串數據
// 寫入操作,這里的D100可以替換成C100,A100,W100,H100效果時一樣的
omronFinsNet.Write( "D100", (byte)0x33 ); // 寫單個字節
omronFinsNet.Write( "D100", (short)12345 ); // 寫雙字節有符號
omronFinsNet.Write( "D100", (ushort)45678 ); // 寫雙字節無符號
omronFinsNet.Write( "D100", (uint)3456789123 ); // 寫雙字無符號
omronFinsNet.Write( "D100", 123.456f ); // 寫單精度
omronFinsNet.Write( "D100", 1234556434534545L ); // 寫大整數有符號
omronFinsNet.Write( "D100", 523434234234343UL ); // 寫大整數無符號
omronFinsNet.Write( "D100", 123.456d ); // 寫雙精度
omronFinsNet.Write( "D100", "K123456789" );// 寫ASCII字符串
下面說明復雜的數據操作,以及批量化的數據操作,例如讀取D100-D105
OperateResult<byte[]> read = omronFinsNet.Read( "D100", 5 );
{
if (read.IsSuccess)
{
// 此處需要根據實際的情況來自定義來處理復雜的數據
short D100 = omronFinsNet.ByteTransform.TransInt16( read.Content, 0 );
short D101 = omronFinsNet.ByteTransform.TransInt16( read.Content, 2 );
short D102 = omronFinsNet.ByteTransform.TransInt16( read.Content, 4 );
short D103 = omronFinsNet.ByteTransform.TransInt16( read.Content, 6 );
short D104 = omronFinsNet.ByteTransform.TransInt16( read.Content, 7 );
}
else
{
// 發生了異常
}
}
寫入也是一樣的,可以反着來操作。
如果想實現自定義的數據類型,需要繼承一個接口
public class UserType : HslCommunication.IDataTransfer
{
#region IDataTransfer
private HslCommunication.Core.IByteTransform ByteTransform = new HslCommunication.Core.ReverseWordTransform( );
public ushort ReadCount => 20;
public void ParseSource( byte[] Content )
{
int count = ByteTransform.TransInt32( Content, 0 );
float temp = ByteTransform.TransSingle( Content, 4 );
short name1 = ByteTransform.TransInt16( Content, 8 );
string barcode = Encoding.ASCII.GetString( Content, 10, 10 );
}
public byte[] ToSource( )
{
byte[] buffer = new byte[20];
ByteTransform.TransByte( count ).CopyTo( buffer, 0 );
ByteTransform.TransByte( temp ).CopyTo( buffer, 4 );
ByteTransform.TransByte( name1 ).CopyTo( buffer, 8 );
Encoding.ASCII.GetBytes( barcode ).CopyTo( buffer, 10 );
return buffer;
}
#endregion
#region Public Data
public int count { get; set; }
public float temp { get; set; }
public short name1 { get; set; }
public string barcode { get; set; }
#endregion
}
這樣我們就是可以實現特殊數據的讀寫了
OperateResult<UserType> read = omronFinsNet.ReadCustomer<UserType>( "M100" );
if (read.IsSuccess)
{
UserType value = read.Content;
}
// write value
omronFinsNet.WriteCustomer( "M100", new UserType( ) );
究極數據的讀取:
此處提供一個核心的報文讀取機制,你可以自己傳入自己的報文,然后接收服務器的報文,再自己解析操作,可以根據報文格式實現任意的操作,當然,前提是需要報文支持。假設我要實現讀取D0,D1,那么最終的報文為
46494E530000001A000000020000000080000200210000C000000101820000000002
private void userButton23_Click_1(object sender, EventArgs e)
{
byte[] buffer = HslCommunication.BasicFramework.SoftBasic.HexStringToBytes(
"46494E530000001A000000020000000080000200210000C000000101820000000002");
OperateResult<byte[]> operate = omronFinsNet.ReadFromServerCore(buffer);
if (operate.IsSuccess)
{
// 顯示服務器返回的報文
TextBoxAppendStringLine(HslCommunication.BasicFramework.SoftBasic.ByteToHexString(operate.Content));
}
else
{
// 顯示網絡錯誤
MessageBox.Show(operate.ToMessageShowString());
}
}
更詳細的信息,可以參照源代碼里面的測試項目。
創作不易,感謝打賞

