这是我参与更文挑战的第6天,活动详情查看: 更文挑战
业务场景
最近在做和硬件设备交互的项目, 遇到的难题便是如何计算出正确的通信指令
具体业务需求如下:
- 基于Modbus协议完成设备通信
- 可以读取设备返回内容, 解析出数据信息
方案
感兴趣的兄弟们可以自行百度了解下协议的起源, 这里不再赘述
总体来说, 就是通过约定好的规范, 计算或解析出数据
含义说明
先来看一个写的示例: 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);
}
}
复制代码
大致步骤如下:
- 先观察总结出指令前缀, 例如
01 10 00 42 00 02 0
, 前面的01 10 00 42
不再赘述,00 02
代表要写两个保持寄存器, 因为数据内容是32位的, 最后的0实际是0x
, 其中x代表数据的长度, 例如实际数据为42 48 00 00
, 则x=4 - 将实际数据转换为16进值字符串
- 算出数据的长度
- 拼接命令并发送
需要注意, 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;
}
}
复制代码
大致步骤如下:
- 读取返回数据长度
- 根据数据长度读取数据内容
- 将16进值数据内容转换为整型
近期评论