PLC4X 是一组库,用于通过多种协议与工业可编程逻辑控制器(PLC)进行通信。PLC4X 实现了最常见的工业通信协议,并使用这些协议与各种类型的设备进行通信。PLC4X 还是很强大的,我项目中就使用了这个库。

PLC4X 可以使用 Java 和 Go 来调用,我是搞 Java 的所以这个教程也就以 java 为主了。

PLC4X 支持多种物联网协议,我这里常用的为 OPC-UA 和 Modbus-TCP。其他的也大差不差,不过项目中没有用到也就没有示例了。

我是使用 Jdk-17、Maven、Modbus Slave、Prosys OPC UA Simulation Server 来测试的,如果没有请准备一下或者有替代品实现。

使用 plc4x 获取 MODBUS-TCP 的数据

导入 Maven 坐标

<dependencies>
    <!-- 这只是统一了接口,类似于 mybatis 但是如果要读取对应的协议还要添加不同的 driver,这个就像 Mysql Oracle区分一样-->
    <dependency>
        <groupId>org.apache.plc4x</groupId>
        <artifactId>plc4j-api</artifactId>
        <version>0.12.0</version>
    </dependency>
    <!-- 这个就是对应的  driver-->
    <dependency>
        <groupId>org.apache.plc4x</groupId>
        <artifactId>plc4j-driver-modbus</artifactId>
        <version>0.12.0</version>
    </dependency>
</dependencies>

打开 Modbus Slave 来创建几个测试用例

  • F3 打开连接项(这个一般不用改,反正也是用来测试的。不过你的端口如果和我不一样的话后面代码需要你自己修改一下端口)

2025-07-10T09:12:08.png

  • F8 打开 Slave 定义,改掉箭头指向的内容。这里之前是 0 和 未选中

2025-07-10T09:12:14.png

  • 在数据面板里配置几个数据用来测试不同的内容

2025-07-10T09:12:20.png

注:修改不同的类型数据就是右键->format 即可

2025-07-10T09:12:26.png

编写一个代码来读取刚才配置的测试用例

package com.runbrick;

import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.PlcDriverManager;
import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.apache.plc4x.java.api.messages.PlcReadResponse;

import java.util.Collection;
import java.util.concurrent.CompletableFuture;

public class Main {
    public static void main(String[] args) {
        String connectionString = "modbus-tcp:tcp://127.0.0.1:502";

        try (PlcConnection plcConnection = PlcDriverManager.getDefault().getConnectionManager().getConnection(connectionString)) {
            // 这里实现各种逻辑
            PlcReadRequest.Builder builder = plcConnection.readRequestBuilder();
            builder.addTagAddress("value-1", "holding-register:1:REAL");
            builder.addTagAddress("value-2", "holding-register:3:REAL");
            builder.addTagAddress("value-3", "holding-register:5:INT");
            builder.addTagAddress("value-4", "holding-register:6:INT");
            PlcReadRequest readRequest = builder.build();

            CompletableFuture<? extends PlcReadResponse> asyncResponse = readRequest.execute();
            // 这里只是启动了一个main ,所以使用了同步获取
            // 如果你是web服务或者是在下面 sleep 了,就可以享受 CompletableFuture 带来的便捷了
            PlcReadResponse plcReadResponse = asyncResponse.get();
            Collection<String> tagNames = plcReadResponse.getTagNames();
            tagNames.forEach(tagName -> {
                if ("value-3".equals(tagName)) {
//                    这个是二级制的所以要转为二进制
                    System.out.println(Integer.toBinaryString(plcReadResponse.getInteger(tagName)));
                } else {
                    System.out.println(plcReadResponse.getObject(tagName));
                }
            });

        } catch (PlcConnectionException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

如果没有错的话应该返回下面的结果

12.55
255.55
11111111111111111000100001000010
1230

下面来讲讲 builder.addTagAddress("value-1", "holding-register:1:REAL"); 中的 "holding-register:1:REAL" 是什么

首先 holding-register 是对象类型 也就是刚才 F8 打开的那个界面中的 Function ,plc4x支持下面这几种类型

  • coil:线圈 (0x),可读可写布尔值。
  • discrete-input:离散输入 (1x),只读布尔值。
  • holding-register:保持寄存器 (4x),可读可写16位整数(或组合成其他类型)。
  • input-register:输入寄存器 (3x),只读16位整数(或组合成其他类型)。

holding-register 的冒号后面有个 1 代表着不同的点位,Float 占了两个点位。

最后那个 REAL 是对应点位的类型,PLC4x 支持了很多类型

  • BOOL (boolean) BOOL(布尔型)
  • SINT (int 8) 有符号 8 位整数(SINT)
  • USINT (uint 8) 无符号 8 位整数(USINT)
  • BYTE (uint 8) 无符号 8 位字节(BYTE)
  • INT (int 16) 有符号 16 位整数(INT)
  • UINT (uint 16) 无符号 16 位整数(UINT)
  • WORD (uint 16) 无符号 16 位字(WORD)
  • DINT (int 32) 有符号 32 位整数(DINT)
  • UDINT (uint 32) 无符号 32 位整数(UDINT)
  • DWORD (uint 32) 无符号 32 位双字(DWORD)
  • LINT (int 64) 有符号 64 位整数(LINT)
  • ULINT (uint 64) 无符号 64 位整数(ULINT)
  • LWORD (uint 64) 无符号 64 位长字(LWORD)
  • REAL (float) 单精度浮点数(REAL)
  • LREAL (double) 双精度浮点数(LREAL)
  • CHAR (char) 字符(CHAR)
  • WCHAR (2 byte char) 双字节字符(WCHAR)

示例地址:

  • holding-registerINT:读取第100号保持寄存器,解析为16位有符号整数。
  • coil:50:读取第50号线圈的状态(布尔值)。
  • holding-register:200:REAL:从第200号保持寄存器开始,读取两个连续的寄存器,并解析为一个32位浮点数。

使用 plc4x 写入 MODBUS-TCP 的数据


public class Main {
    public static void main(String[] args) {
        String connectionString = "modbus-tcp:tcp://127.0.0.1:502";

        try (PlcConnection plcConnection = PlcDriverManager.getDefault().getConnectionManager().getConnection(connectionString)) {

            if (!plcConnection.getMetadata().isWriteSupported()) {
                System.out.println("不支持写操作");
            } else {
                PlcWriteRequest.Builder builder = plcConnection.writeRequestBuilder();
                builder.addTagAddress("value-1", "holding-register:1:REAL", 12.45f);
                PlcWriteRequest writeRequest = builder.build();

                CompletableFuture<? extends PlcWriteResponse> asyncResponse = writeRequest.execute();
                PlcWriteResponse plcWriteResponse = asyncResponse.get();
                System.out.println(plcWriteResponse.getTagNames());
            }


        } catch (PlcConnectionException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

其余的和读取步骤是一样的只是将 readRequestBuilder 换成了 writeRequestBuilder 并把 builder.addTagAddress 的参数加了一个要改变的内容

这是读: builder.addTagAddress("value-1", "holding-register:1:REAL");

这是写: builder.addTagAddress("value-1", "holding-register:1:REAL", 12.45f);

其余的是一样的。

使用 plc4x 获取 OPC-UA 的数据

导入 OPC-UA 的 driver 坐标,其余的和上面 modbus 的坐标一样。

        <!-- 这个就是对应的  driver-->
        <dependency>
            <groupId>org.apache.plc4x</groupId>
            <artifactId>plc4j-driver-opcua</artifactId>
            <version>0.12.0</version>
        </dependency>
        <!-- 如果是 opcua 就要加上这个不然会报错  org.apache.plc4x.java.api.exceptions.PlcConnectionException: Unsupported transport tcp-->
        <dependency>
            <groupId>org.apache.plc4x</groupId>
            <artifactId>plc4j-driver-plc4x</artifactId>
            <version>0.12.0</version>
        </dependency>

打开 Prosys OPC UA Simulation Server 点击 ,这时候会自己运行

2025-07-10T09:12:46.png

点击 Objects ,默认是给了我们几个数据的,我们用这几个测试数据来测试读取就够了

2025-07-10T09:12:52.png

实现读取的代码

package com.runbrick;

import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.PlcDriverManager;
import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.apache.plc4x.java.api.messages.PlcReadResponse;

import java.util.Collection;
import java.util.concurrent.CompletableFuture;

public class Main {
    public static void main(String[] args) {
        String connectionString = "opcua:tcp://DESKTOP-SF0ESP4:53530/OPCUA/SimulationServer";
        try (PlcConnection plcConnection = PlcDriverManager.getDefault().getConnectionManager().getConnection(connectionString)) {
//             这里实现各种逻辑
            PlcReadRequest.Builder builder = plcConnection.readRequestBuilder();
            builder.addTagAddress("value-1", "ns=3;i=1003;LREAL");
            PlcReadRequest readRequest = builder.build();

            CompletableFuture<? extends PlcReadResponse> response = readRequest.execute();
            PlcReadResponse plcReadResponse = response.get();
            Collection<String> tagNames = plcReadResponse.getTagNames();
            tagNames.forEach(tagName -> {
                System.out.println(plcReadResponse.getObject(tagName));
            });

        } catch (PlcConnectionException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

这个读的代码和 modbus 很像 就是将协议改掉了由 modbus-tcp 改为了 opcua ,然后修改了 builder.addTagAddress的第二参数为 ns=3;i=1003;LREA 这样就能实现读取 opcua的数据了

plc4x 也支持了 opcua 的很多种数据类型

  • BOOL (boolean) BOOL(布尔型)
  • SINT (int 8) 有符号 8 位整数(SINT)
  • USINT (uint 8) 无符号 8 位整数(USINT)
  • BYTE (uint 8) 无符号 8 位字节(BYTE)
  • INT (int 16) 有符号 16 位整数(INT)
  • UINT (uint 16) 无符号 16 位整数(UINT)
  • WORD (uint 16) 字(无符号 16 位整数)
  • DINT (int 32) 有符号 32 位整数(DINT)
  • UDINT (uint 32) 无符号 32 位整数(UDINT)
  • DWORD (uint 32) 无符号 32 位双字(DWORD)
  • LINT (int 64) 有符号 64 位整数(LINT)
  • ULINT (uint 64) 无符号 64 位整数(ULINT)
  • LWORD (uint 64) 无符号 64 位长字(LWORD)
  • REAL (float) 实数(浮点数)
  • LREAL (double) 长实数(双精度)
  • CHAR (char) 字符(CHAR)
  • WCHAR (2 byte char) 双字节字符(WCHAR)
  • STRING (utf-8) 字符串(UTF-8 编码)
  • WSTRING (utf-16) 宽字符串(UTF-16 编码)

使用 plc4x 写入 OPC-UA 的数据

先上个代码

package com.runbrick;

import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.PlcDriverManager;
import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
import org.apache.plc4x.java.api.messages.PlcWriteRequest;
import org.apache.plc4x.java.api.messages.PlcWriteResponse;

import java.util.concurrent.CompletableFuture;

public class Main {
    public static void main(String[] args) {
        String connectionString = "opcua:tcp://DESKTOP-SF0ESP4:53530/OPCUA/SimulationServer";
        try (PlcConnection plcConnection = PlcDriverManager.getDefault().getConnectionManager().getConnection(connectionString)) {
            if (!plcConnection.getMetadata().isWriteSupported()) {
                System.out.println("不支持写操作");
            } else {
                PlcWriteRequest.Builder builder = plcConnection.writeRequestBuilder();
                builder.addTagAddress("value-1", "ns=3;i=1003;LREAL", Double.valueOf("12.45"));
                PlcWriteRequest writeRequest = builder.build();

                CompletableFuture<? extends PlcWriteResponse> asyncResponse = writeRequest.execute();
                PlcWriteResponse plcWriteResponse = asyncResponse.get();
                System.out.println(plcWriteResponse.getTagNames());
            }


        } catch (PlcConnectionException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

可以看到其实和modbus没什么两样。记得执行之前先把下面的配置打开了,要不然数据一转而过就看不到了

2025-07-10T09:13:07.png

执行后数据就已经改变了

2025-07-10T09:13:14.png

PLC4x的代码风格高度统一,这样我们就不用再每个协议都要重新写一遍代码,或者自己实现一套这样的逻辑了。

其他的协议我目前没有用到,你们有需要可以去官网找找是否已经支持

注:

标签: java, IOT, plc4x

添加新评论