C#读写西门子PLC数据,包含S7协议和Fetch/Write协议,s7支持200smart,300PLC,1200PLC,1500PLC
本文将使用一个gitHub开源的组件技术来读写西门子plc数据,使用的是基于以太网的TCP/IP实现,不需要额外的组件,读取操作只要放到后台线程就不会卡死线程,本组件支持超级方便的高性能读写操作
官方地址:http://www.hslcommunication.cn/ 打赏请认准官网。
nuget地址:https://www.nuget.org/packages/HslCommunication/
github地址:https://github.com/dathlin/HslCommunication 如果喜欢可以star或是fork,还可以打赏支持。
联系作者及加群方式(激活码在群里发放):http://www.hslcommunication.cn/Cooperation
在Visual Studio 中的NuGet管理器中可以下载安装,也可以直接在NuGet控制台输入下面的指令安装
1
|
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个命名空间:
1
2
|
using HslCommunication;
using HslCommunication.Profinet.Siemens;
|
随便聊聊
当我们一个上位机需要读取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
举个例子:
1
|
siemensTcpNet.LogNet = new HslCommunication.LogNet.LogNetSingle( "log.txt" );
|
关于两种协议
本组件支持的西门子通信有两种协议,一种是S7协议,在PLC侧几乎不需要配置参数,另一个协议Fetch/Write协议,相对比较麻烦一点,如果S7不方便读取的话,可以选择Fetch/Write,相对而言,S7更加方便点。
这两个协议除了实例化的类型不一致,读写PLC的代码和连接机制都是一致的,所以FW协议的具体代码就不粘贴了,详细参照下面的Demo项目。
访问测试项目
在上述的github源代码里有个测试项目,HslCommunicationDemo,里面包含了各种客户端的Demo项目,不需要编写任何的代码就可以测试数据的访问了。
下载地址为:HslCommunicationDemo.zip
演示项目
下面的三篇演示了具体如何去访问PLC的数据,我们在访问完成后,通常需要进行处理,以下的示例项目就演示了后台从PLC读取数据后,前台显示并推送给所有在线客户端的功能,客户端并进行图形化显示,具有一定的参考意义,项目地址为:
https://github.com/dathlin/RemoteMonitor
下面的图片示例中的左边程序就是服务器程序,它应该和PLC直接连接并接入局域网,然后把数据推送给客户端显示。注意:一个复杂高级的程序就应该把处理逻辑程序和界面程序分开,比如这里的服务器程序实现数据采集,推送,存储。让客户端程序去实现数据的整理,分析,显示,这样即使客户端程序因为BUG奔溃,服务器端仍然可以正常的工作。
S7协议下的tcp直接通讯,配置简单,一般PLC都支持
测试通过的PLC:1200系列 本人亲测
200smart 感谢 無名①终止^^ 的测试
300系列 感谢 懂PLC不懂c# 的测试
1500系列 感谢 ∮溪风-⊙_⌒ 的测试
报文的格式参考了如下的两篇文章
http://www.itpub.net/thread-2052649-1-1.html
https://wenku.baidu.com/view/d93b88b06394dd88d0d233d4b14e852459fb3912.html
如果你擅长于网络通信和组件开发,可以通过报文格式开发出自己的西门子通信库,我所做的就是基于报文格式进行了二次封装,隐藏了socket通信的细节,还包含了异常处理,提供了简单方便的API来读写数据。提供了整数数据的读写,字符串读写,来丰富各种需求,从事实上来说,只要可以读写字节,相当于任何数据了。
准备:在西门子PLC上配置好IP地址,就只有一个IP地址就够了,然后打开电脑的cmd指令,只要能ping通西门子PLC即可。
还需要在PLC侧配置打开 GET/SET通讯允许:(感谢网友 OLIFE 提供的图片) (如果碰到读取数据时出现长度验证失败的信息,请务必检查下面的勾是否打上)
实例化:
1
2
3
|
siemensTcpNet = new SiemensS7Net( SiemensPLCS.S1200, "192.168.0.100" ) {
ConnectTimeOut = 5000
};
|
如果你的PLC是其他系列的,就修改上面的枚举值,本组件支持的西门子型号都在里面。
连接服务器,也可以放在窗口的Load方法中,一般建议使用长连接,速度更快,又是线程安全的(调用下面的方法就是使用了长连接,如果不连接直接读取数据,那就是短连接):
1
2
3
4
5
6
7
8
9
|
OperateResult connect = siemensTcpNet.ConnectServer( );
if (connect.IsSuccess)
{
MessageBox.Show( "连接成功!" );
}
else
{
MessageBox.Show( "连接失败!" );
}
|
断开连接,也就是关闭了长连接,如果再去请求数据,就变成了短连接
1
|
siemensTcpNet.ConnectClose( );
|
下面就演示一些简单的数据操作,省去了对结果是否成功的验证,所有的读写结果都是OperateResult类型及派生类型,都有一个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
|
// 读取操作,这里的M100可以替换成I100,Q100,DB20.100效果时一样的
bool M100_7 = siemensTcpNet.ReadBool( "M100.7" ).Content; // 读取M100.7是否通断,注意M100.0等同于M100
byte byte_M100 = siemensTcpNet.ReadByte( "M100" ).Content; // 读取M100的值
short short_M100 = siemensTcpNet.ReadInt16( "M100" ).Content; // 读取M100-M101组成的字
ushort ushort_M100 = siemensTcpNet.ReadUInt16( "M100" ).Content; // 读取M100-M101组成的无符号的值
int int_M100 = siemensTcpNet.ReadInt32( "M100" ).Content; // 读取M100-M103组成的有符号的数据
uint uint_M100 = siemensTcpNet.ReadUInt32( "M100" ).Content; // 读取M100-M103组成的无符号的值
float float_M100 = siemensTcpNet.ReadFloat( "M100" ).Content; // 读取M100-M103组成的单精度值
long long_M100 = siemensTcpNet.ReadInt64( "M100" ).Content; // 读取M100-M107组成的大数据值
ulong ulong_M100 = siemensTcpNet.ReadUInt64( "M100" ).Content; // 读取M100-M107组成的无符号大数据
double double_M100 = siemensTcpNet.ReadDouble( "M100" ).Content; // 读取M100-M107组成的双精度值
string str_M100 = siemensTcpNet.ReadString( "M100" , 10 ).Content; // 读取M100-M109组成的ASCII字符串数据
// 写入操作,这里的M100可以替换成I100,Q100,DB20.100效果时一样的
siemensTcpNet.Write( "M100.7" , true ); // 写位,注意M100.0等同于M100
siemensTcpNet.Write( "M100" , ( byte )0x33 ); // 写单个字节
siemensTcpNet.Write( "M100" , ( short )12345 ); // 写双字节有符号
siemensTcpNet.Write( "M100" , ( ushort )45678 ); // 写双字节无符号
siemensTcpNet.Write( "M100" , 123456789 ); // 写双字有符号
siemensTcpNet.Write( "M100" , ( uint )3456789123 ); // 写双字无符号
siemensTcpNet.Write( "M100" , 123.456f ); // 写单精度
siemensTcpNet.Write( "M100" , 1234556434534545L ); // 写大整数有符号
siemensTcpNet.Write( "M100" , 523434234234343UL ); // 写大整数无符号
siemensTcpNet.Write( "M100" , 123.456d ); // 写双精度
siemensTcpNet.Write( "M100" , "K123456789" ); // 写ASCII字符串
|
下面说明复杂的数据操作,以及批量化的数据操作,例如读取M100-M109
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
OperateResult< byte []> read = siemensTcpNet.Read( "M100" , 10 );
{
if (read.IsSuccess)
{
byte m100 = read.Content[0];
byte m101 = read.Content[1];
byte m102 = read.Content[2];
byte m103 = read.Content[3];
byte m104 = read.Content[4];
byte m105 = read.Content[5];
byte m106 = read.Content[6];
byte m107 = read.Content[7];
byte m108 = read.Content[8];
byte m109 = read.Content[9];
}
else
{
// 发生了异常
}
}
|
这样就把所有的字节数据都提取上来了,如果数据比较复杂,还可以根据实际情况处理。当然也支持批量的写入数据信息
如果想实现自定义的数据类型,需要继承一个接口
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
public class UserType : HslCommunication.IDataTransfer
{
#region IDataTransfer
private HslCommunication.Core.IByteTransform ByteTransform = new HslCommunication.Core.ReverseBytesTransform( );
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
}
|
这样我们就是可以实现特殊数据的读写了
1
2
3
4
5
6
7
|
OperateResult "M100" );
if (read.IsSuccess)
{
UserType value = read.Content;
}
// write value
siemensTcpNet.WriteCustomer( "M100" , new UserType( ) );
|
支持M,I,Q,DB,T,C数据的读写操作
究极数据的读取:
此处提供一个核心的报文读取机制,你可以自己传入自己的报文,然后接收服务器的报文,再自己解析操作,可以根据报文格式实现任意的操作,当然,前提是需要报文支持。假设我要实现写入M100,为0x3B,那么最终的报文为
03 00 00 24 02 F0 80 32 01 00 00 00 01 00 0E 00 05 05 01 12 0A 10 02 00 01 00 00 83 00 03 20 00 04 00 08 3B
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private void userButton23_Click_1( object sender, EventArgs e)
{
byte [] buffer = HslCommunication.BasicFramework.SoftBasic.HexStringToBytes(
"03 00 00 24 02 F0 80 32 01 00 00 00 01 00 0E 00 05 05 01 12 0A 10 02 00 01 00 00 83 00 03 20 00 04 00 08 3B" );
OperateResult< byte []> operate = siemensTcpNet.ReadFromServerCore(buffer);
if (operate.IsSuccess)
{
// 显示服务器返回的报文
TextBoxAppendStringLine(HslCommunication.BasicFramework.SoftBasic.ByteToHexString(operate.Content));
}
else
{
// 显示网络错误
MessageBox.Show(operate.ToMessageShowString());
}
}
|
更详细的信息,可以参照源代码里面的测试项目。
如果使用Fetch/Write协议进行读写操作,PLC端的配置不一致,实例化的类不一致,其他都是一样的,不再赘述了,就重点说明下PLC网络模块的配置
环境:此处使用了STEP 7V5.5 sp4编程软件作为示例,在添加以太网模块(6GK7 343-1EX30-0E0 CP343-1)到组态中时,可以设置IP地址及子网掩码, 此处测试使用,所以不使用路由器,如果您的西门子需要连接到内网中的话,需要配置路由器。目前只支持M,I,Q数据的读写。 然后点击新建,创建一个Ethernet(1)网络。以太网参数配置如下图:
将以太网的模块添加到机架中以后,现在打开网络组态 ,打开后点击组态上的PLC模块。会出现如下界面,在箭头出进行双击操作,可以弹出对话框,并进行一系列操作:
按照上面一套操作下来,创建了一个读取的端口,端口号为2000,后面有用,需要记住, 按照上述的步骤再创建一个写入的端口,只有最后一步不一致,如下:
配置完之后的效果图如下,新建了两个端口,一个用于读取数据,一个用于写入数据。 注意:设置完成后一定要写入到PLC才算真的完成。
如上图所示,上图配置错误,应该配置一个同时支持读写的操作的端口
实例化的类的时候
1
|
private SiemensFetchWriteNet siemensFWNet = null ;
|
1
2
3
4
5
6
|
siemensFWNet = new SiemensFetchWriteNet( )
{
IpAddress = "192.168.0.100" ,
Port = 2000,
ConnectTimeOut = 5000,
};
|