本文将使用一个gitHub开源的组件技术来读写西门子plc数据,使用的是基于以太网的TCP/IP实现,不须要额外的组件,读取操做只要放到后台线程就不会卡死线程,本组件支持超级方便的高性能读写操做css
官方地址:http://www.hslcommunication.cn/ 打赏请认准官网。html
nuget地址:https://www.nuget.org/packages/HslCommunication/
git
github地址:https://github.com/dathlin/HslCommunication
若是喜欢能够star或是fork,还能够打赏支持。github
联系做者及加群方式(激活码在群里发放):http://www.hslcommunication.cn/Cooperation
web
在Visual Studio 中的NuGet管理器中能够下载安装,也能够直接在NuGet控制台输入下面的指令安装编程
Install-Package HslCommunication
若是须要教程:Nuget安装教程:http://www.cnblogs.com/dathlin/p/7705014.htmlc#
组件的完整信息和其余API介绍参照:http://www.cnblogs.com/dathlin/p/7703805.html 组件的受权协议,更新日志,都在该页面里面。安全
本文将展现如何配置网络参数及怎样使用代码来访问PLC数据,但愿给有须要的人解决一些实际问题。主要对西门子PLC的M,Q,I,DB块的数据读写,亲测有效。服务器
此处使用了网线直接的方式,若是PLC接进了局域网,就能够进行远程读写了^_^网络
此处使用到了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
举个例子:
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奔溃,服务器端仍然能够正常的工做。
测试经过的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 提供的图片) (若是碰到读取数据时出现长度验证失败的信息,请务必检查下面的勾是否打上)
实例化:
siemensTcpNet = new SiemensS7Net( SiemensPLCS.S1200, "192.168.0.100" ) { ConnectTimeOut = 5000 };
若是你的PLC是其余系列的,就修改上面的枚举值,本组件支持的西门子型号都在里面。
链接服务器,也能够放在窗口的Load方法中,通常建议使用长链接,速度更快,又是线程安全的(调用下面的方法就是使用了长链接,若是不链接直接读取数据,那就是短链接):
OperateResult connect = siemensTcpNet.ConnectServer( ); if (connect.IsSuccess) { MessageBox.Show( "链接成功!" ); } else { MessageBox.Show( "链接失败!" ); }
断开链接,也就是关闭了长链接,若是再去请求数据,就变成了短链接
siemensTcpNet.ConnectClose( );
下面就演示一些简单的数据操做,省去了对结果是否成功的验证,全部的读写结果都是OperateResult类型及派生类型,都有一个IsSuccess属性来判断成功与否
// 读取操做,这里的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
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 { // 发生了异常 } }
这样就把全部的字节数据都提取上来了,若是数据比较复杂,还能够根据实际状况处理。固然也支持批量的写入数据信息
若是想实现自定义的数据类型,须要继承一个接口
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 }
这样咱们就是能够实现特殊数据的读写了
OperateResult<UserType> read = siemensTcpNet.ReadCustomer<UserType>( "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
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()); } }
更详细的信息,能够参照源代码里面的测试项目。
环境:此处使用了STEP 7V5.5 sp4编程软件做为示例,在添加以太网模块(6GK7 343-1EX30-0E0 CP343-1)到组态中时,能够设置IP地址及子网掩码, 此处测试使用,因此不使用路由器,若是您的西门子须要链接到内网中的话,须要配置路由器。目前只支持M,I,Q数据的读写。 而后点击新建,建立一个Ethernet(1)网络。以太网参数配置以下图:
将以太网的模块添加到机架中之后,如今打开网络组态 ,打开后点击组态上的PLC模块。会出现以下界面,在箭头出进行双击操做,能够弹出对话框,并进行一系列操做:
按照上面一套操做下来,建立了一个读取的端口,端口号为2000,后面有用,须要记住, 按照上述的步骤再建立一个写入的端口,只有最后一步不一致,以下:
配置完以后的效果图以下,新建了两个端口,一个用于读取数据,一个用于写入数据。 <strong>注意:设置完成后必定要写入到PLC才算真的完成。</strong>
如上图所示,上图配置错误,应该配置一个同时支持读写的操做的端口
实例化的类的时候
private SiemensFetchWriteNet siemensFWNet = null;
siemensFWNet = new SiemensFetchWriteNet( ) { IpAddress = "192.168.0.100", Port = 2000, ConnectTimeOut = 5000, };