【ModBus】modbus之modbus4j的使用和流程原理解析(5)

hey-girl东拼西凑原创文章,若有歧义可留言,若需转载需标明出处

前言:接上篇继续说说modbus4j的几个常用方法.已经代码分析

项目中使用MouBus4j

说说实际开发中。首先创建一个springboot项目。pom引入依赖如下:

    <repositories>
        <repository>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
            <id>ias-snapshots</id>
            <name>Infinite Automation Snapshot Repository</name>
            <url>https://maven.mangoautomation.net/repository/ias-snapshot/</url>
        </repository>
        <repository>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <id>ias-releases</id>
            <name>Infinite Automation Release Repository</name>
            <url>https://maven.mangoautomation.net/repository/ias-release/</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>com.infiniteautomation</groupId>
            <artifactId>modbus4j</artifactId>
            <version>3.0.3</version>
        </dependency>
        <dependency>
            <groupId>org.scream3r</groupId>
            <artifactId>jssc</artifactId>
            <version>2.8.0</version>
        </dependency>
    </dependencies>

下面针对modbus4j几个常用方法进行分析,这样才能保证开发起来得心应手。程序为master(主),slave工具模拟从设备。
在开始代码分析前:首先说说modbus4j这个库常用核心的类代表啥。参考文章

  • 主机Master及其子类:主机的入口,数据流的起点和终点。
  • 数据端口类StreamTransport:负责数据的写入和读出。
  • Modbus消息类ModbusMessage及其子类:支持Modbus定义的各种方法(FunctionCode)
  • 收发数据控制类MessageControl:支持 timeout、retries,默认200ms,1次。
  • 收发等待室WaitingRoom:负责同步收发逻辑。
  • 输出Request消息类:OutgoingRequestMessage 及其子类。
  • 收到Response消息类:IncomingResponseMessage 及其子类。
  • 解析类MessageParser:负责解析收到的消息。
  • 协议数据类型定义:DataType
  • 协议功能码定义:FunctionCode
  • 协议寄存器范围:RegisterRange

A---- readHoldingRegisters方法,读保持寄存器。调用这个方法 readHoldingRegisters(master,1,0,4) 进行分析。

  public void readHoldingRegisters(ModbusMaster master, int slaveId, int start, int len) {
        try {
            // 关键代码
            ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, start, len);
            ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) master.send(request);
            if (response.isException()){
                log.error("Exception response: message=" + response.getExceptionMessage());
            } else{
                log.info(Arrays.toString(response.getShortData()));
            }
        }
        catch (ModbusTransportException e) {
            e.printStackTrace();
        }
        finally {
            master.destroy();
        }
    }

DemoA整体流程解析:

  • ModbusFactory类创建对应的Master对象。这里用到的是子类RtuMaster

  • 构建ReadHoldingRegistersRequest(继承ModbusRequest)请求对象。里面就是包含从机地址,保持寄存器地址和数量等信息。ModbusRequest有很多子类。若我们需求不是读保持寄存器。就找对应的子类构建就可以

  • 调用ModbusMaster(抽象类)的send(ModbusRequest request)方法。先校验数据,主要是看看你给request的参数是不是符合需求。然后会调用this.sendImpl(request),这其实就是调用的他的实现子类RtuMaster的sendImpl(ModbusRequest request)方法。这个方法的返回就是ModbusResponse。这方法做的就是发消息了会调用MessageControl的send。方法返回的是RtuMessageResponse。

  • 在这里插入图片描述

  • MessageControl的send方法就会发送报文。并接收报文。

  • 这里我们收到的数据应该是正常报文,就是从机地址,功能码,数据字节数,数据,校验这种格式。但是MessageControl会做返回数据的处理。

  • MessageParser接口的parseMessage方法,是对消息处理的。实现类RtuMessageParser的parseMessageImpl是实际干活的会调用RtuMessageResponse.createRtuMessageResponse(queue)这里queue是原始报文数据,RtuMessageResponse的创建需要ModbusResponse对象。所以这里createModbusResponse方法是关键,它会把从机地址,功能码以及校验去掉。还会调用((ModbusResponse)response).read(queue, isException);执行的就是readResponse这个抽象方法。这个方法会把原始数据进行处理。
    这么描述只是让你大概熟悉流程,具体的代码解析,请往下看。
    Q1-代码解析ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, start, len)
    A1- ReadHoldingRegistersRequest 继承 ReadNumericRequest 继承 ModbusRequest 继承ModbusMessage。创建一个ReadHoldingRegistersRequest对象的过程主要是验证slaveId的值是不是不符合规则的。然后保持寄存器起始位置和寄存器数量赋值的过程。也就是start是保持寄存器的起始位置。len表示的是保持寄存器数量。

Q1-(ReadHoldingRegistersResponse) master.send(request)
A1- master就是创建的RtuMaster对象。主要看send方法。先执行ModbusMaster里的send方法。
send
这里会调用validate方法。这个方法是ModbusRequest的抽象方法。
validate方法
他的实现有10个。如图:
在这里插入图片描述
所以就来到ReadNumericRequest 的validate方法。
在这里插入图片描述
这个方法主要做就是对保持寄存器的这个起始位置值做了校验(取值合法范围0-65535)。对读寄存器的数量也进行校验。以及对读取的起始位置+读的数量。判断是否合规。就理解为发请求前对参数的校验。

在看他的第二个方法sendImpl,接收一个ModbusRequest参数。这个方法是它自己本身的抽象方法。

有5个具体实现
在这里插入图片描述
这里我们是RTU模式。主要看看rtu
在这里插入图片描述
这个方法主要是把传入的request对象包装成RtuMessageRequest对象。this.conn是MessageControl对象。然后调用send方法。
send方法主要是把包装的request里面的参数变成byte[].也就是包装成发送的报文。

这串报文看着就是我们熟悉的标准rtu报文帧,01也就是从机地址,03功能码和data以及校验位2个字节。然后循环write发送。也就是和我们最开始的传参符合,报文就是读从机地址01,保持寄存器起始位置0,保持寄存器数量4.
报文帧
我们看看响应,正常响应的报文应该是如下
01 03 08 00 00 00 00 00 16 00 00 74 13 (01从机地址,03功能码,08数据域字节数,00 00 00 00 00 16 00 00数据高低位 ,74 13校验)。正好对应从设备。(注意这里16是hex对应dec就是22)
在这里插入图片描述
转看程序返回的是一个RtuMessageResponse对象,extends RtuMessage implements OutgoingResponseMessage, IncomingResponseMessage。
RtuMessageResponse的创建接收一个ModbusResponse对象。ModbusResponse extends ModbusMessage。他是一个抽象类。这里要用到他的实现类ReadHoldingRegistersResponse。
RtuMessageResponse
他接收的参数queue,就是响应的原始报文。
在这里插入图片描述
经过一系列构建方法以后,会得到RtuMessageResponse对象。它里的的data就是数据位。已经从16进制转换成10进制了。这里就是我们读的数据
在这里插入图片描述
最终返回结果[0, 0, 22, 0]

B----readHoldingRegistersByLocator的方法。(这个方法也是对保持寄存器进行读取)进行分析。

方法调用readHoldingRegistersByLocator(master,1,0, DataType.FOUR_BYTE_INT_UNSIGNED)。
这里也master是用的RtuMaster
1 :从机地址
0:保持寄存器起始 地址
这个方法:从从机地址为1,保持寄存器地址0。
DataType.FOUR_BYTE_INT_UNSIGNED:这个参数很重要,往后看。后面讲了的。

    public void readHoldingRegistersByLocator(ModbusMaster master, int slaveId, int offset, int dataType) {
        try{
            BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, offset, dataType);
            System.out.println(master.getValue(locator));
        }catch (ModbusTransportException | ErrorResponseException e) {

        } 
    }

Q1-代码解析BaseLocator.holdingRegister(slaveId, 0, DataType.EIGHT_BYTE_INT_UNSIGNED);
A1- BaseLocator是个抽象类。这里调用的它的静态方法holdingRegister方法,接收三个参数。slaveId表示从机地址,0就是保持寄存器起始地址,DataType.FOUR_BYTE_INT_UNSIGNED的值这里是4。这个方法主要是做了一件事创建创建NumericLocator对象,new NumericLocator(slaveId, 3, offset, dataType); NumericLocator继承BaseLocator抽象类。构建方法接收4个参数
NumericLocator的构造方法
这里分别是slaveId从机地址,range 功能码,offset偏移量。dataType数据类型。构造方法做的事情就是赋值并且校验datatype的合法性。在modbus4j3.0.6-SNAPSHOT版本中。参数range的取值范围是如下:
RegisterRange
执行完以后会得到一个BaseLocator对象。如下:
BaseLocator对象
Q2-代码解析master.getValue(locator)
A2- getvalue 其实还是调用了ModbusMaster的send方法。
getvalue方法
send方法
注意这个方法,会得到一个List<ReadFunctionGroup>集合。对这个集合进行迭代执行sendFunctionGroup方法
如下图:
在这里插入图片描述
sendFunctionGroup这个方法。根据FunctionCode就是就功能码,判断当前是什么类型请求。这里是读保持寄存器,也就是03。创建ReadHoldingRegistersRequest对象(这个对象是不是很眼熟。不就是我们第一个demoA,在send的时候用到的请求对象哈)。有了这个request对象就可以执行send方法。就和demoA完全一样的请求流程。

本例中请求的报文为如下图:
这个报文就是和前面相呼应从机地址01 ,功能码03,保持寄存器起始地址0,保持寄存器数量2
在这里插入图片描述
响应报文如下:
slave工具
在这里插入图片描述

这句报文对应的就是01从机地址,03功能码,04数据的字节数,00 58 00 00数据。58就是十进制的88

但是modbus4j做了返回的报文封装。我们程序执行到最后得到的是5767168这个值

为什么会是这个值,想想我们本例的需求。读保持寄存器0位置,数量读2个。按理来说2个寄存器得给我返回4byte.一个寄存器里存的是1word(等价于2byte)。为啥结果是5767168。还得从我们调用方法的时候给的参数DataType.FOUR_BYTE_INT_UNSIGNED说起。
这个DataType 在 package com.serotonin.modbus4j.code。大家可以去看看源码。
理解也好理解。因为对应的设备不一样。modbus规定了一个寄存器里存的是1word。这没毛病。但是你想想。万一硬件的产品是4个字节或者8个字节存储一个值。这个值又和它约定有关。
所以我们这个需求的意思就是从机地址01,读保持寄存器00开始,保持寄寄存器数量2。读到了4个字节。我这个字节表示的是一个值。比如我们这里的。
00 58 00 00 这是16进制表示。我们给的参数是FOUR_BYTE_INT_UNSIGNED。换算成二进制等于
00000000 01011000 00000000 00000000。
这个2进制换算成10进制就是5767168

还有点说明的。我们前面并没有告诉方法说要读保持寄存器的数量。这个数量2的由来
int length = functionGroup.getLength();
这个length来自DataType的getRegisterCount方法

和demoA的区别就是:sendFunctionGroup这个方法后面执行了。
这么一句results.addResult(locator.getKey(), locator.bytesToValue(data, startOffset));而demoA是直接将报文结果进制转换后返回。
这句代码最终干活的是bytesToValueRealOffset这个方法。也就是根据datatype进行转换
在这里插入图片描述
总结:虽然写的有点潦草,因为边学边写有点赶时间,但是理就是这样的。懂得都懂

### 如何在 CUDA 10.2 上安装 PyTorch 为了确保顺利安装适用于 CUDA 10.2 的 PyTorch 版本,建议通过 Anaconda 来管理依赖关系环境。以下是具体操作指南: #### 创建并激活新的 Conda 环境 推荐先创建一个新的 Python 环境来隔离不同项目的库文件,防止版本冲突。 ```bash conda create -n pytorch_env python=3.7 conda activate pytorch_env ``` #### 配置国内镜像源加快下载速度 考虑到网络因素可能影响包的获取效率,可设置清华大学开源软件镜像站作为默认渠道之一[^5]。 ```bash conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ conda config --set show_channel_urls yes conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/win-64/ ``` #### 安装指定版本的 PyTorch 及其相关组件 根据需求选择合适的 PyTorch 其他必要的扩展模块版本进行安装。对于 CUDA 10.2 用户来说,可以选择如下命令完成安装过程[^2]。 ```bash conda install pytorch==1.8.0 torchvision==0.9.0 torchaudio==0.8.0 cudatoolkit=10.2 -c pytorch ``` #### 验证安装情况 最后一步是在 Python 解释器内部验证是否正确加载了带有 GPU 支持功能的 PyTorch 库[^4]。 ```python import torch print(torch.__version__) print(torch.cuda.is_available()) ``` 如果一切正常,则会显示相应的 PyTorch 版本号,并确认存在可用的 CUDA 设备支持。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值