Apache-Plc4x-Modbus-Tcp学习记录(三)

  • Apache-Plc4x-Modbus指令发送/接收配合源码学习(2)
    • 发送
      • ModbusTcpADUIO
      • ModbusPDUIO
      • ModbusPDUReadHoldingRegistersRequestIO
    • 接收
      • ModbusTcpADUIO
      • ModbusPDUIO
      • ModbusPDUReadHoldingRegistersResponseIO
    • 后记

Apache-Plc4x-Modbus指令发送/接收配合源码学习(2)

本文承接上文 Apache-Plc4x-Modbus指令发送/接收配合源码学习(1),对Apache-Plc4x-Modbus的指令发送、接收IO数据流处理,结合源码学习(以read读取数据为例,memory-Area以HoldingRegisters为例)。

发送

上文我们已经将发送指令所需的参数整理好了,就是ModbusTcpADU参数包括:协议标识符、事务标识符、单位标识符和ModbusPDU。
接下来我们就需要将所有参数,转为二进制流发送给设备。
参数的需要经过:ModbusTcpADUIO的serialize、ModbusPDUIO的staticSerialize最后经过ModbusPDUReadHoldingRegistersRequestIO的staticSerialize指令发送设备完成,这些工作主要在readwrite包内的io包进行。

ModbusTcpADUIO

IO总方法,每次发送指令首先进入ModbusTcpADUIO的serialize。
将事务标识符、协议标识符、剩余长度、单位标识符转为二进制流发送;然后进入ModbusPDUIO的staticSerialize。
write部分:00 01 00 00 00 06 01 03 00 00 00 01

 /*** 对象转换二进制流发送*/public static void staticSerialize(WriteBuffer io, ModbusRewriteTcpADU _value) throws ParseException {int transactionIdentifier = (int) _value.getTransactionIdentifier();    // 事务标识符io.writeUnsignedInt(16, ((Number) (transactionIdentifier)).intValue());io.writeUnsignedInt(16, ((Number) 0x0000).intValue());                  // 协议标识符, 固定格式int length = (int) ((_value.getPdu().getLengthInBytes()) + (1));        // 剩余长度io.writeUnsignedInt(16, ((Number) (length)).intValue());short unitIdentifier = (short) _value.getUnitIdentifier();              // 单位标识符io.writeUnsignedShort(8, ((Number) (unitIdentifier)).shortValue());ModbusRewritePDU pdu = (ModbusRewritePDU) _value.getPdu();              // ModbusRewritePDUModbusRewritePDUIO.staticSerialize(io, pdu);}

ModbusPDUIO

ModbusPDUIO的staticSerialize负责将ModbusPDU中的错误标识符、功能码标识符转为二进制流发送;然后根据请求的类型进入不同staticSerialize方法。如:ModbusPDUReadHoldingRegistersRequest进入ModbusPDUReadHoldingRegistersRequestIO的staticSerialize。
write部分:00 01 00 00 00 06 01 03 00 00 00 01

    /*** 根据ModbusRewritePDU类型判断是哪种请求,进入对应封装方法* @param io* @param _value* @throws ParseException*/public static void staticSerialize(WriteBuffer io, ModbusRewritePDU _value) throws ParseException {int startPos = io.getPos();boolean errorFlag = (boolean) _value.getErrorFlag();     // 错误标志io.writeBit((boolean) (errorFlag));short functionFlag = (short) _value.getFunctionFlag();       // 功能码io.writeUnsignedShort(7, ((Number) (functionFlag)).shortValue());// 判断数值类型if(_value instanceof ModbusRewritePDUError) {ModbusRewritePDUErrorIO.staticSerialize(io, (ModbusRewritePDUError) _value);} else if(_value instanceof ModbusRewritePDUReadHoldingRegistersRequest) {ModbusRewritePDUReadHoldingRegistersRequestIO.staticSerialize(io, (ModbusRewritePDUReadHoldingRegistersRequest) _value);} else if(_value instanceof ModbusRewritePDUReadHoldingRegistersResponse) {ModbusRewritePDUReadHoldingRegistersResponseIO.staticSerialize(io, (ModbusRewritePDUReadHoldingRegistersResponse) _value);} elseif(_value instanceof ModbusRewritePDUWriteMultipleHoldingRegistersRequest) {ModbusRewritePDUWriteMultipleHoldingRegistersRequestIO.staticSerialize(io, (ModbusRewritePDUWriteMultipleHoldingRegistersRequest) _value);} else if(_value instanceof ModbusRewritePDUWriteMultipleHoldingRegistersResponse) {ModbusRewritePDUWriteMultipleHoldingRegistersResponseIO.staticSerialize(io, (ModbusRewritePDUWriteMultipleHoldingRegistersResponse) _value);}}

ModbusPDUReadHoldingRegistersRequestIO

ModbusPDUReadHoldingRegistersRequestIO将ModbusPDUReadHoldingRegistersRequest中的寄存器起始地址、寄存器长度转为二进制流发送。
write部分:00 01 00 00 00 06 01 03 00 00 00 01

    /*** 封装方法* @param io* @param _value* @throws ParseException*/public static void staticSerialize(WriteBuffer io, ModbusRewritePDUReadHoldingRegistersRequest _value) throws ParseException {int startingAddress = (int) _value.getStartingAddress();    // 寄存器起始地址io.writeUnsignedInt(16, ((Number) (startingAddress)).intValue());int quantity = (int) _value.getQuantity();                  // 寄存器长度io.writeUnsignedInt(16, ((Number) (quantity)).intValue());}

至此发送指令二进制流结束,发送的指令格式为:00 01 00 00 00 06 01 03 00 00 00 01
00 01 :事务标识符
00 00 :协议标识符,固定格式
00 06 :长度
01 :单位标识符
03 :功能码
00 00 :寄存器起始地址
00 01 :寄存器长度

接收

接收与发送相反,是将二进制流转为实体类参数的过程。
二进制流经过:ModbusTcpADUIO的parse、ModbusTcpADUIO的staticParse最后经过ModbusPDUReadHoldingRegistersResponseIO的staticParse拿出设备返回的数据组。这些工作主要在readwrite包内的io包进行。

ModbusTcpADUIO

IO总方法,每次接收指令首先进入ModbusTcpADUIO的parse,效验收到指令的合法性,如果效验合格进入staticParse解析事务标识符、协议标识符、剩余长度、单位标识符、ModbusPDU。
在解析ModbusPDU时会调用ModbusPDUIO的staticParse。
read部分:00 01 00 00 00 05 01 03 02 00 01

 /*** 返回二进制流转为对象*/@Overridepublic ModbusRewriteTcpADU parse(ReadBuffer io, Object... args) throws ParseException {if((args == null) || (args.length != 1)) {throw new PlcRuntimeException("参数数目错误,应为1:" + args.length);}Boolean response;       // 响应是布尔类型if(args[0] instanceof Boolean) {response = (Boolean) args[0];} else if (args[0] instanceof String) {response = Boolean.valueOf((String) args[0]);} else {throw new PlcRuntimeException("参数0应为Boolean类型或字符串:" + args[0].getClass().getName());}return ModbusRewriteTcpADUIO.staticParse(io, response);}
    /*** 解析事务标识符、协议标识符、剩余长度、单位标识符、ModbusPDU* @param io* @param response* @return* @throws ParseException*/public static ModbusRewriteTcpADU staticParse(ReadBuffer io, Boolean response) throws ParseException {int transactionIdentifier = io.readUnsignedInt(16); // 事务标识符int protocolIdentifier = io.readUnsignedInt(16);   // 协议标识符if(protocolIdentifier != ModbusRewriteTcpADU.PROTOCOLIDENTIFIER) {throw new ParseException("期望常数值:" + ModbusRewriteTcpADU.PROTOCOLIDENTIFIER + "但是得到:" + protocolIdentifier);}int length = io.readUnsignedInt(16);               // 剩余长度short unitIdentifier = io.readUnsignedShort(8);     // 单位标识符ModbusRewritePDU pdu = ModbusRewritePDUIO.staticParse(io, (boolean) (response));// 创建实例return new ModbusRewriteTcpADU(transactionIdentifier, unitIdentifier, pdu);}

ModbusPDUIO

ModbusPDUIO的staticParse负责根据错误标识符、功能码标识符进入不同staticParse方法。如:错误标识符为true、功能码为03进入ModbusPDUReadHoldingRegistersResponseIO的staticParse。
read部分:00 01 00 00 00 05 01 03 02 00 01

    /*** 根据ModbusRewritePDU类型判断是哪种请求,进入对应解包方法* @param io* @param response* @return* @throws ParseException*/public static ModbusRewritePDU staticParse(ReadBuffer io, Boolean response) throws ParseException {boolean errorFlag = io.readBit();                // 错误标识符short functionFlag = io.readUnsignedShort(7);  // 功能码标识符// 根据errorFlag回复信息是否正确标识、functionFlag功能码标识、response true/false来进入对应的解析方法ModbusPDUBuilder builder = null;if(EvaluationHelper.equals(errorFlag, true)) {builder = ModbusRewritePDUErrorIO.staticParse(io);} else if(EvaluationHelper.equals(errorFlag, false) && EvaluationHelper.equals(functionFlag, 0x03) && EvaluationHelper.equals(response, false)) {builder = ModbusRewritePDUReadHoldingRegistersRequestIO.staticParse(io);} else if(EvaluationHelper.equals(errorFlag, false) && EvaluationHelper.equals(functionFlag, 0x03) && EvaluationHelper.equals(response, true)) {builder = ModbusRewritePDUReadHoldingRegistersResponseIO.staticParse(io);} elseif(EvaluationHelper.equals(errorFlag, false) && EvaluationHelper.equals(functionFlag, 0x10) && EvaluationHelper.equals(response, false)) {builder = ModbusRewritePDUWriteMultipleHoldingRegistersRequestIO.staticParse(io);} else if(EvaluationHelper.equals(errorFlag, false) && EvaluationHelper.equals(functionFlag, 0x10) && EvaluationHelper.equals(response, true)) {builder = ModbusRewritePDUWriteMultipleHoldingRegistersResponseIO.staticParse(io);}if (builder == null) {throw new ParseException("不支持该种组合errorFlag:"+errorFlag+"functionFlag:"+functionFlag+"response:"+response);}// 创建实例return builder.build();}

ModbusPDUReadHoldingRegistersResponseIO

ModbusPDUReadHoldingRegistersResponseIO将ModbusPDUReadHoldingRegistersResponse中的数值组解析封装成实体类。
read部分:00 01 00 00 00 05 01 03 02 00 01

    /*** 解包方法* @param io* @return* @throws ParseException*/public static ModbusPDUReadHoldingRegistersResponseBuilder staticParse(ReadBuffer io) throws ParseException {short byteCount = io.readUnsignedShort(8);if(byteCount > Integer.MAX_VALUE) {throw new ParseException("数组计数:" + (byteCount) + "超过允许的最大计数:" + Integer.MAX_VALUE);}byte[] value;{int itemCount = Math.max(0, (int) byteCount);value = new byte[itemCount];for(int curItem = 0; curItem < itemCount; curItem++) {value[curItem] = io.readByte(8);}}return new ModbusPDUReadHoldingRegistersResponseBuilder(value);}

至此接收指令二进制流结束,接收的指令格式为:00 01 00 00 00 05 01 03 02 00 01
00 01 :事务标识符
00 00 :协议标识符,固定格式
00 05 :剩余长度
01 :单位标识符
03 :功能码
02 :数据长度
00 01 :数据值

后记

由此我们可以看出Apache-Plc4x-Modbus指令格式是Modbus-TCP格式和Modbus-RTU格式不同:多了事务标识符、协议标识符、剩余长度;发送没有附加CRC效验并且接收没有效验CRC效验。
如果使用Modbus-RTU协议与设备通讯,需要重写。
附Modbus-RTU协议指令格式:
读发送:

读接收:

写发送:

写接收:

本文只是记录本人学习过程中的理解,可能有部分内容有纰漏,望各位不吝赐教。

Apache-Plc4x-Modbus-Tcp学习记录(三)相关推荐

  1. MySQL学习记录 (三) ----- SQL数据定义语句(DDL)

    相关文章: <MySQL学习记录 (一) ----- 有关数据库的基本概念和MySQL常用命令> <MySQL学习记录 (二) ----- SQL数据查询语句(DQL)> &l ...

  2. css学习记录三:文本属性

    css学习记录三:CSS文本属性 一.文本属性的作用 二.文本颜色 三.文本对齐 四.装饰文本 五.文本缩进 六.行间距 一.文本属性的作用 CSSS Text(文本)属性可定义文本的外观,比如文本的 ...

  3. Kafka学习记录(三)——Broker

    Kafka学习记录(三)--Broker 目录 Kafka学习记录(三)--Broker 对应课程 Zookeeper存储的Kafka信息 Broker总体工作流程 Broker的服役和退役 Kafk ...

  4. webrtc学习记录三【创建基于RTCPeerConnection本机内的1v1音视频互通】

    系列文章目录 webrtc学习记录一[媒体录制MediaRecorder] webrtc学习记录二[基于socket.io创建信令服务器聊天室] 目录 系列文章目录 前言 一.媒体能力的协商过程 1. ...

  5. MODBUS协议规范学习记录

    串行链路和TCP/IP上的MODBUS标准介绍 1.串行链路上的MODBUS MODBUS串行链路取决于TIA/EIA标准:232-F和485-A 2.TCP/IP上的MODBUS MODBUS TC ...

  6. TCP协议学习记录 (三) Ping程序 RR选项 记录路由hop

    一开始想直接在上个程序改,自己构造IP包头,但后来发现不行,微软不让干了,所以后来选用libcap库来收发包 代码写的很乱.. 1 #pragma pack(4) 2 3 #define ECHO_R ...

  7. 《你好,放大器》----学习记录(三)

    3 多种多样的运算放大器 ADI把放大器分为精密和高速两大类 3.1 精密运放和高速运放 3.1.1 精密运放概述 一般来讲,带宽小于 50MHz 的,能够具有某些特殊指标优异性的运放,都属于精密运放 ...

  8. Opencv学习记录(三) —— 得到图像中目标物坐标的简单处理算法(望指正与补充)

    先描述一下使用场景: 我要识别一个红灯,已经把目标准确的提取出来了,二值图像中白色为目标物,现在要算出二值图中的白色像素点的坐标.因为之后需要移植到树莓派,所以需要高效的方法.             ...

  9. 新浪微博Android客户端学习记录三:完成登录界面和授权界面

    本讲完成的效果为: 登录界面的布局代码(login.xml)为: <?xml version="1.0" encoding="utf-8"?> &l ...

  10. ELK学习记录三 :elasticsearch、logstash及kibana的安装与配置(windows)

    注意事项: 1.ELK版本要求5.X以上 2.Elasticsearch5.x版本必须基于jdk1.8,安装环境必须使用jdk1.8 3.操作系统windows10作为测试环境,其他环境命令有差异,请 ...

最新文章

  1. 【java】将自己写的类生成说明文档的方法
  2. 基于Springboot实现医院HIS信息管理系统
  3. xxl-job 执行结果是空_空指针,段错误,这场面试我栽倒在这里了!
  4. superset安装配置
  5. 兼容Tomcat和Weblogic的Spring 数据源JNDI配置
  6. HTML5 Canvas游戏开发(一)基础知识
  7. 专2-第二课 Eclipse开发环境搭建
  8. A5D2应用程序无法启动问题
  9. 【转】VTK修炼之道1_初识VTK
  10. Python 模块 requests 模拟登录豆瓣 并 发表动态
  11. POJ-1008(滑雪)
  12. js dojo 保存txt文件
  13. linux检查python安装情况,使用Python检测Linux服务器连接状态
  14. linux set 39 date 39,Linux date命令
  15. vsCode 快捷键、插件
  16. 爬虫python下载电影_python爬虫--爬取某网站电影下载地址
  17. JVM监控及诊断工具之JConsole
  18. Java实现 蓝桥杯 算法提高 文本加密
  19. linux命令手册安卓版,linux手册app-linux手册 安卓版v3.0.0-PC6安卓网
  20. 安装Adobe After Effects(AE)的时候卡在登陆界面?

热门文章

  1. labelme-mask
  2. java数据包重放攻击代码实现,Web服务的重放攻击的一点想法
  3. Abnova ABCB10(人)重组蛋白说明书
  4. LeetCode-Python-1544. 整理字符串(栈)
  5. 升讯威微信营销系统开发实践:(4)源代码结构说明 与 安装部署说明( 完整开源于 Github)...
  6. 武田天津工厂扩建项目竣工;益方生物完成10亿人民币D轮融资 | 美通企业日报...
  7. vscode+python开发环境搭建
  8. fastJson 解析一个比较复杂的json串为Java对象
  9. 程序员眼中的中国传统文化-王阳明《传习录》32
  10. 灾后第一个清明,我走进北川的那一刻