本文通过Java开源库jlibmodbus实现modbusTCP通讯 从机端
添加依赖
<dependency>
<groupId>com.intelligt.modbus</groupId>
<artifactId>jlibmodbus</artifactId>
<version>1.2.9.7</version>
</dependency>
Java实现从机读写操作
ModbusSlaveUtil 类,主程序创建Modbus Slave监听事件
import com.intelligt.modbus.jlibmodbus.Modbus;
import com.intelligt.modbus.jlibmodbus.data.ModbusHoldingRegisters;
import com.intelligt.modbus.jlibmodbus.exception.IllegalDataAddressException;
import com.intelligt.modbus.jlibmodbus.exception.IllegalDataValueException;
import com.intelligt.modbus.jlibmodbus.exception.ModbusIOException;
import com.intelligt.modbus.jlibmodbus.slave.ModbusSlave;
import com.intelligt.modbus.jlibmodbus.slave.ModbusSlaveFactory;
import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters;
import io.github.talelin.latticy.model.TPSensorValueDO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
//springboot启动时创建tcp slave
@Component
public class ModbusSlaveUtil
implements CommandLineRunner
{
@Override
public void run(String... args) throws Exception {
Thread.sleep(10000);
new Thread(new Runnable() {
@Override
public void run() {
createSalve();
}
}).start();
}
private static int[] arr = new int[]{0,0};
public static ModbusSlave slave ;
public void createSalve(){
try{
initSlave();
System.out.println("slave start...........");
createDataHolder();
closeSlave();
}catch (Exception e){
}
}
public static void initSlave() throws UnknownHostException, ModbusIOException {
TcpParameters tcpParameters = new TcpParameters();// 设置从机TCP参数
InetAddress adress = InetAddress.getByName("127.0.0.1");// 设置TCP的ip地址
// getLocalHost()返回的是本机地址
// tcpParameters.setHost(InetAddress.getLocalHost());
tcpParameters.setHost(adress); // 为从机TCP设置上述ip地址参数
// 设置从机TCP的是否长连接,通俗点讲就是一直保持连接,一次连接完下次就不要在连接了
tcpParameters.setKeepAlive(true);
tcpParameters.setPort(Modbus.TCP_PORT);// 设置从机TCP的端口
// 创建一个从机
slave = ModbusSlaveFactory.createModbusSlaveTCP(tcpParameters);
// 设置控制台输出主机和从机命令交互日志
Modbus.setLogLevel(Modbus.LogLevel.LEVEL_DEBUG);
}
public void createDataHolder() throws ModbusIOException {
// 创建从机的寄存器
MyOwnDataHolder dh = new MyOwnDataHolder();
// 为从机寄存器添加监听事件,这里的监听事件主要是主机如果发送写命令修改从机则控制台输出
dh.addEventListener(new ModbusEventListener() {
@Override
public int[] readHoldingRegisterRange(int offset, int quantity){
System.out.println("readHoldingRegisterRange: 读取信息 offset = " + offset+";quantity"+quantity);
try{
updateHoldingRegisters(offset, quantity);
}catch (Exception e){
e.printStackTrace();
}
return arr;
}
@Override
public int[] readInputRegisterRange(int offset, int quantity){
System.out.println("readInputRegisterRange: 读取信息 offset = " + offset+";quantity"+quantity);
try{
updateInputRegisters(offset, quantity);
}catch (Exception e){
e.printStackTrace();
}
return arr;
}
@Override
public void onWriteToSingleCoil(int address, boolean value) {
System.out
.print("onWriteToSingleCoil: address " + address + ", value " + value);
}
@Override
public void onWriteToMultipleCoils(int address, int quantity, boolean[] values) {
System.out.print("onWriteToMultipleCoils: address " + address + ", quantity "
+ quantity);
}
@Override
public void onWriteToSingleHoldingRegister(int address, int value) {
System.out.print("onWriteToSingleHoldingRegister: address " + address
+ ", value " + value);
}
@Override
public void onWriteToMultipleHoldingRegisters(int address, int quantity,
int[] values) {
System.out.print("onWriteToMultipleHoldingRegisters: address " + address
+ ", quantity " + quantity);
}
});
// 为从机设置寄存器
slave.setDataHolder(dh);
// 设置从机的读超时时间,建议主机读的超时时间小于该值
slave.setReadTimeout(1500);
// 为从机设置从机服务地址slaveid
slave.setServerAddress(1);
// 开启从机监听事件,必须要这一句
slave.listen();
}
public static void closeSlave() throws InterruptedException, ModbusIOException {
//这部分代码主要是设置Java虚拟机关闭的时候需要做的事情,即本程序关闭的时候需要做的事情,直接使用即可
if (slave.isListening()) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
synchronized (slave) {
slave.notifyAll();
}
}));
synchronized (slave) {
slave.wait();
}
slave.shutdown();
System.out.println("slave shutdown........");
}
}
public void updateHoldingRegisters(int offset, int quantity) throws IllegalDataAddressException, IllegalDataValueException {
List<Float> list = Arrays.asList(10.1f,20.3f,89.5f,77.353f);
ModbusHoldingRegisters hr = new ModbusHoldingRegisters(10000);
// 修改数值寄存器对应位置的值,第一个参数代表寄存器地址,第二个代表修改的数值
//hr.set有几个方法,根据自己要赋值的数据类型选择,此处示例的是赋值float类型,一个float是4个字节,32bit,对应2个寄存器所以i*2
for(int i=0;i<list.size();i++){
hr.setFloat32At(i*2, list.get(i));
}
slave.getDataHolder().setHoldingRegisters(hr);
}
public void updateInputRegisters(int offset, int quantity) throws IllegalDataAddressException, IllegalDataValueException, ModbusIOException {
List<Float> list = Arrays.asList(10.1f,20.3f,89.5f,77.353f);
ModbusHoldingRegisters hr = new ModbusHoldingRegisters(10000);
// 修改数值寄存器对应位置的值,第一个参数代表寄存器地址,第二个代表修改的数值
for(int i=0;i<list.size();i++){
hr.setFloat32At(i*2, list.get(i));
}
slave.getDataHolder().setInputRegisters(hr);
}
}
ModbusEventListener 接口
public interface ModbusEventListener {
int[] readHoldingRegisterRange(int offset, int quantity);
int[] readInputRegisterRange(int offset, int quantity);
void onWriteToSingleCoil(int address, boolean value);
void onWriteToMultipleCoils(int address, int quantity, boolean[] values);
void onWriteToSingleHoldingRegister(int address, int value);
void onWriteToMultipleHoldingRegisters(int address, int quantity, int[] values);
}
MyOwnDataHolder 继承jlibmodbus类库中的DataHolder
import com.intelligt.modbus.jlibmodbus.data.DataHolder;
import com.intelligt.modbus.jlibmodbus.exception.IllegalDataAddressException;
import java.util.ArrayList;
import java.util.List;
public class MyOwnDataHolder extends DataHolder {
final List<ModbusEventListener> modbusEventListenerList = new ArrayList<ModbusEventListener>();
public MyOwnDataHolder() {
// you can place the initialization code here
/*
* something like that: setHoldingRegisters(new
* SimpleHoldingRegisters(10)); setCoils(new Coils(128)); ... etc.
*/
}
public void addEventListener(ModbusEventListener listener) {
modbusEventListenerList.add(listener);
}
public boolean removeEventListener(ModbusEventListener listener) {
return modbusEventListenerList.remove(listener);
}
@Override
public int[] readHoldingRegisterRange(int offset, int quantity) throws IllegalDataAddressException {
for (ModbusEventListener l : modbusEventListenerList) {
l.readHoldingRegisterRange(offset, quantity);
}
return super.readHoldingRegisterRange(offset, quantity);
}
@Override
public int[] readInputRegisterRange(int offset, int quantity) throws IllegalDataAddressException{
for (ModbusEventListener l : modbusEventListenerList) {
l.readInputRegisterRange(offset, quantity);
}
return super.readInputRegisterRange(offset, quantity);
}
@Override
public void writeHoldingRegister(int offset, int value) throws IllegalDataAddressException,
IllegalDataValueException {
for (ModbusEventListener l : modbusEventListenerList) {
l.onWriteToSingleHoldingRegister(offset, value);
}
super.writeHoldingRegister(offset, value);
}
@Override
public void writeHoldingRegisterRange(int offset, int[] range)
throws IllegalDataAddressException, IllegalDataValueException {
for (ModbusEventListener l : modbusEventListenerList) {
l.onWriteToMultipleHoldingRegisters(offset, range.length, range);
}
super.writeHoldingRegisterRange(offset, range);
}
@Override
public void writeCoil(int offset, boolean value) throws IllegalDataAddressException,
IllegalDataValueException {
for (ModbusEventListener l : modbusEventListenerList) {
l.onWriteToSingleCoil(offset, value);
}
super.writeCoil(offset, value);
}
@Override
public void writeCoilRange(int offset, boolean[] range) throws IllegalDataAddressException,
IllegalDataValueException {
for (ModbusEventListener l : modbusEventListenerList) {
l.onWriteToMultipleCoils(offset, range.length, range);
}
super.writeCoilRange(offset, range);
}
}
测试
启动程序,用MODSCAN模拟modbus主机读取数据,本例从机发送的是float,使用MODSCAN测试时工具栏上的数据类型选浮点数就可以直接显示出正确的数据了
参考文章 Java实现ModBus的slave端(从机server端,发送数据),代码略有修改,添加了监听读取保持寄存器和读取输入寄存器的方法,并在监听到读事件时给寄存器赋值。
还有在一开始run()的时候,增加一个线程去处理业务,进而不至于阻塞主线程的启动,也不会因为出错致使主线程挂掉。因为原来的代码是在主线程上开启监听slave,在开发工具中运行正常,部署到tomcat上运行起来访问不到url,连tomcat主页都显示不出来。这个问题可以参考这篇文章:Spring Boot中实现CommandLineRunner完成启动时加载数据在内外置tomcat条件下的启动差异及正确的使用姿势