最近由于项目需要重新接触ModBus这个网络协议,ModBus这个协议的内容在网络上有很多,如果是刚接触这个协议的新手需要将这个协议的主要参数了解清楚,比如从机的IP地址和端口,从机的slaveid,功能码,寄存器地址等,了解清楚这些主要参数的作用后可以下载ModBus的模拟器进行测试,可以参考下面这篇文章学习模拟器的使用,点击访问。
ModBus的模拟器分为Poll端和Slave端,Poll端相当于TCP中的客户端(Client),Slave端相当于服务端(Server),因此网络上也有人将ModBus分为Client端和Server端。对TCP了解的人应该知道服务端Server是根据客户端Client的请求进行发送数据(这里可能说的不是很专业),ModBus的Slave端也是根据Poll端的一些请求进行发送数据,但是它的命令格式有些不一样,根据功能码的不同而不同,我的下一篇博客会详细介绍。关于ModBus模拟器的下载可以百度进行搜索,这是官网地址,点击下载,另外也可以访问我的博客资源下载,博客资源下载。
这篇博客的重点是介绍如何用Java实现modbus tcp从机通信,从机需要实现的功能是实时监听主机发送过来的命令,然后根据主机的命令进行对应的操作,如果是查询读命令,返回对应寄存器地址的值,如果是写命令,则修改对应寄存器地址的值。
如果要通过自己个人的能力将这些通信机制利用Java来实现使很困难的,我查询了大量的资料和考察了很多不同的Java开源库,最后采用了jlibmodbus,之所以选择这个是因为它的doc文档写的很好,一些example写的很全,这里可以附上我在一个modbus的官方网站上找到该库网站,点击访问,它上面包含很多modbus的开源库,大家都可以进行下载使用,这里再附上我博客资源,点击下载jlibmodbus。
下载jlibmodbus的jar包后在eclipse中将该包和相关依赖包导入到工程中,然后可以直接使用下面的代码了。
导入的jlibmodbus和相关依赖包如下图。
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import com.intelligt.modbus.jlibmodbus.Modbus;
import com.intelligt.modbus.jlibmodbus.data.DataHolder;
import com.intelligt.modbus.jlibmodbus.data.ModbusCoils;
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.slave.ModbusSlave;
import com.intelligt.modbus.jlibmodbus.slave.ModbusSlaveFactory;
import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters;
public class ModBusSlaveTest {
public static void main(String[] args) {
try {
// 设置从机TCP参数
TcpParameters tcpParameters = new TcpParameters();
// 设置TCP的ip地址
InetAddress adress = InetAddress.getByName("127.0.0.1");
// getLocalHost()返回的是本机地址
// tcpParameters.setHost(InetAddress.getLocalHost());
// 为从机TCP设置上述ip地址参数
tcpParameters.setHost(adress);
// 设置从机TCP的是否长连接,通俗点讲就是一直保持连接,一次连接完下次就不要在连接了
tcpParameters.setKeepAlive(true);
// 设置从机TCP的端口
tcpParameters.setPort(Modbus.TCP_PORT);
// 创建一个从机
ModbusSlave slave = ModbusSlaveFactory.createModbusSlaveTCP(tcpParameters);
// 设置控制台输出主机和从机命令交互日志
Modbus.setLogLevel(Modbus.LogLevel.LEVEL_DEBUG);
// 创建从机的寄存器
MyOwnDataHolder dh = new MyOwnDataHolder();
// 为从机寄存器添加监听事件,这里的监听事件主要是主机如果发送写命令修改从机则控制台输出
dh.addEventListener(new ModbusEventListener() {
@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);
// 设置从机寄存器的03和04功能码对应的数值寄存器
ModbusHoldingRegisters hr = new ModbusHoldingRegisters(10);
// 修改数值寄存器对应位置的值,第一个参数代表寄存器地址,第二个代表修改的数值
hr.set(0, 12345);
// 设置从机寄存器的01和02功能码对应的位寄存器,即只有false和true值(或0和1)
ModbusCoils mc = new ModbusCoils(16);
// 设置对应位寄存器地址的位值
mc.set(0, true);
// 为从机设置04功能码对应的数值寄存器
slave.getDataHolder().setInputRegisters(hr);
// 为从机设置01功能码对应的数值寄存器
slave.getDataHolder().setCoils(mc);
// 为从机设置从机服务地址slaveid
slave.setServerAddress(1);
// 开启从机监听事件,必须要这一句
slave.listen();
//这部分代码主要是设置Java虚拟机关闭的时候需要做的事情,即本程序关闭的时候需要做的事情,直接使用即可
if (slave.isListening()) {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
synchronized (slave) {
slave.notifyAll();
}
}
});
synchronized (slave) {
slave.wait();
}
/*
* using master-branch it should be #slave.close();
*/
slave.shutdown();
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
e.printStackTrace();
}
}
// 监听接口
public interface ModbusEventListener {
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);
}
// 寄存器类定义
public static class MyOwnDataHolder extends DataHolder {
final List modbusEventListenerList = new ArrayList();
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 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);
}
}
}