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 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):
| 类别 | 构件 | 用途 |
|---|---|---|
| API | plc4j-api | PlcDriverManager、连接、消息、取值接口 |
| SPI | plc4j-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,单独任取一个主类运行均可。
七、注意事项
⚠️ 注意:
- 0.x 升 1.0.0 的 API 断点:消息类包名由
api.message变为api.messages;驱动管理器类变为org.apache.plc4x.java.DefaultPlcDriverManager;连接方法在PlcConnectionFactory接口上。- 驱动发现靠 ServiceLoader(每个驱动 jar 内
META-INF/services注册 PlcDriver),只引 plc4j-api 不引任何驱动时getProtocolCodes()返回空集合。- 连接必须用 try-with-resources 关闭;长连接进程配合连接缓存使用,避免高频建连。
- 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