SpringBoot集成PLC4x与Modbus TCP通信

3,280 阅读2分钟

基本环境

  • SpringBoot:2.6.13
  • PLC4X:0.10.0

Modbus模拟器:

ModbusPal使用参考 plc4x.apache.org/users/getti…

pom.xml

<dependency>  
    <groupId>org.apache.plc4x</groupId>  
    <artifactId>plc4j-api</artifactId>  
    <version>0.10.0</version>  
</dependency>  
<dependency>  
    <groupId>org.apache.plc4x</groupId>  
    <artifactId>plc4j-driver-modbus</artifactId>  
    <version>0.10.0</version>  
    <scope>runtime</scope>  
</dependency>

其他依赖根据自己需要添加。

模拟一个Modbus slave

添加slave后并启动

image.png

再点击眼睛按钮编辑slave,选择Coils(线圈)添加10个地址,其value只能是0/1

image.png

读取线圈值

Coil指的是Modbus设备中用于控制开关状态的位数据。这些数据通常表示为可读写的线圈寄存器,可以用于控制诸如继电器和开关等设备。

Modbus地址参考:plc4x.apache.org/users/proto… 需要注意的点是:

image.png

官方文档少了一个:,所以正确的格式是:

{memory-Area}:{start-address}:{data-type}[{array-size}]

而且只能填coil,0/0x都会报是不合法的地址 image.png

同步读

// 0.10.0版本之前是 modbus://127.0.0.1:502  
String connectionString = "modbus-tcp://127.0.0.1:502";  
  
// 1.获取PlcConnection对象  
PlcConnection plcConnection = new PlcDriverManager().getConnection(connectionString);  
  
// 2.判断是否可以读取  
boolean canRead = plcConnection.getMetadata().canRead();  
if (!canRead) {  
log.error("该连接不支持读取数据");  
return;  
}  
  
// 3.创建读取请求  
PlcReadRequest.Builder builder = plcConnection.readRequestBuilder();  
// 第一个参数是地址别名,第二个参数是 Modbus 地址  
// {memory-Area}:{start-address}:{data-type}[{array-size}]  
builder.addItem("coil-1", "coil:1:BOOL[1]");  
builder.addItem("coil-2", "coil:2:BOOL");  
builder.addItem("coil-3", "coil:3");  
builder.addItem("coil-4", "coil:4");  
builder.addItem("coils-5-10", "coil:5[6]");  
PlcReadRequest readRequest = builder.build();  
  
PlcReadResponse response = readRequest.execute().get(5000, TimeUnit.MILLISECONDS);  
for (String fieldName : response.getFieldNames()) {  
if(response.getResponseCode(fieldName) == PlcResponseCode.OK) {  
int numValues = response.getNumberOfValues(fieldName);  
if(numValues == 1) {  
log.info("Value[" + fieldName + "]: " + response.getObject(fieldName));  
}  
else {  
log.info("Value[" + fieldName + "]:");  
for(int i = 0; i < numValues; i++) {  
log.info(" - " + response.getObject(fieldName, i));  
}  
}  
}  
else {  
log.error("Error[" + fieldName + "]: " + response.getResponseCode(fieldName).name());  
}  
}

其中builder.addItem("coils-5-10", "coil:5[6]");是读取从地址5到10的数据

结果:

image.png

image.png

异步读

String connectionString = "modbus-tcp://127.0.0.1";  
PlcConnection plcConnection = new PlcDriverManager().getConnection(connectionString);  
boolean canRead = plcConnection.getMetadata().canRead();  
if (!canRead) {  
log.error("该连接不支持读取数据");  
return;  
}  
  
PlcReadRequest.Builder builder = plcConnection.readRequestBuilder();  
builder.addItem("coils", "coil:1[10]");  
PlcReadRequest readRequest = builder.build();  
  
CompletableFuture<? extends PlcReadResponse> asyncResponse = readRequest.execute();  
asyncResponse.whenComplete((response, throwable) -> {  
Boolean[] coilArray = new Boolean[10];  
response.getAllBooleans("coils").toArray(coilArray);  
for (int i = 0; i < coilArray.length; i++) {  
log.info("Coil[" + i + "]: " + coilArray[i]);  
}  
});  
  
System.out.println("这是异步读取,将会出现在打印值之前");

image.png

写线圈值

同步写

String connectionString = "modbus-tcp://127.0.0.1";  
PlcConnection plcConnection = new PlcDriverManager().getConnection(connectionString);  
boolean canWrite = plcConnection.getMetadata().canWrite();  
if (!canWrite) {  
log.error("该连接不支持写入数据");  
return;  
}  
  
PlcWriteRequest.Builder builder = plcConnection.writeRequestBuilder();  
builder.addItem("value-1", "coil:1", false);  
PlcWriteRequest writeRequest = builder.build();  
  
PlcWriteResponse response = writeRequest.execute().get();  
  
for (String fieldName : response.getFieldNames()) {  
PlcResponseCode responseCode = response.getResponseCode(fieldName);  
if(responseCode == PlcResponseCode.OK) {  
log.info("Value[" + fieldName + "]: updated");  
}  
else {  
log.error("Error[" + fieldName + "]: " + responseCode.name());  
}  
}

结果是写入失败: image.png

暂时没找到原因!!!

异步写

String connectionString = "modbus-tcp://127.0.0.1";  
PlcConnection plcConnection = new PlcDriverManager().getConnection(connectionString);  
boolean canWrite = plcConnection.getMetadata().canWrite();  
if (!canWrite) {  
log.error("该连接不支持写入数据");  
return;  
}  
  
PlcWriteRequest.Builder builder = plcConnection.writeRequestBuilder();  
builder.addItem("value-1", "coil:1", false);  
PlcWriteRequest writeRequest = builder.build();  
  
CompletableFuture<? extends PlcWriteResponse> asyncResponse = writeRequest.execute();  
asyncResponse.whenComplete((response, throwable) -> {  
for (String fieldName : response.getFieldNames()) {  
PlcResponseCode responseCode = response.getResponseCode(fieldName);  
if(responseCode == PlcResponseCode.OK) {  
log.info("Value[" + fieldName + "]: updated");  
}  
else {  
log.error("Error[" + fieldName + "]: " + responseCode.name());  
}  
}  
});  
System.out.println("这是异步读取,将会出现在打印值之前");

结果同同步写一样,暂时没找到原因。

读取Holding Register

Holding Register(保持寄存器):指的是Modbus设备中用于存储控制和状态数据的16位数据。这些数据通常表示为可读写的保持寄存器,用于保存设备的状态信息、设置参数等。

在操作Holding Register时得先在ModbusPal里加地址,不然会找不到。

image.png

同步读

只需要在addItem时调整一下即可:

builder.addItem("value-3", "holding-register:1");  
builder.addItem("value-4", "holding-register:3[4]");

image.png

异步读

String connectionString = "modbus-tcp://127.0.0.1";  
PlcConnection plcConnection = new PlcDriverManager().getConnection(connectionString);  
boolean canRead = plcConnection.getMetadata().canRead();  
if (!canRead) {  
log.error("该连接不支持读取数据");  
return;  
}  
  
PlcReadRequest.Builder builder = plcConnection.readRequestBuilder();  
builder.addItem("values", "holding-register:1[10]");  
PlcReadRequest readRequest = builder.build();  
  
CompletableFuture<? extends PlcReadResponse> asyncResponse = readRequest.execute();  
asyncResponse.whenComplete((response, throwable) -> {  
Short[] holdingRegisterArray = new Short[10];  
response.getAllShorts("values").toArray(holdingRegisterArray);  
for (int i = 0; i < holdingRegisterArray.length; i++) {  
log.info("holding-register[" + i + "]: " + holdingRegisterArray[i]);  
}  
});  
  
System.out.println("这是异步读取,将会出现在打印值之前");  
}

image.png

写入Holding Register

同步写

同理在addItem时替换成:builder.addItem("value-1", "holding-register:1", 26);

image.png

image.png

异步写

String connectionString = "modbus-tcp://127.0.0.1";  
PlcConnection plcConnection = new PlcDriverManager().getConnection(connectionString);  
boolean canWrite = plcConnection.getMetadata().canWrite();  
if (!canWrite) {  
log.error("该连接不支持写入数据");  
return;  
}  
  
PlcWriteRequest.Builder builder = plcConnection.writeRequestBuilder();  
for (int i = 1; i <= 10; i++) {  
builder.addItem("value-" + i, "holding-register:" + i + ":UINT[1]", 10 - i);  
}  
PlcWriteRequest writeRequest = builder.build();  
  
CompletableFuture<? extends PlcWriteResponse> asyncResponse = writeRequest.execute();  
asyncResponse.whenComplete((response, throwable) -> {  
for (String fieldName : response.getFieldNames()) {  
PlcResponseCode responseCode = response.getResponseCode(fieldName);  
if (responseCode == PlcResponseCode.OK) {  
log.info("Value[" + fieldName + "]: updated");  
} else {  
log.error("Error[" + fieldName + "]: " + responseCode.name());  
}  
}  
});  
System.out.println("这是异步读取,将会出现在打印值之前");

image.png