前言:
本项目的孵化说来也是机缘巧合的事,本人于13年大学毕业后去了一家大型的国企工作,慢慢的走上了工业软件,上位机软件开发的道路。于14年正式开发基于windows的软件,当时可选的技术栈就是MFC和C#的winform,后来就发现C#的更为简单一些,那就直接干,先做再说。工厂的基本主要需求就是工艺参数的历史追述,附带一些实时监控设备的功能,那么第一道拦路虎就是如何将三菱的PLC(逻辑控制器,通常作为设备的核心控制单元)的数据给拿到我的软件中来呢?这真是一个棘手的问题啊,首先就是百度,搜索到了MX component组件,初步试了试,真的比较麻烦。然后就去看看有没有其他的方式实现,后来就搜索到了三菱的以太网模块,下载到了支持的通讯说明,长篇大论。边测试边开发,勉勉强强读到了我想要读的数据(当然,这时候的代码基本都是写死的),在接下来的两三年里,接触并开发了好几个类似的项目,通常工业软件的需求是采集,分析,存数据库,显示。后来对通信的理解深入,由单机软件发展成了CS架构的软件。后来在17年趁着换工作和考驾照的间隙,梳理了上份工作积累的经验,和实际的需求,整理成了HslCommunication,并将之开源出来,初步的功能是三菱PLC的数据读写,C#软件之间的数据通信。后来又集成了modbus协议,西门子,欧姆龙,ab plc,三菱串口等等,都是后话的事了。
做这个项目的目标和开源的初衷是方便广大的像我这种的在工厂一线的软件工程师,我一直觉得我们不应该把自己看做是程序员,程序员的角色更像是码农,主要工作就是敲代码,而软件工程师应该是更大的定义,设计软件的整体架构和开发的。这几年大多数工作都开始意识到工业软件,上位机软件,数据追述系统,SCADA软件,MES软件开发的重要性,所以像我这样的有通信需求的人应该不在少数,况且开源有助于别人来一起改进,和代码测试。所以在开源之后,在博客园就陆陆续续的写了一些文章,比如如何使用C#和三菱PLC通讯,C#和西门子通讯等等。从博客园的点击量来看,确实有大量的工厂的程序员有这方便的需求,而直接采用socket来开发,比较晦涩难懂,坑又比较多,事实上确实有很多人来报告了bug。帮助我修复了这个组件,提高了稳定性。
HslCommunication 能干什么?
相比大多数人比较关心这个问题,简单的说,这个组件主要是用于工业通信的,也有两个程序之间的通讯,还有其他杂七杂八的功能,更像是我的工具插件。各种小功能,扩展的小功能等等。直接上图:
这是这个开源项目的demo程序,基本上将80%的功能列举出来了,当然还有一些小功能没有列举。大多数支持的设备都在上面进行显示了,可以方便的进行测试,看看是不是可以实现读写的操作(对现场实际在生产的设备应当注意写入不正确的数据会导致意外事故发生)。比如我们来看看三菱的PLC的demo程序:
其他的截图画面就不一一举例了,都是类似或是基本类似的。可以方便的使用demo进行测试。
特别注意,本组件实现的所有的通讯都是基于socket直接实现的,通信部分不依赖任何第三方通讯库或是组件安装,也就是说,你拿个dll可以直接和PLC通讯,这对于部署,开发调试,升级都是非常方便的。
通讯核心说明
整个网络类的核心在于 NetworkBase类
这个类实现了基础的字节收发功能和连接断开功能。接下来就是 NetworkDoubleBase 类的实现,实现了长短连接的操作,在我们实际读写设备的过程中,网络状况往往是差别很大,所以本项目的初衷就是同时支持长连接和短连接。
实现了长短的连接后,还要实现设备的BCL类型的读写,本质是基于byte数组和C#基础类型的转换,但是这里有个问题,不同的PLC,modbus协议对于转换的格式不是固定的,有可能是一样的,有可能不是一样的,所以又抽象出来一个 IByteTransform 接口
这个接口集成到了下面的设备交互的基类 NetworkDeviceBase 里,这个基类实现了一些基础的类型的数据读写。
所以到这里可以看到,从NetworkDeviceBase类继承出去的设备类(大部分的设备通信协议都是从这个继承出去的),其基本的读写代码都是一致的,关于解析协议,通信的底层都是封装完毕。
通讯举例说明
先举例说明三菱PLC的读写操作:
// 实例化对象,指定PLC的ip地址和端口号 MelsecMcNet melsecMc = new MelsecMcNet( "192.168.1.110", 6000 ); // 连接对象 OperateResult connect = melsecMc.ConnectServer( ); if (!connect.IsSuccess) { Console.WriteLine( "connect failed:" + connect.Message ); return; } // 举例读取D100的值 short D100 = melsecMc.ReadInt16( "D100" ).Content; melsecMc.ConnectClose( ); |
经过层层封装后,读写的逻辑精简为,实例化,连接,读写,关闭。无论是三菱的PLC,还是西门子的PLC,都是一致的,因为基类的模型都是一致的。
// 实例化对象,指定PLC的ip地址和端口号 SiemensS7Net siemens = new SiemensS7Net( SiemensPLCS.S1200, " 192.168.1.110" ); // 连接对象 OperateResult connect = siemens.ConnectServer( ); if (!connect.IsSuccess) { Console.WriteLine( "connect failed:" + connect.Message ); return; } // 举例读取M100的值 short M100 = siemens.ReadInt16( "M100" ).Content; siemens.ConnectClose( ); |
当然,支持大多数的C#类型数据读写
MelsecMcNet melsec_net = new MelsecMcNet( "192.168.0.100", 6000 ); // 此处以D寄存器作为示例 short short_D1000 = melsec_net.ReadInt16( "D1000" ).Content; // 读取D1000的short值 ushort ushort_D1000 = melsec_net.ReadUInt16( "D1000" ).Content; // 读取D1000的ushort值 int int_D1000 = melsec_net.ReadInt32( "D1000" ).Content; // 读取D1000-D1001组成的int数据 uint uint_D1000 = melsec_net.ReadUInt32( "D1000" ).Content; // 读取D1000-D1001组成的uint数据 float float_D1000 = melsec_net.ReadFloat( "D1000" ).Content; // 读取D1000-D1001组成的float数据 long long_D1000 = melsec_net.ReadInt64( "D1000" ).Content; // 读取D1000-D1003组成的long数据 ulong ulong_D1000 = melsec_net.ReadUInt64( "D1000" ).Content; // 读取D1000-D1003组成的long数据 double double_D1000 = melsec_net.ReadDouble( "D1000" ).Content; // 读取D1000-D1003组成的double数据 string str_D1000 = melsec_net.ReadString( "D1000", 10 ).Content; // 读取D1000-D1009组成的条码数据 // 读取数组 short[] short_D1000_array = melsec_net.ReadInt16( "D1000", 10 ).Content; // 读取D1000的short值 ushort[] ushort_D1000_array = melsec_net.ReadUInt16( "D1000", 10 ).Content; // 读取D1000的ushort值 int[] int_D1000_array = melsec_net.ReadInt32( "D1000", 10 ).Content; // 读取D1000-D1001组成的int数据 uint[] uint_D1000_array = melsec_net.ReadUInt32( "D1000", 10 ).Content; // 读取D1000-D1001组成的uint数据 float[] float_D1000_array = melsec_net.ReadFloat( "D1000", 10 ).Content; // 读取D1000-D1001组成的float数据 long[] long_D1000_array = melsec_net.ReadInt64( "D1000", 10 ).Content; // 读取D1000-D1003组成的long数据 ulong[] ulong_D1000_array = melsec_net.ReadUInt64( "D1000", 10 ).Content; // 读取D1000-D1003组成的long数据 double[] double_D1000_array = melsec_net.ReadDouble( "D1000", 10 ).Content; // 读取D1000-D1003组成的double数据 |
写入的操作:
MelsecMcNet melsec_net = new MelsecMcNet( "192.168.0.100", 6000 ); // 此处以D寄存器作为示例 melsec_net.Write( "D1000", (short)1234 ); // 写入D1000 short值 ,W3C0,R3C0 效果是一样的 melsec_net.Write( "D1000", (ushort)45678 ); // 写入D1000 ushort值 melsec_net.Write( "D1000", 1234566 ); // 写入D1000 int值 melsec_net.Write( "D1000", (uint)1234566 ); // 写入D1000 uint值 melsec_net.Write( "D1000", 123.456f ); // 写入D1000 float值 melsec_net.Write( "D1000", 123.456d ); // 写入D1000 double值 melsec_net.Write( "D1000", 123456661235123534L ); // 写入D1000 long值 melsec_net.Write( "D1000", 523456661235123534UL ); // 写入D1000 ulong值 melsec_net.Write( "D1000", "K123456789" ); // 写入D1000 string值 // 读取数组 melsec_net.Write( "D1000", new short[] { 123, 3566, -123 } ); // 写入D1000 short值 ,W3C0,R3C0 效果是一样的 melsec_net.Write( "D1000", new ushort[] { 12242, 42321, 12323 } ); // 写入D1000 ushort值 melsec_net.Write( "D1000", new int[] { 1234312312, 12312312, -1237213 } ); // 写入D1000 int值 melsec_net.Write( "D1000", new uint[] { 523123212, 213,13123 } ); // 写入D1000 uint值 melsec_net.Write( "D1000", new float[] { 123.456f, 35.3f, -675.2f } ); // 写入D1000 float值 melsec_net.Write( "D1000", new double[] { 12343.542312d, 213123.123d, -231232.53432d } ); // 写入D1000 double值 melsec_net.Write( "D1000", new long[] { 1231231242312,34312312323214,-1283862312631823 } ); // 写入D1000 long值 melsec_net.Write( "D1000", new ulong[] { 1231231242312, 34312312323214, 9731283862312631823 } ); // 写入D1000 ulong值 |
这里举例了三菱的PLC,实际上各种PLC的操作都是类似的。
Redis实现
除了上述的基本的设备通信,还实现了redis数据库读写操作,分了两个类实现,下图为一般的通信功能
同时demo中实现了一个浏览redis服务器的界面功能
最后的总结
本通信库实现了.net 3.5 和 .net 4.5的框架,还附带了一些简单的控件,此外还实现了.net standard版本,已在linux测试成功,由于官方在.net core2.2中还未实现串口类,所以暂时没有实现串口相关的。
未来的方向,希望继续优化代码,架构,集成实现更多设备通信,方便广大的网友直接开发测试。
开源地址:https://github.com/dathlin/HslCommunication
更多详细的内容请查看源代码的readme文件。
原文地址:https://www.cnblogs.com/dathlin/p/10390311.html
.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com