[轉]https://www.cnblogs.com/dathlin/p/7885368.htmlcss
本文將使用一個NuGet公開的組件技術來實現一個ModBus TCP的客戶端,方便的對Modbus tcp的服務器進行讀寫,這個服務器能夠是電腦端C#設計的,也能夠是PLC實現的,也能夠是其餘任何支持這個通訊協議的服務器。html
github地址:https://github.com/dathlin/HslCommunication 若是喜歡能夠star或是fork,還能夠打賞支持。git
在Visual Studio 中的NuGet管理器中能夠下載安裝,也能夠直接在NuGet控制檯輸入下面的指令安裝:github
1
|
Install-Package HslCommunication
|
NuGet安裝教程 http://www.cnblogs.com/dathlin/p/7705014.html設計模式
技術支持QQ羣:592132877 (組件的版本更新細節也將第一時間在羣裏發佈)組件API地址:http://www.cnblogs.com/dathlin/p/7703805.html數組
關於兩種模式服務器
在PLC端,包括三菱和西門子,歐姆龍以及Modbus Tcp客戶端的訪問器上,都支持兩種模式,短鏈接模式和長鏈接模式,如今就來解釋下什麼原理。網絡
短鏈接:每次讀寫都是一個單獨的請求,請求完畢也就關閉了,若是服務器的端口僅僅支持單鏈接,那麼關閉後這個端口能夠被其餘鏈接複用,可是在頻繁的網絡請求下,容易發生異常,會有其餘的請求不成功,尤爲是多線程的狀況下。多線程
長鏈接:建立一個公用的鏈接通道,全部的讀寫請求都利用這個通道來完成,這樣的話,讀寫性能更快速,即時多線程調用也不會影響,內部有同步機制。若是服務器的端口僅僅支持單鏈接,那麼這個端口就被佔用了,好比三菱的端口機制,西門子的Modbus tcp端口機制也是這樣的。如下代碼默認使用短鏈接,方便測試。tcp
在短鏈接的模式下,每次請求都是單獨的訪問,因此沒有重連的困擾,在長鏈接的模式下,若是本次請求失敗了,在下次請求的時候,會自動從新鏈接服務器,直到請求成功爲止。另外,儘可能全部的讀寫都對結果的成功進行判斷。
只要是網絡訪問,就會存在主從的區別,此處的設計模式是客戶端主動請求服務器數據,而後接收服務器的反饋數據,支持原生的指令收發,支持其餘一些方便的API收發。特殊功能碼須要使用原生收發的API,本組件支持以下的功能操做:
若是你的設備須要這些功能以外的數據,可使用原生API方法,可是這個方法的前提就是你對MODBUS TCP協議很是清晰才能夠,若是你不瞭解這個協議,能夠參照下面的博客說明:
http://blog.csdn.net/thebestleo/article/details/52269999
若是你須要搭建本身的ModBus服務器,能夠參照這邊文章:http://www.cnblogs.com/dathlin/p/7782315.html
在你開發本身的客戶端程序以前,能夠先用MODBUS測試工具進行測試,如下地址的一個開源項目就是基於這個組件開發的Modbus tcp測試工具,可直接用於讀寫測試。
下面的一個項目是這個組件的訪問測試項目,您能夠進行初步的訪問的測試,免去了您寫測試程序的麻煩,這個項目是和三菱,西門子PLC的訪問寫在一塊兒的。能夠同時參考。
下載地址爲:HslCommunicationDemo.zip
ModBus組件全部的功能類都在 HslCommunication.ModBus命名空間,因此再使用以前先添加
1
2
|
using
HslCommunication.ModBus;
using
HslCommunication;
|
實例化:
在使用讀寫功能以前必須先進行實例化:
1
|
private
ModbusTcpNet busTcpClient =
new
ModbusTcpNet(
"192.168.1.195"
, 502, 0x01);
// 站號1
|
上面的實例化指定了服務器的IP地址,端口號(通常都是502),以及本身的站號,容許設置爲0-255,後面的兩個參數有默認值,在實例化的時候能夠省略。
1
|
private
ModbusTcpNet busTcpClient =
new
ModbusTcpNet(
"192.168.1.195"
);
// 端口號502,站號1
|
注意:在Modbus服務器的設備裏,大部分的設備都是從地址0開始的,有些特殊的設備是從地址1開始的,因此本組件裏面,默認從地址0開始,若是想要從地址1開始,那麼就須要以下的配置:
1
|
busTcpClient.AddressStartWithZero = False;
|
上面兩個聲明選擇其中一個就好了。而後實例化以後(也能夠放在窗體的Load方法中)就能夠調用下面的方法切換爲長鏈接了,
1
|
busTcpClient.ConnectServer();
|
關閉的話,調用以下的方法
1
|
busTcpClient.ConnectClose( );
|
如下代碼演示經常使用的讀寫操做,爲了方便起見,再也不對IsSuccess判斷,通常都是成功的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
private
void
userButton30_Click(
object
sender, EventArgs e)
{
// 讀取操做
bool
coil100 = busTcpClient.ReadCoil(
"100"
).Content;
// 讀取線圈100的通斷
short
short100 = busTcpClient.ReadInt16(
"100"
).Content;
// 讀取寄存器100的short值
ushort
ushort100 = busTcpClient.ReadUInt16(
"100"
).Content;
// 讀取寄存器100的ushort值
int
int100 = busTcpClient.ReadInt32(
"100"
).Content;
// 讀取寄存器100-101的int值
uint
uint100 = busTcpClient.ReadUInt32(
"100"
).Content;
// 讀取寄存器100-101的uint值
float
float100 = busTcpClient.ReadFloat(
"100"
).Content;
// 讀取寄存器100-101的float值
long
long100 = busTcpClient.ReadInt64(
"100"
).Content;
// 讀取寄存器100-103的long值
ulong
ulong100 = busTcpClient.ReadUInt64(
"100"
).Content;
// 讀取寄存器100-103的ulong值
double
double100 = busTcpClient.ReadDouble(
"100"
).Content;
// 讀取寄存器100-103的double值
string
str100 = busTcpClient.ReadString(
"100"
, 5).Content;
// 讀取100到104共10個字符的字符串
// 寫入操做
busTcpClient.WriteCoil(
"100"
,
true
);
// 寫入線圈100爲通
busTcpClient.Write(
"100"
, (
short
)12345);
// 寫入寄存器100爲12345
busTcpClient.Write(
"100"
, (
ushort
)45678);
// 寫入寄存器100爲45678
busTcpClient.Write(
"100"
, 123456789);
// 寫入寄存器100-101爲123456789
busTcpClient.Write(
"100"
, (
uint
)123456778);
// 寫入寄存器100-101爲123456778
busTcpClient.Write(
"100"
, 123.456);
// 寫入寄存器100-101爲123.456
busTcpClient.Write(
"100"
, 12312312312414L);
//寫入寄存器100-103爲一個大數據
busTcpClient.Write(
"100"
, 12634534534543656UL);
// 寫入寄存器100-103爲一個大數據
busTcpClient.Write(
"100"
, 123.456d);
// 寫入寄存器100-103爲一個雙精度的數據
busTcpClient.Write(
"100"
,
"K123456789"
);
}
|
下面再分別講解嚴格的操做,以及批量化的複雜的讀寫操做,假設你要讀取1000個M,循環讀取1千次可能要3秒鐘,若是用了下面的批量化讀取,只須要50ms,可是須要你對字節的原理比較熟悉才能駕輕就熟的處理
讀取線圈API:
在此處舉例讀取地址爲0,長度爲10的線圈數量,讀取出來的數據已經自動轉化成了bool數組,方便的進行二次處理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
private
void
userButton8_Click(
object
sender,EventArgs e)
{
HslCommunication.OperateResult<
bool
[]> read = busTcpClient.ReadCoil(
"0"
, 10);
if
(read.IsSuccess)
{
bool
coil_0 = read.Content[0];
bool
coil_1 = read.Content[1];
bool
coil_2 = read.Content[2];
bool
coil_3 = read.Content[3];
bool
coil_4 = read.Content[4];
bool
coil_5 = read.Content[5];
bool
coil_6 = read.Content[6];
bool
coil_7 = read.Content[7];
bool
coil_8 = read.Content[8];
bool
coil_9 = read.Content[9];
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
|
固然也能夠用組件提供的數據轉換API實現數據提取:
讀取離散數據:
讀取離散數據和讀取線圈的代碼幾乎是一致的,處理方式也是一致的,只是方法名稱改爲了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
private
void
userButton8_Click(
object
sender,EventArgs e)
{
HslCommunication.OperateResult<
bool
[]> read = busTcpClient.ReadDiscrete(
"0"
, 10);
if
(read.IsSuccess)
{
bool
coil_0 = read.Content[0];
bool
coil_1 = read.Content[1];
bool
coil_2 = read.Content[2];
bool
coil_3 = read.Content[3];
bool
coil_4 = read.Content[4];
bool
coil_5 = read.Content[5];
bool
coil_6 = read.Content[6];
bool
coil_7 = read.Content[7];
bool
coil_8 = read.Content[8];
bool
coil_9 = read.Content[9];
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
|
讀取寄存器數據:
假設咱們須要讀取地址爲0,長度爲10的數據,也便是10個數據,每一個數據2個字節,總計20個字節的數據。下面解析數據前,先進行了假設,你在解析本身的數據前能夠參照下面的解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
private
void
userButton10_Click(
object
sender, EventArgs e)
{
HslCommunication.OperateResult<
byte
[]> read = busTcpClient.Read(
"0"
, 10);
if
(read.IsSuccess)
{
// 共返回20個字節,每一個數據2個字節,高位在前,低位在後
// 在數據解析前須要知道里面到底存了什麼類型的數據,因此須要進行一些假設:
// 前兩個字節是short數據類型
short
value1 = busTcpClient.ByteTransform.TransInt16(read.Content, 0);<br>
// 接下來的2個字節是ushort類型
ushort
value2 = busTcpClient.ByteTransform.TransUInt16(read.Content, 2);<br>
// 接下來的4個字節是int類型
int
value3 = busTcpClient.ByteTransform.TransInt32(read.Content, 4);<br>
// 接下來的4個字節是float類型
float
value4 = busTcpClient.ByteTransform.TransFloat(read.Content, 8);<br>
// 接下來的所有字節,共8個字節是規格信息
string
speci = Encoding.ASCII.GetString(read.Content, 12, 8);
// 已經提取完全部的數據
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
|
寫一個線圈:
寫一個線圈,這個相對比較簡單,假設咱們須要寫入線圈0,爲通
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private
void
userButton11_Click(
object
sender, EventArgs e)
{
HslCommunication.OperateResult write = busTcpClient.WriteCoil(
"0"
,
true
);
if
(write.IsSuccess)
{
// 寫入成功
textBox1.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
寫一個寄存器:
寫一個寄存器的操做也是很是的方便,在這裏提供了三個重載的方法,容許使用三種方式寫入:分別寫入,short,ushort,byte三種:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private
void
userButton12_Click(
object
sender, EventArgs e)
{
short
value = -1234;
HslCommunication.OperateResult write = busTcpClient.WriteOneRegister(
"0"
, value);
if
(write.IsSuccess)
{
// 寫入成功
textBox1.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private
void
userButton12_Click(
object
sender, EventArgs e)
{
ushort
value = 56713;
HslCommunication.OperateResult write = busTcpClient.WriteOneRegister(
"0"
, value);
if
(write.IsSuccess)
{
// 寫入成功
textBox1.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private
void
userButton12_Click(
object
sender, EventArgs e)
{
// 0x00爲高位,0x10爲低位
HslCommunication.OperateResult write = busTcpClient.WriteOneRegister(
"0"
, 0x00, 0x10);
if
(write.IsSuccess)
{
// 寫入成功
textBox1.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
批量寫入線圈:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
private
void
userButton13_Click(
object
sender, EventArgs e)
{
// 線圈0爲True,線圈1爲false,線圈2爲true.....等等,以此類推,數組長度多少,就寫入多少線圈
bool
[] value =
new
bool
[] {
true
,
false
,
true
,
true
,
false
,
false
};
HslCommunication.OperateResult write = busTcpClient.WriteCoil(
"0"
, value);
if
(write.IsSuccess)
{
// 寫入成功
textBox1.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
批量寫入寄存器:
第一種狀況寫入一串short數組,這種狀況比較簡單:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private
void
userButton14_Click(
object
sender, EventArgs e)
{
short
[] value =
new
short
[] { -1234, 467, 12345 };
HslCommunication.OperateResult write = busTcpClient.Write(
"0"
, value);
if
(write.IsSuccess)
{
// 寫入成功
textBox1.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
第二狀況寫入一串ushort數組,也是比較簡單:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private
void
userButton14_Click(
object
sender, EventArgs e)
{
ushort
[] value =
new
ushort
[] { 46789, 467, 12345 };
HslCommunication.OperateResult write = busTcpClient.Write(
"0"
, value);
if
(write.IsSuccess)
{
// 寫入成功
textBox1.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
比較複雜的是寫入自定義的數據,按照上述讀取寄存器,好比我須要寫入寄存器0,寄存器1共同組成的一個int數據,那麼咱們這麼寫:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
private
void
userButton15_Click(
object
sender, EventArgs e)
{
int
value = 12345678;
// 等待寫入的一個數據
HslCommunication.OperateResult write = busTcpClient.Write(
"0"
, value);
if
(write.IsSuccess)
{
// 寫入成功
textBox1.Text =
"寫入成功"
;
}
else
{
MessageBox.Show(write.ToMessageShowString());
}
}
|
其餘數據參考這個就行,若是有不明白的,能夠聯繫上面的QQ羣。
模式切換(支持熱切換,想何時切換均可以):
上面默認都是使用短鏈接的機制,若是須要使用長鏈接的話,這種通信模式更加穩定。多線程已經同步。
1
2
3
4
|
private
void
userButton11_Click(
object
sender, EventArgs e)
{
modBusTcpClient.ConnectServer();
}
|
執行完這一行代碼後,通常在實例化後面就能夠切換長鏈接了,會返回一個OperateResult對象,鏈接成功IsSuccess爲True,後面全部的讀寫操做都調用同一個通訊通道。若是想要切換回短鏈接。
1
|
modBusTcpClient.ConnectClose();
|
究極數據操做,使用原生的報文來操做數據:
傳入一個字節數組,數據內容和原生的數據一致,好比我要經過原生API讀取寄存器地址爲0,長度爲3的數據,那麼字節的HEX標識形式爲 00 00 00 00 00 06 00 03 00 00 00 03
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
private
void
userButton2_Click(
object
sender, EventArgs e)
{
byte
[] data = HslCommunication.BasicFramework.SoftBasic.HexStringToBytes(
"00 00 00 00 00 06 00 03 00 00 00 03"
);
HslCommunication.OperateResult<
byte
[]> read = busTcpClient.ReadFromCoreServer(data);
if
(read.IsSuccess)
{
// 獲取結果,並轉化爲Hex字符串,方便顯示
string
result = HslCommunication.BasicFramework.SoftBasic.ByteToHexString(read.Content,
' '
);
}
else
{
MessageBox.Show(read.ToMessageShowString());
}
}
|
上述代碼在操做時用了一個轉化機制,輸入爲十六進制的文本,轉化爲byte[]數據,中間的分割符能夠爲空格,能夠爲'-',也能夠爲',','_'等等等等,調用了組件基礎的數據轉化功能。
只有鏈接對象和IO對象才須要釋放,其餘的對象都是託管的對象,若是你想手動回收:busTcpClient.ConnectClose( );busTcpClient = null;