Modbus由MODICON公司于1979年开发,是一种工业现场总线协议标准。1996年施耐德公司推出基于以太网TCP/IP的Modbus协议:ModbusTCP。Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master/slave方式通信。
个人感觉:
modbus协议也是对地址变量
进行读取或者写入操作,变化的可能是地址变量的地址
和数据类型
。
这个功能码(指定要做什么,对4个不同modbus对象寄存器:是读啊,是写啊,还是对多个一起操作啊)
Modbus和RS485的关系:Modbus是协议,物理层接口有RS232、RS422、RS485和以太网接口几种
我要写一个Master(主站),所以需要一个Slave(从站)
验证4个常用功能码,仿真软件上面有F=01,F=02,F=03和F=04来显示
对应的代码要写4个方法
代码参数的理解
saveid:看资料"从站在modbus总线上可以有多个",仿真软件就能模拟一个从站,就是ID=1,当然可以修改成ID=2功能码:4个功能码,对应写4个方法,,仿真软件上的F=1,或者F=2,3,4addr:一开始看代码4个方法addr都是从0开始,是否重复?答案是:4个功能码表示4个区域或者设备,addr表示各自区域的地址编号。
选择TCP模式,端口是固定的502
F8:
Slave Definition
可以自由设置地址的开始地址是多少(默认0),设置有多少个数量(默认10个)。
功能码
操作:新建四个不同功能码的窗口,然后运行代码,修改仿真软件上的值。
功能码01
功能码02
功能码03,选择Float类型
signed:有符号 unsigned:无符号 hex:十六进制 binary:二进制
big-endian:大端,将高序字节存储在起始地址(高位编址) little-endian:小端,将低序字节存储在起始地址(低位编址)
swap:交换
双击第一个地址输入数据,会提示输入数据的类型,32位数据占2个地址,所以下一个地址是--
功能码04
<!-- 若想引用modbus4j需要引入下列repository id:ias-snapshots id:ias-releases 两个 ,使用默认仓库下载,不要使用阿里云仓库-->
<repositories>
<repository>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>ias-snapshots</id>
<name>Infinite Automation Snapshot Repository</name>
<url>https://maven.mangoautomation.net/repository/ias-snapshot/</url>
</repository>
<repository>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>ias-releases</id>
<name>Infinite Automation Release Repository</name>
<url>https://maven.mangoautomation.net/repository/ias-release/</url>
</repository>
</repositories>
<!-- modbus4j -->
<dependency>
<groupId>com.infiniteautomation</groupId>
<artifactId>modbus4j</artifactId>
<version>3.0.3</version>
</dependency>
ModbusTcpMaster连接类
@Service(value = "ModbusTcpMaster")
public class ModbusTcpMaster {
private final ModbusFactory modbusFactory = new ModbusFactory();
/**
* 获取slave
* @return
* @throws ModbusInitException
*/
public ModbusMaster getSlave(String ip,int port) {
ModbusMaster master = null;
try {
IpParameters params = new IpParameters();
params.setHost(ip);
params.setPort(port);
//这个属性确定了协议帧是否是通过tcp封装的RTU结构,采用modbus tcp/ip时,要设为false, 采用modbus rtu over tcp/ip时,要设为true
params.setEncapsulated(false);
// modbusFactory.createRtuMaster(wapper); //RTU 协议
// modbusFactory.createUdpMaster(params);//UDP 协议
// modbusFactory.createAsciiMaster(wrapper);//ASCII 协议
master = modbusFactory.createTcpMaster(params, false);
//最大等待时间
master.setTimeout(2000);
//最大连接次数
master.setRetries(5);
master.init();
} catch (ModbusInitException e) {
e.printStackTrace();
}
return master;
}
}
Modbus4jReadUtil类
public class Modbus4jReadUtil {
/**
* 读取[01 Coil Status 0x]类型 开关数据
*
* @param slaveId slaveId
* @param offset 位置
* @return 读取值
* @throws ModbusTransportException 异常
* @throws ErrorResponseException 异常
*/
public static Boolean readCoilStatus(ModbusMaster master,int slaveId, int offset,String dev_code){
// 01 Coil Status
BaseLocator<Boolean> loc = BaseLocator.coilStatus(slaveId, offset);
try {
return master.getValue(loc);
}catch (Exception e){
if (e.getMessage().equals("java.net.SocketTimeoutException: connect timed out")) System.err.println(dev_code+":"+e.getMessage());
else e.printStackTrace();
return null;
}
}
/**
* 读取[02 Input Status 1x]类型 开关数据
*
* @param slaveId
* @param offset
* @return
* @throws ModbusTransportException
* @throws ErrorResponseException
*/
public static Boolean readInputStatus(ModbusMaster master,int slaveId, int offset,String dev_code) {
// 02 Input Status
BaseLocator<Boolean> loc = BaseLocator.inputStatus(slaveId, offset);
try{
return master.getValue(loc);
}catch (Exception e){
if (e.getMessage().equals("java.net.SocketTimeoutException: connect timed out")) System.err.println(dev_code+":"+e.getMessage());
else e.printStackTrace();
return null;
}
}
/**
* 读取[03 Holding Register类型 2x]模拟量数据
*
* @param slaveId slave Id
* @param offset 位置
* @param dataType 数据类型,来自com.serotonin.modbus4j.code.DataType
* @return
* @throws ModbusTransportException 异常
* @throws ErrorResponseException 异常
*/
public static Number readHoldingRegister(ModbusMaster master,int slaveId, int offset, int dataType,String dev_code) {
// 03 Holding Register类型数据读取
BaseLocator<Number> loc = BaseLocator.holdingRegister(slaveId, offset, dataType);
try {
return master.getValue(loc);
}catch (Exception e){
if (e.getMessage().equals("java.net.SocketTimeoutException: connect timed out")) System.err.println(dev_code+":"+e.getMessage());
else e.printStackTrace();
return null;
}
}
/**
* 读取[04 Input Registers 3x]类型 模拟量数据
*
* @param slaveId slaveId
* @param offset 位置
* @param dataType 数据类型,来自com.serotonin.modbus4j.code.DataType
* @return 返回结果
* @throws ModbusTransportException 异常
* @throws ErrorResponseException 异常
*/
public static Number readInputRegisters(ModbusMaster master,int slaveId, int offset, int dataType,String dev_code) {
// 04 Input Registers类型数据读取
BaseLocator<Number> loc = BaseLocator.inputRegister(slaveId, offset, dataType);
try{
return master.getValue(loc);
}catch (Exception e){
if (e.getMessage().equals("java.net.SocketTimeoutException: connect timed out")) System.err.println(dev_code+":"+e.getMessage());
else e.printStackTrace();
return null;
}
}
/**
* 批量读取使用方法
*
* @throws ModbusTransportException
* @throws ErrorResponseException
*/
public static void batchRead(ModbusMaster master) throws ModbusTransportException, ErrorResponseException {
BatchRead<Integer> batch = new BatchRead<Integer>();
batch.addLocator(0, BaseLocator.holdingRegister(1, 1, DataType.TWO_BYTE_INT_SIGNED));
batch.addLocator(1, BaseLocator.inputStatus(1, 0));
batch.setContiguousRequests(true);
BatchResults<Integer> results = master.send(batch);
System.out.println("batchRead:" + results.getValue(0));
System.out.println("batchRead:" + results.getValue(1));
}
}
测试
@Autowired
@Qualifier(value = "ModbusTcpMaster")
ModbusTcpMaster masterTcp;
@Test
public void test() {
//开启ModbusTcpMaster连接
ModbusMaster master = masterTcp.getSlave("127.0.0.1", 502);
try {
// 01测试
Boolean v011 = Modbus4jReadUtil.readCoilStatus(master,1, 0,"test_code");
Boolean v012 = Modbus4jReadUtil.readCoilStatus(master,1, 1,"test_code");
Boolean v013 = Modbus4jReadUtil.readCoilStatus(master,1, 6,"test_code");
System.out.println("v011:" + v011);
System.out.println("v012:" + v012);
System.out.println("v013:" + v013);
// 02测试
Boolean v021 = Modbus4jReadUtil.readInputStatus(master,1, 0,"test_code");
Boolean v022 = Modbus4jReadUtil.readInputStatus(master,1, 1,"test_code");
Boolean v023 = Modbus4jReadUtil.readInputStatus(master,1, 2,"test_code");
System.out.println("v021:" + v021);
System.out.println("v022:" + v022);
System.out.println("v023:" + v023);
// 03测试
Number v031 = Modbus4jReadUtil.readHoldingRegister(master,1, 1, DataType.FOUR_BYTE_FLOAT,"test_code");// 注意,float
Number v032 = Modbus4jReadUtil.readHoldingRegister(master,1, 3, DataType.FOUR_BYTE_FLOAT,"test_code");// 同上
System.out.println("v031:" + v031);
System.out.println("v032:" + v032);
// 04测试
Number v041 = Modbus4jReadUtil.readInputRegisters(master,1, 0, DataType.FOUR_BYTE_FLOAT,"test_code");//
Number v042 = Modbus4jReadUtil.readInputRegisters(master,1, 2, DataType.FOUR_BYTE_FLOAT,"test_code");//
System.out.println("v041:" + v041);
System.out.println("v042:" + v042);
// 批量读取
Modbus4jReadUtil.batchRead(master);
} catch (Exception e) {
e.printStackTrace();
}
}
slave配置
操作:新建四个不同功能码的窗口,然后运行代码,修改仿真软件上的值。
输出:
v011:true
v012:false
v013:true
v021:true
v022:false
v023:true
v031:7.5
v032:10.5
v041:1.5
v042:3.0
7.5
true
Modbus4jWriteUtils类
public class Modbus4jWriteUtils{
/**
* 写单个(线圈)开关量数据
* 功能码为:05,开关量输出点Q置位或复位,写入数据到真机的DO类型的寄存器上面,可以读写的布尔类型(0x)
* @param slaveId slave的ID
* @param writeOffset 位置-预访问的地址-地址范围:0-255
* @param writeValue 值-置位则为1,复位则为0
* @return 是否写入成功
*/
public static boolean writeCoil(ModbusMaster master,int slaveId, int writeOffset, boolean writeValue){
boolean flag = false;
try {
// 创建请求
WriteCoilRequest request = new WriteCoilRequest(slaveId, writeOffset, writeValue);
// 发送请求并获取响应对象
WriteCoilResponse response = (WriteCoilResponse) master.send(request);
flag = !response.isException();
}catch (ModbusTransportException e){
e.printStackTrace();
}
return flag;
}
/**
* 写多个开关量数据(线圈)
* 功能码为:0F,写多个开关量数据(线圈)
* @param slaveId slaveId
* @param startOffset 开始位置
* @param bdata 写入的数据
* @return 是否写入成功
*/
public static boolean writeCoils(ModbusMaster master,int slaveId, int startOffset, boolean[] bdata) {
boolean flag = false;
try {
// 创建请求
WriteCoilsRequest request = new WriteCoilsRequest(slaveId, startOffset, bdata);
// 发送请求并获取响应对象
WriteCoilsResponse response = (WriteCoilsResponse) master.send(request);
flag = !response.isException();
}catch (ModbusTransportException e){
e.printStackTrace();
}
return flag;
}
/***
* 保持寄存器写单个
* 功能码为:06,将数据写入至V存储器, 数据到真机,数据类型是Int,可以读写的数字类型(4x)
* @param slaveId slaveId
* @param writeOffset 开始位置
* @param writeValue 写入的数据
*/
public static boolean writeRegister(ModbusMaster master,int slaveId, int writeOffset, short writeValue){
boolean flag = false;
try {
// 创建请求对象
WriteRegisterRequest request = new WriteRegisterRequest(slaveId, writeOffset, writeValue);
// 发送请求并获取响应对象
WriteRegisterResponse response = (WriteRegisterResponse) master.send(request);
flag = !response.isException();
}catch (ModbusTransportException e){
e.printStackTrace();
}
return flag;
}
/**
* 保持寄存器写入多个模拟量数据
* 功能码为:16,将数据写入至多个V存储器,写入数据到真机,数据类型是short[],可以读写的数字类型(4x)
* @param slaveId modbus的slaveID
* @param startOffset 起始位置偏移量值
* @param sdata 写入的数据
* @return 返回是否写入成功
*/
public static boolean writeRegisters(ModbusMaster master,int slaveId, int startOffset, short[] sdata) {
boolean flag = false;
try {
// 创建请求对象
WriteRegistersRequest request = new WriteRegistersRequest(slaveId, startOffset, sdata);
// 发送请求并获取响应对象
WriteRegistersResponse response = (WriteRegistersResponse) master.send(request);
flag = !response.isException();
}catch (ModbusTransportException e){
e.printStackTrace();
}
return flag;
}
/**
* 根据类型写数据(如:写入Float类型的模拟量、Double类型模拟量、整数类型Short、Integer、Long)
*
* @param value 写入值
* @param dataType com.serotonin.modbus4j.code.DataType
*/
public static void writeHoldingRegister(ModbusMaster master,int slaveId, int offset, Number value, int dataType) {
try {
// 类型
BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, offset, dataType);
master.setValue(locator, value);
}catch (Exception e){
e.printStackTrace();
}
}
}
测试
@Autowired
@Qualifier(value = "ModbusTcpMaster")
ModbusTcpMaster masterTcp;
@Test
public void test() {
//开启ModbusTcpMaster连接
ModbusMaster master = masterTcp.getSlave("127.0.0.1", 502);
try {
//写模拟量
writeHoldingRegister(master,1,0, 10.1f, DataType.FOUR_BYTE_FLOAT);
} catch (Exception e) {
e.printStackTrace();
}
}