当前位置:   article > 正文

Java modbus 实现RTU串口作为slave(服务端)读写数据_java modbus rtu

java modbus rtu

这里要了解下modbus的RTU和TCP 的几个名称关系:

Modbus/RTU:主站 和从站 关系

Modbus/TCP:客户端和服务端关系

关系

主站主动找从站读写数据
客户端主动找服务端读写数据

所以当使用Modbus/TCP时,主站一般作为客户端,从站一般作为服务端

modbus poll和modbus slave模拟软件

当使用Modbus/TCP时,modbus poll一般模拟客户端,modbus slave一般模拟服务端。

以上这几个关系必须了解,否则容易搞混乱。

modbus协议是工业使用比较多的协议,大部分设备都支持modbus的,也就是说,通过modbus可以获取市场上90%的设备,一般新设备都支持modbus。

modbus有几种协议:

modbusTCP、modbusRTU、modbusRTUOVERTCP(本质tcp,需要单独解析)、ASCII(不常用,可以忽略)

Modbus RTU和Modbus TCP两个协议的本质都是Modbus协议,都是靠Modbus寄存器地址来交换数据;但所用的硬件接口不一样,Modbus RTU一般采用串口RS232C或RS485/422,而Modbus TCP一般采用以太网口。现在市场上有很多协议转换器,可以轻松的将这些不同的协议相互转换。
实际上Modbus协议包括ASCII、RTU、TCP。
Modbus协议需要对数据进行校验,串行协议中除有奇偶校验外,ASCII模式采用LRC校验,RTU模式采用16位CRC校验.
Modbus TCP模式没有额外规定校验,因为TCP协议是一个面向连接的可靠协议。
TCP和RTU协议非常类似,只要把RTU协议的两个字节的校验码去掉,然后在RTU协议的开始加上5个0和一个6并通过TCP/IP网络协议发送出去即可。

以上应该可以理解modbus到底干啥的了,明白后,我们开始做测试,整理流程比较长。

先说工具准备情况:

1、虚拟串口工具 vspd,正常安装即可(目的是模拟串口,如果是tcp 协议用不到它)

2、modbus 仿真工具,这个工具很好用,也经常使用

3、java依赖,该依赖比较强大,强推

  1. <dependency>
  2. <groupId>com.intelligt.modbus</groupId>
  3. <artifactId>jlibmodbus</artifactId>
  4. <version>1.2.9.11</version>
  5. </dependency>

模拟工具和仿真工具下载

1、串口模拟工具正常安装即可,然后把补丁进行覆盖安装目录,否则只有14天试用期,建议覆盖,永久使用(安装过程省略)

安装成功后,添加串口,默认是COM2和COM3 两个端口,就用这两个就可以了。

我这边是COM2个slave用,也就是tcp的服务端哈,COM3给master用,也就是tcp的客户端。

其实这块在写代码之前可以进行用modbus 仿真工具进行测试下,例如:

1、打开modbus slave;选择串口

协议类型选择:串口

串口设置,这块根据你的串口模拟工具,我把COM2 slave了,如果是真实设备,具体连接到真实设备的串口即可。

其他的设置,根据具体情况来即可。

点击ok,

应该可以看到模拟工具在跑数据了哈

可以看到串口模拟工具已经被modbus slave模拟器连上了 9600-N-8-1 就是刚才配置的其他参数信息,了解就行。

可以看到COM3的Port 是closed状态,我们这时开始启动poll模拟器。

和slave设置很像,我这边细节就不说了,有个注意点串口要连COM3,因为COM2已经被slave占用了(这里因为是模拟的设备,COM2和COM3 模拟器帮我们进行连接了)。

看modbus模拟器数据已经连上了,也获取到数据了:

串口模拟器也显示连上了:

如果报错,请检查内存起止地址和内存数量。这里通过工具进行模拟的,整体是没问题的,说明环境都是没问题的,确保在编码前环境一定没问题,否则只用用代码容易出问题;可以进行编码了。

用java进行连接,其实我的理解,哪种语言连接都是差不多的,比较简单,直接上代码了哈:

监听接口:

  1. public interface ModbusEventListener {
  2. /**
  3. * 描述
  4. * 单个线圈写入监听 function=01 、 02
  5. *
  6. * @param address 内存地址
  7. * @param value 值
  8. * @return void
  9. * @author
  10. * @date 2024/5/11 15:30:31
  11. */
  12. default void onWriteToSingleCoil(int address, boolean value) {
  13. System.out.print("onWriteToSingleCoil: address " + address + ", value " + value);
  14. }
  15. /**
  16. * 描述
  17. * 批量线圈写入监听 function=01 、 02
  18. *
  19. * @param address 地址
  20. * @param quantity 数量
  21. * @param values 值
  22. * @return void
  23. * @author
  24. * @date 2024/5/11 15:31:20
  25. */
  26. default void onWriteToMultipleCoils(int address, int quantity, boolean[] values) {
  27. System.out.print("onWriteToMultipleCoils: address " + address + ", quantity " + quantity);
  28. }
  29. /**
  30. * 描述
  31. * 读保持寄存器 function=03
  32. *
  33. * @param address 地址
  34. * @param value 值
  35. * @return void
  36. * @author
  37. * @date 2024/5/11 15:32:26
  38. */
  39. void onWriteToSingleHoldingRegister(int address, int value);
  40. /**
  41. * 描述
  42. * 读保持寄存器 function=03
  43. *
  44. * @param address 地址
  45. * @param quantity 数量
  46. * @param values 多个值
  47. * @return void
  48. * @author
  49. * @date 2024/5/11 15:49:22
  50. */
  51. void onWriteToMultipleHoldingRegisters(int address, int quantity, int[] values);
  52. /**
  53. * 描述
  54. * 获取监听类型
  55. *
  56. * @param
  57. * @return com.goldstar.common.utils.constants.GlobalConstants.ModbusListenerType
  58. * @author
  59. * @date 2024/5/13 10:54:14
  60. */
  61. String getListenerType();
  62. }

dataholder:

  1. /**
  2. * 从机的寄存器
  3. */
  4. public class CustomDataHolder extends DataHolder {
  5. final List<ModbusEventListener> modbusEventListenerList = new ArrayList();
  6. public CustomDataHolder() {
  7. // you can place the initialization code here
  8. /*
  9. something like that:
  10. setHoldingRegisters(new SimpleHoldingRegisters(10));
  11. setCoils(new Coils(128));
  12. ...
  13. etc.
  14. */
  15. }
  16. public void addEventListener(ModbusEventListener listener) {
  17. modbusEventListenerList.add(listener);
  18. }
  19. public boolean removeEventListener(ModbusEventListener listener) {
  20. return modbusEventListenerList.remove(listener);
  21. }
  22. @Override
  23. public void writeHoldingRegister(int offset, int value) throws IllegalDataAddressException, IllegalDataValueException {
  24. System.out.println("writeHoldingRegister: offset " + offset + ", value " + value);
  25. for (ModbusEventListener l : modbusEventListenerList) {
  26. l.onWriteToSingleHoldingRegister(offset, value);
  27. }
  28. super.writeHoldingRegister(offset, value);
  29. }
  30. @Override
  31. public void writeHoldingRegisterRange(int offset, int[] range) throws IllegalDataAddressException, IllegalDataValueException {
  32. System.out.println("writeHoldingRegisterRange: offset " + offset + ", range " + range);
  33. for (ModbusEventListener l : modbusEventListenerList) {
  34. l.onWriteToMultipleHoldingRegisters(offset, range.length, range);
  35. }
  36. super.writeHoldingRegisterRange(offset, range);
  37. }
  38. @Override
  39. public void writeCoil(int offset, boolean value) throws IllegalDataAddressException, IllegalDataValueException {
  40. System.out.println("writeCoil: offset " + offset + ", value " + value);
  41. for (ModbusEventListener l : modbusEventListenerList) {
  42. l.onWriteToSingleCoil(offset, value);
  43. }
  44. super.writeCoil(offset, value);
  45. }
  46. @Override
  47. public void writeCoilRange(int offset, boolean[] range) throws IllegalDataAddressException, IllegalDataValueException {
  48. System.out.println("writeCoilRange: offset " + offset + ", range " + range);
  49. for (ModbusEventListener l : modbusEventListenerList) {
  50. l.onWriteToMultipleCoils(offset, range.length, range);
  51. }
  52. super.writeCoilRange(offset, range);
  53. }
  54. }

modbus slave服务 

  1. public class ModbusSlaveService {
  2. private String modbusType = "";
  3. private ModbusSlave modbusSlave;
  4. // 创建从机的寄存器
  5. private CustomDataHolder dataHolder = new CustomDataHolder();
  6. //禁止无参构造函数创建对象
  7. private ModbusSlaveService() {
  8. }
  9. public ModbusSlaveService(@NonNull TcpParameters tcpParameters, @NonNull Integer serverId) {
  10. this.modbusSlave = ModbusSlaveFactory.createModbusSlaveTCP(tcpParameters);
  11. modbusSlave.setReadTimeout(0); // if not set default timeout is 1000ms, I think this mus
  12. // 为从机寄存器添加监听事件,这里的监听事件主要是主机如果发送写命令修改从机则进行业务处理
  13. // dataHolder.addEventListener(new DefaultModbusEventListener());
  14. modbusSlave.setDataHolder(dataHolder);
  15. modbusSlave.setServerAddress(serverId);
  16. modbusType = "协议类型:tcp, ip: " + tcpParameters.getHost() + ":" + tcpParameters.getPort();
  17. }
  18. public ModbusSlaveService(@NonNull SerialParameters serialParameters, @NonNull Integer serverId) throws SerialPortException {
  19. this.modbusSlave = ModbusSlaveFactory.createModbusSlaveRTU(serialParameters);
  20. modbusSlave.setReadTimeout(0); // if not set default timeout is 1000ms, I think this mus
  21. // 为从机寄存器添加监听事件,这里的监听事件主要是主机如果发送写命令修改从机则进行业务处理
  22. // dataHolder.addEventListener(new DefaultModbusEventListener());
  23. modbusSlave.setDataHolder(dataHolder);
  24. modbusSlave.setServerAddress(serverId);
  25. modbusType = "协议类型:RTU(串口), 串口号: " + serialParameters.getDevice();
  26. }
  27. /**
  28. * 注册相关点位数据;用来服务端写到客户端数据
  29. *
  30. * @param modbusHoldingRegisters
  31. * @return
  32. */
  33. public ModbusSlaveService addHoldingRegisters(ModbusHoldingRegisters modbusHoldingRegisters) {
  34. if (modbusHoldingRegisters == null) {
  35. return this;
  36. }
  37. modbusSlave.getDataHolder().setHoldingRegisters(modbusHoldingRegisters);
  38. return this;
  39. }
  40. /**
  41. * 注册相关点位数据;用来服务端写到客户端数据
  42. *
  43. * @param modbusCoils
  44. * @return
  45. */
  46. public ModbusSlaveService addModbusCoils(ModbusCoils modbusCoils) {
  47. if (modbusCoils == null) {
  48. return this;
  49. }
  50. modbusSlave.getDataHolder().setCoils(modbusCoils);
  51. return this;
  52. }
  53. public ModbusSlaveService addModbusInputRegisters(ModbusHoldingRegisters inputRegisters) {
  54. if (inputRegisters == null) {
  55. return this;
  56. }
  57. modbusSlave.getDataHolder().setInputRegisters(inputRegisters);
  58. return this;
  59. }
  60. /**
  61. * 数据监听器,用来监听 客户端写入到数据,进行相关处理
  62. *
  63. * @param listener
  64. * @return
  65. */
  66. public ModbusSlaveService addEventListener(ModbusEventListener listener) {
  67. dataHolder.addEventListener(listener);
  68. return this;
  69. }
  70. public ModbusSlaveService removeEventListener(ModbusEventListener listener) {
  71. dataHolder.removeEventListener(listener);
  72. return this;
  73. }
  74. /**
  75. * 开启监听
  76. *
  77. * @return
  78. * @throws Exception
  79. */
  80. public ModbusSlaveService startListen() throws Exception {
  81. modbusSlave.listen();
  82. log.info("{} ----当前服务已启动", modbusType);
  83. closeSlave();
  84. return this;
  85. }
  86. /**
  87. * 关闭modbus 服务
  88. *
  89. * @throws InterruptedException
  90. * @throws ModbusIOException
  91. */
  92. private void closeSlave() throws InterruptedException, ModbusIOException {
  93. //这部分代码主要是设置Java虚拟机关闭的时候需要做的事情,即本程序关闭的时候需要做的事情,直接使用即可
  94. if (modbusSlave.isListening()) {
  95. Runtime.getRuntime().addShutdownHook(new Thread(() -> {
  96. synchronized (modbusSlave) {
  97. modbusSlave.notifyAll();
  98. }
  99. }));
  100. synchronized (modbusSlave) {
  101. modbusSlave.wait();
  102. }
  103. modbusSlave.shutdown();
  104. log.info("{} ----当前服务已关闭", modbusType);
  105. }
  106. }
  107. /**
  108. * 手动进行关闭服务
  109. */
  110. public void closeModbusSlave() {
  111. try {
  112. modbusSlave.shutdown();
  113. log.info("{} ----当前服务已手动关闭", modbusType);
  114. } catch (ModbusIOException e) {
  115. throw new RuntimeException(e);
  116. }
  117. }
  118. public static void main(String[] args) throws Exception {
  119. SerialParameters serialParameters = new SerialParameters();
  120. serialParameters.setDevice("COM2");
  121. serialParameters.setParity(SerialPort.Parity.NONE);
  122. // SerialPort.BaudRate baudrate = new SerialPort.BaudRate(921600);
  123. // serialParameters.setBaudRate(baudrate);
  124. serialParameters.setBaudRate(SerialPort.BaudRate.BAUD_RATE_9600);
  125. serialParameters.setDataBits(8);
  126. serialParameters.setStopBits(1);
  127. ModbusSlaveService modbusSlaveService1 = new ModbusSlaveService(serialParameters, 1);
  128. //modbusSlaveService1.addEventListener(new DefaultModbusEventListener());
  129. modbusSlaveService1.addEventListener(new ModbusEventListener() {
  130. @Override
  131. public void onWriteToSingleCoil(int address, boolean value) {
  132. log.info("onWriteToSingleCoil({},{})", address, value);
  133. }
  134. @Override
  135. public void onWriteToMultipleCoils(int address, int quantity, boolean[] values) {
  136. log.info("onWriteToMultipleCoils({},{},{})", address, quantity, values);
  137. }
  138. @Override
  139. public void onWriteToSingleHoldingRegister(int address, int value) {
  140. log.info("onWriteToSingleHoldingRegister({},{})", address, value);
  141. }
  142. @Override
  143. public void onWriteToMultipleHoldingRegisters(int address, int quantity, int[] values) {
  144. log.info("onWriteToMultipleHoldingRegisters({},{},{})", address, quantity, values);
  145. }
  146. @Override
  147. public String getListenerType() {
  148. return "";
  149. }
  150. });
  151. //更新寄存器值
  152. ModbusHoldingRegisters hr1 = new ModbusHoldingRegisters(10);
  153. //主机发送数据
  154. ThreadUtil.execute(() -> {
  155. while (true) {
  156. ThreadUtil.sleep(1000);
  157. try {
  158. hr1.set(0, RandomUtil.randomInt(0, 10000));
  159. hr1.set(1, RandomUtil.randomInt(0, 10000));
  160. hr1.set(2, RandomUtil.randomInt(0, 10000));
  161. hr1.set(3, RandomUtil.randomInt(0, 10000));
  162. hr1.set(4, RandomUtil.randomInt(0, 10000));
  163. log.info("COM数据:{}", JSONUtil.toJsonStr(hr1));
  164. modbusSlaveService1.addHoldingRegisters(hr1);
  165. } catch (IllegalDataAddressException e) {
  166. throw new RuntimeException(e);
  167. } catch (IllegalDataValueException e) {
  168. throw new RuntimeException(e);
  169. }
  170. }
  171. });
  172. modbusSlaveService1.startListen();
  173. }
  174. }

测试代码说明:

  1. public static void main(String[] args) throws Exception {
  2. //串口参数实体类
  3. SerialParameters serialParameters = new SerialParameters();
  4. //这是设置串口 刚才上面说了,用COM2作为slave
  5. serialParameters.setDevice("COM2");
  6. //用模拟器时的其他几个参数设置
  7. serialParameters.setParity(SerialPort.Parity.NONE);
  8. // SerialPort.BaudRate baudrate = new SerialPort.BaudRate(921600);
  9. // serialParameters.setBaudRate(baudrate);
  10. serialParameters.setBaudRate(SerialPort.BaudRate.BAUD_RATE_9600);
  11. serialParameters.setDataBits(8);
  12. serialParameters.setStopBits(1);
  13. //new modbus slave 对象
  14. ModbusSlaveService modbusSlaveService1 = new ModbusSlaveService(serialParameters, 1);
  15. //modbusSlaveService1.addEventListener(new DefaultModbusEventListener());
  16. //添加监听;此处监听是为了让 master写数据时,slave能监听到,这样就可以相互通信了,这是扩展项
  17. modbusSlaveService1.addEventListener(new ModbusEventListener() {
  18. @Override
  19. public void onWriteToSingleCoil(int address, boolean value) {
  20. log.info("onWriteToSingleCoil({},{})", address, value);
  21. }
  22. @Override
  23. public void onWriteToMultipleCoils(int address, int quantity, boolean[] values) {
  24. log.info("onWriteToMultipleCoils({},{},{})", address, quantity, values);
  25. }
  26. @Override
  27. public void onWriteToSingleHoldingRegister(int address, int value) {
  28. log.info("onWriteToSingleHoldingRegister({},{})", address, value);
  29. }
  30. @Override
  31. public void onWriteToMultipleHoldingRegisters(int address, int quantity, int[] values) {
  32. log.info("onWriteToMultipleHoldingRegisters({},{},{})", address, quantity, values);
  33. }
  34. @Override
  35. public String getListenerType() {
  36. return "";
  37. }
  38. });
  39. //更新寄存器值,每个一秒更新一次数据
  40. ModbusHoldingRegisters hr1 = new ModbusHoldingRegisters(10);
  41. //主机发送数据;这里如果是真实场景的业务,就是业务数据了,我这边通过随机生成的数据进行模拟的,大家根据自己的情况进行设置即可
  42. ThreadUtil.execute(() -> {
  43. while (true) {
  44. ThreadUtil.sleep(1000);
  45. try {
  46. hr1.set(0, RandomUtil.randomInt(0, 10000));
  47. hr1.set(1, RandomUtil.randomInt(0, 10000));
  48. hr1.set(2, RandomUtil.randomInt(0, 10000));
  49. hr1.set(3, RandomUtil.randomInt(0, 10000));
  50. hr1.set(4, RandomUtil.randomInt(0, 10000));
  51. log.info("COM数据:{}", JSONUtil.toJsonStr(hr1));
  52. modbusSlaveService1.addHoldingRegisters(hr1);
  53. } catch (IllegalDataAddressException e) {
  54. throw new RuntimeException(e);
  55. } catch (IllegalDataValueException e) {
  56. throw new RuntimeException(e);
  57. }
  58. }
  59. });
  60. //启动监听
  61. modbusSlaveService1.startListen();
  62. }

上面明白后,咱们运行起来:

1、开发工具日志:

已经启动了,再看看串口模拟器:

这里可以看到,COM2已经换成了java.exe了,这就对了

在看poll工具:

数据实时读取到了,我们测试下poll也就是master 发送数据,slave能否接收到:

Ok,slave 发送master(poll)  和master(poll) 发送slave 都是没问题的,完美解决,后面进行业务集成即可。

总结说明:

其实工业的协议要比TCP简单很多,逻辑不复杂,大家尝试下就明白了,modbusTCP 相互通信更简单了,这里不进行演示了,上面代码其实我已经写了,大家可以写下测试代码就支持tcp了,Ok到此结束。

后续会深入研究modbus协议,争取用java进行实现modbus协议,也就是上面用的库文件,已经进行实现了,进行尝试手动实现,这样后期可以深入定制业务以及各类定制化modbus协议。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/843815
推荐阅读
相关标签
  

闽ICP备14008679号