西门子S7通信

心酸之路

去年下半年左右开始搞一些与西门子PLC通信的事情,也补了一些PLC编程知识,相信有一些编程基础的人,学PLC编程应该会很快的,尤其有一些C基础。

但是呢原来的程序一直一直是用OPC DA的方式与PLC通信,看了下代码质量确实不高,近20年前的代码,反正能运行读取数据,也没有重构的动力。

怎么工作的呢?有一个配置程序进行地址配置

  1. 配置PLC地址串,连接方式
  2. 配置所有需要用到的变量,地址,数据类型,存在数据库一张表中
  3. 配置变量组,从上步中选取各变量,存在数据库一张表中
  4. 在程序中对应各变量组建立相应类,用于读写数据
  5. 配置连接,变量组等
  6. 变量组读到的数据通过for循环,每次用switch判断字符串,然后硬编码对应到对象的字段中去,还需要硬编码类型转换

总之这个过程非常非常不优雅,太多模版代码,还有太多硬编码,既然已经配置好了地址对应的数据类型,为什么还需要硬编码转类型呢?而且当时里面用的EF读写数据库,那种操作真的是快让人吐,正常一个查询搞定的事,代码硬生生读取十几次数据库,然后很多读写数据库,速度慢得吓人。怎么做的呢?先查表按过滤条件,把id全找出来,对,只有id,然后对id列表用for循环,再用id去查数据库,一次查一条数据,再组装数据到对象中去,感觉完全不像是在用ORM框架。想死的心都有,完完全全不想动这些代码。

尝试过的S7通信方式:

  1. HslCommunication,感觉很全面,但是好像要付费,pass,国人写的
  2. OPC UA Helper,用的OPC基金UA Client的包,然后西门子官网上有个Helper示例,试了试,还OK,但是需要PLC开启OPC UA功能,或用Kepware软件
  3. s7netplus,比较不错,自己包装包装可以用得比较顺手,后面几个小项目用的是它
  4. Eclipse Milo,OPC UA方式读写数据,未仔细研究,OPC UA方式暂时放弃
  5. s7connector, Java,想着C#还不是很精通,用Java来试试,蛮久没有更新,没找到批量读取的功能。
  6. plc4x,netty, SPI, opm, connection-pool, byte-buddy, 暂时发现还蛮合适的。

开整

最开始的时候学习官方的方式,OPM,类似ORM,读取的数据直接映射到对象。

新建对象,类加上注解,字段加上注解并填上地址、类型,创建连接,ByteBuddy创建动态对象,拦截get/set方法。get方法,判断是否在缓存时间内,如果在则直接返回缓存值,不在则从PLC中读取新值。set方法,直接写入值到PLC。这里有两个地方有坑,其它无参方法则会先读取所有PLC值,执行原无参方法,再将值写入PLC;其它一个参数方法,则会先读取PLC值,再执行原方法。这两点看源码能清楚,所以感觉Java这块比C#要好一些,随时能清楚代码干了什么事情,而不是包装得不清楚怎么实现的。

动态类

了解了规则后,自己按照类型方法开整。

读取yml文件,两大配置,一是PLC地址,包含需要读取的数据列表;二是数据说明,TagName,地址,数据类型,转换类型,比例等。根据“数据说明”配置,生成动态类,加类注解@PLCEntity,字段加注解@PLCField并地址和缓存时间填入,生成相应get/set方法,生成一个方法,带一个参数,用于从PLC读取所有字段值(从OPM源码了解有此功能)。

生成另一个动态类,和第一个动态类类似,但不增加注解,再增加另外2个字段,数据更新时间和PLC标识,用于写入数据库。另外如有比例变换,增加变换后的get方法,处理比例逻辑。现场间断生成,可能一些数值几个小时内都不会变化,如果采集数据都写入数据库,会有很多无效数据。后面新读的值与旧值比较,如果一样,则不写入数据库。从PLC读到值写入第一个动态生成的类对象,然后复制到第二个动态生成的类对象,此时后者多两个字段,是变化的,所以比较新旧值的时候,hashCodeequals方法将这两个字段值去掉。嗯,现场效果还可以。但是young GC比较多,可能与机制相关,频繁读取数据,生成新对象。

改变策略

前面的方法熟悉了bytebuddy的使用,以后写框架会有一些好处吧。

后面想了想,感觉没有必要生成动态类,直接用Map应该就可以了。嗯,MongoDB的bson里面有个Document很合适,写入数据后直接插入到MongoDB。新代码写完了,但是学没有到现场测试效果。不知道young GC会不会少一些。