Apache PLC4X 1.0.0 常用工具与 API 详解

Apache PLC4X 1.0.0 常用工具与 API 详解:1.0.0 构件全家福、PlcDriverManager/PlcConnection/读写/订阅 API 签名与取值方法、plc4j-tools-connection-cache 连接缓存与 plc4j-tools-opm 实体映射两大工具实测示例,附注意事项与常见问题。

Apache PLC4X2026/10/12

Apache PLC4X 1.0.0 常用工具与 API 详解

目标:梳理 Apache PLC4X 1.0.0(plc4j)的常用构件与核心 API:驱动管理器、连接、读写、订阅的接口签名与取值方法,并实测 1.0.0 两件官方工具(连接缓存 plc4j-tools-connection-cache、实体映射 plc4j-tools-opm)的最小可运行示例。所有命令与回显均在 Ubuntu 22.04.5 + JDK 21 环境实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS
  • Java:OpenJDK 21.0.12.1
  • 构建工具:Apache Maven 3.6.3
  • PLC4X 版本:1.0.0(构件间版本必须一致,见《Ubuntu 22.04 安装 Apache PLC4X 1.0.0 指南》)
  • 驱动:simulated(模拟 PLC,无需真实硬件)
  • 示例工程目录:/root/plc4x-demo/
  • 演示账号:root(模拟驱动不支持认证,登录类参数仅对支持的协议生效)

二、工具与构件概览

1.0.0 对 0.x 的构件做了重组:连接池(旧 plc4j-connection-pool)更名为 plc4j-tools-connection-cache,OPM 归入 plc4j-tools-opm,SPI 拆分为多个小构件。Maven Central 上 1.0.0 可用构件(逐个探测均存在,分组 org.apache.plc4x):

类别构件用途
APIplc4j-apiPlcDriverManager、连接、消息、取值接口
SPIplc4j-spi、plc4j-spi-drivers、plc4j-spi-values、plc4j-spi-fields、plc4j-spi-config、plc4j-spi-buffers-api、plc4j-spi-buffers-byte、plc4j-spi-utils驱动框架与值体系(传递依赖,无需显式引入)
传输plc4j-transports-api、plc4j-transports-tcp、plc4j-transports-udp、plc4j-transports-serial 等真实协议驱动的传输层
驱动plc4j-driver-s7、plc4j-driver-modbus、plc4j-driver-opcua、plc4j-driver-ads、plc4j-driver-eip、plc4j-driver-ab-eth、plc4j-driver-firmata、plc4j-driver-knxnetip、plc4j-driver-iec-60870、plc4j-driver-can、plc4j-driver-canopen、plc4j-driver-slmp、plc4j-driver-umas、plc4j-driver-mock、plc4j-driver-simulated各协议驱动
聚合plc4j-driver-all一键聚合 15 个驱动(ab-eth、ads、can、canopen、eip、firmata、iec-60870、knxnetip、modbus、opcua、plc4x、s7、simulated、slmp、umas,实测 pom 依赖列表)
工具plc4j-tools-connection-cache连接缓存(租借式复用连接)
工具plc4j-tools-opm实体映射(注解驱动读写)
工具plc4j-tools-capture-replay报文捕获与回放
工具plc4j-tools-event-pump订阅事件泵
其他plc4j-utils-audit-log-api/impl、plc4j-osgi、plc4c、plc4net、plc4py审计日志、OSGi、其他语言实现

三、前置条件

  • JDK 21 与 Maven 已就绪(java -version 回显 openjdk 21.0.12.1);

  • 示例工程 /root/plc4x-demo/ 已按《Apache PLC4X 1.0.0 构建并运行示例应用指南》创建,pom 已含 plc4j-tools-connection-cache 与 plc4j-tools-opm;

  • 运行命令统一带堆与 classpath:

    java -Xms256m -Xmx512m -cp "target/classes:$(cat /tmp/cp.txt)" demo.类名
    

    其中 /tmp/cp.txt 由 mvn dependency:build-classpath -Dmdep.outputFile=/tmp/cp.txt 生成。

四、核心 API 详解

4.1 驱动管理器(DefaultPlcDriverManager)

1.0.0 的驱动管理器类在 org.apache.plc4x.java 包(不再在 spi 包),连接方法移到了它实现的 PlcConnectionFactory 接口上(0.12 时代的 PlcDriverManager.getConnection 已不在此接口)。javap 实测签名(摘录):

public class org.apache.plc4x.java.DefaultPlcDriverManager
  implements org.apache.plc4x.java.api.PlcDriverManager,org.apache.plc4x.java.api.PlcConnectionFactory
  public org.apache.plc4x.java.DefaultPlcDriverManager();
  public org.apache.plc4x.java.DefaultPlcDriverManager(java.lang.ClassLoader);
  public org.apache.plc4x.java.api.PlcConnection getConnection(java.lang.String) throws org.apache.plc4x.java.api.exceptions.PlcConnectionException;
  public org.apache.plc4x.java.api.PlcConnection getConnection(java.lang.String, org.apache.plc4x.java.api.authentication.PlcAuthentication) throws org.apache.plc4x.java.api.exceptions.PlcConnectionException;

常用方法与用法:

方法用途实测要点
getProtocolCodes()列出 classpath 已注册协议码回显 [simulated]
getDriverForUrl(url)按连接串取驱动连接串不合法抛 PlcConnectionException
getConnection(url)建立连接(内部自动 connect)返回即连接态
getConnection(url, auth)带认证连接simulated 直接拒绝(见下)

带认证连接示例(演示账号 root / q1w2e3r4):

driverManager.getConnection("simulated:device1",
    new PlcUsernamePasswordAuthentication("root", "q1w2e3r4"));

实测回显:

org.apache.plc4x.java.api.exceptions.PlcConnectionException: Simulated driver does not support authentication.

4.2 连接(PlcConnection)

PlcConnection 接口核心方法(javap 实测摘录):connect()、isConnected()、close()、ping()(返回 CompletableFuture)、getMetadata()、parseTagAddress()、readRequestBuilder()、writeRequestBuilder()、subscriptionRequestBuilder()、unsubscriptionRequestBuilder()、browseRequestBuilder()。

连接与元数据实测行为:

  • getConnection 返回的连接 isConnected() 为 true;
  • simulated 驱动的 getProtocolCode()、getProtocolName() 返回 unknown / unknown(库默认实现);
  • ping().get(5, TimeUnit.SECONDS).getResponseCode() 回显 OK;
  • getMetadata().isBrowseSupported() 对 simulated 为 false,浏览接口直接抛 UnsupportedOperationException: This driver doesn't support browsing(实测)。

4.3 读写 API

读:readRequestBuilder() 构建请求,addTagAddress(标签名, 地址) 可多次调用,build() 后 execute().get(超时, 单位) 取响应;响应按取值方法取数:

取值方法目标类型本示例标签
getInteger(name)整型RANDOM/foo:INT
getFloat(name)浮点RANDOM/bar:REAL
getLong(name)长整型STATE/counter:DINT
getString(name)字符串STATE/text:STRING
getObject(name)通用兜底任意类型

每个方法都有 isValidXxx(先判断再取)与带下标/批量变体。getTagNames() 返回请求中全部标签名;getResponseCode(name) 返回 PlcResponseCode(实测取值:OK、NOT_FOUND、ACCESS_DENIED、INVALID_ADDRESS、INVALID_DATATYPE、INVALID_DATA、INTERNAL_ERROR、REMOTE_BUSY、REMOTE_ERROR、UNSUPPORTED、RESPONSE_PENDING、NOT_READY、OUT_OF_RANGE)。

写:writeRequestBuilder().addTagAddress(标签名, 地址, 值),值为与地址值类型匹配的对象(整型字面量、字符串等)。实测写入 STATE/counter:DINT 赋 4242、STATE/text:STRING 赋 hello plc4x,响应码均为 OK,回读一致。

4.4 订阅 API

subscriptionRequestBuilder() 支持四类订阅(builder 方法实测签名,参数可变参数形态用数组描述):

方法订阅方式关键参数
addCyclicTagAddress(名, 地址, Duration)周期推送当前值周期,如 Duration.ofMillis(200)
addChangeOfStateTagAddress(名, 地址)值变化时推送STATE 标签
addEventTagAddress(名, 地址)设备事件事件类标签
setConsumer(Consumer)消费者也可按标签传

消费者回调收到 PlcSubscriptionEvent(继承读响应,可直接 getLong、getInteger 等取值,getTimestamp() 为事件时间)。退订用 unsubscriptionRequestBuilder().addHandles(响应中的 subscriptionHandle) 构建退订请求执行。

实测结果(周期 200ms 订阅 STATE/counter:DINT):三个事件约间隔 200ms,值为 4242;写入 1024 后下一事件变为 1024;退订后不再推送。

⚠️ 注意:RANDOM 标签做周期订阅不会产生事件(值不落状态表,推送取值为空被跳过,实测验证);周期与变化订阅务必使用 STATE 标签。

4.5 实体映射工具(plc4j-tools-opm)

OPM 用注解把「一类标签」映射成一个 Java 实体,PlcEntityManager(PlcDriverManager) 两个方法走天下:read(实体类, 连接串)、write(实体类, 连接串, 实体对象);地址以连接串为单位传入,标签地址写在字段注解上。@PlcTag 标注字段、@PlcEntity 标注实体类。

/root/plc4x-demo/src/main/java/demo/OpmDemo.java:

package demo;

import org.apache.plc4x.java.DefaultPlcDriverManager;
import org.apache.plc4x.java.opm.PlcEntity;
import org.apache.plc4x.java.opm.PlcEntityManager;
import org.apache.plc4x.java.opm.PlcTag;

public class OpmDemo {

    @PlcEntity
    public static class MachineState {
        @PlcTag("RANDOM/foo:INT")
        private int foo;

        public int getFoo() { return foo; }
        public void setFoo(int foo) { this.foo = foo; }
    }

    public static void main(String[] args) throws Exception {
        DefaultPlcDriverManager driverManager = new DefaultPlcDriverManager();
        PlcEntityManager entityManager = new PlcEntityManager(driverManager);

        MachineState state = new MachineState();
        state.setFoo(1);

        MachineState written = entityManager.write(MachineState.class, "simulated:device1", state);
        System.out.println("[opm] write returned foo = " + written.getFoo());

        MachineState read = entityManager.read(MachineState.class, "simulated:device1");
        System.out.println("[opm] read foo(INT) = " + read.getFoo());
    }
}

运行与实测回显:

cd /root/plc4x-demo
mvn clean compile
java -Xms256m -Xmx512m -cp "target/classes:$(cat /tmp/cp.txt)" demo.OpmDemo
[opm] write returned foo = 1
[opm] read foo(INT) = -27722

⚠️ 注意:OPM 每次 read/write 都新开连接,STATE 标签的值不跨连接保留;实测把 STATE/text:STRING 放进实体后,新连接上 read 抛 PlcRuntimeException: Tag could not be fetched, response was NOT_FOUND。实体里应使用每次读取都有值的标签(如 RANDOM),或配合连接缓存复用连接。

五、连接缓存工具(plc4j-tools-connection-cache)实测

旧 plc4j-connection-pool 在 1.0.0 中由 PlcConnectionCache 替代:实现 PlcConnectionManager,把按连接串的连接租借出去,空闲与最长租期可配,归还没真正关闭底层连接。

Builder 配置项(源码常量实测):withConnectionFactory、withMaxIdleTime(默认 5 分钟)、withMaxLeaseTime(默认 1 分钟)、withMaxWaitTime(默认 30 秒)、withPingTimeout(默认 5 秒)、withIdlePingThreshold(默认 30 秒)、withCloseTimeout(默认 5 秒)。

/root/plc4x-demo/src/main/java/demo/CacheDemo.java:

package demo;

import org.apache.plc4x.java.DefaultPlcDriverManager;
import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.messages.PlcReadResponse;
import org.apache.plc4x.java.utils.cache.PlcConnectionCache;

import java.util.concurrent.TimeUnit;

public class CacheDemo {

    public static void main(String[] args) throws Exception {
        DefaultPlcDriverManager driverManager = new DefaultPlcDriverManager();
        PlcConnectionCache cache = PlcConnectionCache.getBuilder()
            .withConnectionFactory(driverManager)
            .withMaxIdleTime(30, TimeUnit.SECONDS)
            .withMaxLeaseTime(30, TimeUnit.SECONDS)
            .build();

        // 第一次租借连接:写入 STATE 标签
        try (PlcConnection conn1 = cache.getConnection("simulated:device1")) {
            System.out.println("[cache] lease-1 class = " + conn1.getClass().getName());
            conn1.writeRequestBuilder()
                .addTagAddress("kv", "STATE/kv:DINT", 7)
                .build().execute().get(5, TimeUnit.SECONDS);
            System.out.println("[cache] lease-1 wrote STATE/kv:DINT = 7");
        } // 归还连接,但底层连接并未关闭

        // 第二次租借同一连接串:缓存复用底层连接,STATE 值仍在
        try (PlcConnection conn2 = cache.getConnection("simulated:device1")) {
            PlcReadResponse resp = conn2.readRequestBuilder()
                .addTagAddress("kv", "STATE/kv:DINT")
                .build().execute().get(5, TimeUnit.SECONDS);
            System.out.println("[cache] lease-2 read back STATE/kv:DINT = " + resp.getLong("kv"));
        }

        cache.close();
        System.out.println("[cache] closed");
    }
}

运行与实测回显:

cd /root/plc4x-demo
mvn clean compile
java -Xms256m -Xmx512m -cp "target/classes:$(cat /tmp/cp.txt)" demo.CacheDemo
[cache] lease-1 class = org.apache.plc4x.java.utils.cache.LeasedPlcConnection
[cache] lease-1 wrote STATE/kv:DINT = 7
[cache] lease-2 read back STATE/kv:DINT = 7
[cache] closed

解读:两次租借得到的是同一个底层连接(否则第二台全新模拟设备的 STATE/kv 应为空),证明缓存复用生效;LeasedPlcConnection 即租借代理类。

六、完整最小可运行示例

按《Apache PLC4X 1.0.0 构建并运行示例应用指南》第五节的 HelloPlc4x 编译运行即可(覆盖读 RANDOM、写读 STATE、STDOUT 回显、周期订阅与退订全流程),此处不重复贴码。本篇的两个工具示例(OpmDemo、CacheDemo)与主示例共用同一工程与 pom,单独任取一个主类运行均可。

七、注意事项

⚠️ 注意:

  1. 0.x 升 1.0.0 的 API 断点:消息类包名由 api.message 变为 api.messages;驱动管理器类变为 org.apache.plc4x.java.DefaultPlcDriverManager;连接方法在 PlcConnectionFactory 接口上。
  2. 驱动发现靠 ServiceLoader(每个驱动 jar 内 META-INF/services 注册 PlcDriver),只引 plc4j-api 不引任何驱动时 getProtocolCodes() 返回空集合。
  3. 连接必须用 try-with-resources 关闭;长连接进程配合连接缓存使用,避免高频建连。
  4. plc4j-tools-capture-replay 与 plc4j-tools-event-pump 构件在 1.0.0 已可用(Maven Central 实测存在),本文未展开实测用法,接入前请以官方文档与源码为准。

八、常见问题

Q1:getProtocolCodes() 只显示 simulated,想接 S7 怎么办? 追加 plc4j-driver-s7(或一次性引 plc4j-driver-all)依赖并保持 1.0.0 同版本,重启后协议码列表即含 s7。

Q2:getConnection 报 Unable to find driver for protocol 's7'? classpath 缺对应驱动 jar(实测该错误即此因)。检查 mvn dependency:list 是否含目标 plc4j-driver-*。

Q3:订阅一直收不到事件? 三步排查:标签是否 STATE 类(RANDOM 周期订阅无事件,实测);消费者是否注册(builder 传入回调或 setConsumer);getResponseCode 是否 OK。

Q4:OPM 读取报 NOT_FOUND? 连接串对应的设备上该标签无值(STATE 标签新连接为空,实测现象)。改用 RANDOM 标签验证,或用连接缓存保证同一连接内有写入。

九、参考链接

  • Apache PLC4X 官网:https://plc4x.apache.org/
  • plc4j javadoc(1.0.0):https://plc4x.apache.org/plc4x/latest/users/plc4j/
  • Maven Central 构件目录:https://repo1.maven.org/maven2/org/apache/plc4x/
  • 1.0.0 源码树:https://github.com/apache/plc4x/tree/rel/1.0.0/plc4j