Java中Modbus协议的使用与计算业务场景方案

这是我参与更文挑战的第6天,活动详情查看: 更文挑战

业务场景

最近在做和硬件设备交互的项目, 遇到的难题便是如何计算出正确的通信指令

具体业务需求如下:

  1. 基于Modbus协议完成设备通信
  2. 可以读取设备返回内容, 解析出数据信息

方案

感兴趣的兄弟们可以自行百度了解下协议的起源, 这里不再赘述

总体来说, 就是通过约定好的规范, 计算或解析出数据

含义说明

先来看一个写的示例: 01 05 00 0B FF 00

说明一下各个位置的含义:

  • 01: 从机地址
  • 05: 功能代码
  • 00 0B: 寄存器或线圈地址
  • FF 00: 写入数据

再来看一个读的示例: 01 03 04 00 26 25 A0

说明一下各个位置的含义:

  • 01: 从机地址
  • 03: 功能代码
  • 04: 读取的字节数
  • 00 26 25 A0: 读取的数值

通过格式分析, 我们可以看到, 其中最重要的莫过于第三位和第四位, 即功能代码, Modbus中共有如下几种:

  • 01: 读取单个或多个线圈状态
  • 05: 写单个线圈
  • 15: 写多个线圈
  • 03: 读单个或多个保持寄存器
  • 06: 写单个保持寄存器
  • 16: 写多个保持寄存器

了解完全部功能代码后, 开始写代码吧

发送数据

线圈

对于线圈的指令是固定, 如之前提到的例子

启动开关线圈地址为000B, 那么我们的指令应为: 从机地址+功能代码+线圈地址+FF 00, 即01 05 00 0B FF 00

FF 00十六进制数据为1, 即通电, 开关通电启动

保持寄存器

public class Device {
    
    private static final String SET_DISTANCE_COMMAND = '0110004200020';
    private static final int HEX_STEP = 2;
    
    public void setDistance(Integer distance) throws IOException {
        String value = Integer.toHexString(Float.floatToIntBits(Float.parseFloat(String.valueOf(distance))));
        int length = value.length() / HEX_STEP;
        String message = SET_DISTANCE_COMMAND + length + value.toUpperCase();
        send(message);
    }
    
}
复制代码

大致步骤如下:

  1. 先观察总结出指令前缀, 例如01 10 00 42 00 02 0, 前面的01 10 00 42不再赘述, 00 02代表要写两个保持寄存器, 因为数据内容是32位的, 最后的0实际是0x, 其中x代表数据的长度, 例如实际数据为42 48 00 00, 则x=4
  2. 将实际数据转换为16进值字符串
  3. 算出数据的长度
  4. 拼接命令并发送

需要注意, Modbus为大端传输, 即如果数据为00 00 20 41需要写为41 20 00 00

接收数据

读取线圈状态

public class Device {

    private static final int POSITION_BEGIN_INDEX = 4;
    private static final int POSITION_END_INDEX = 6;
    private static final int POSITION_DATA_BEGIN_INDEX = 6;

    /**
   * 读取数据
   *
   * @param res 返回值
   * @return 整型数据
   */
  private Integer readData(String res) {
    int length = Integer.parseInt(res.substring(POSITION_BEGIN_INDEX, POSITION_END_INDEX)) * HEX_STEP;
    return new BigInteger(res.substring(POSITION_DATA_BEGIN_INDEX, POSITION_DATA_BEGIN_INDEX + length), HEX).intValue()
      / HEX_MULTIPLE;
  }
  
}
复制代码

大致步骤如下:

  1. 读取返回数据长度
  2. 根据数据长度读取数据内容
  3. 将16进值数据内容转换为整型