[转]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;