|
|
@@ -0,0 +1,194 @@
|
|
|
+package com.jeesite.modules;
|
|
|
+
|
|
|
+import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
|
|
|
+import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
|
|
|
+import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
|
|
|
+import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider;
|
|
|
+import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
|
|
|
+import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
|
|
|
+import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
|
|
|
+import org.eclipse.milo.opcua.stack.client.DiscoveryClient;
|
|
|
+import org.eclipse.milo.opcua.stack.core.AttributeId;
|
|
|
+import org.eclipse.milo.opcua.stack.core.Identifiers;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseDirection;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseResultMask;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.structured.BrowseDescription;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.structured.BrowseResult;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
|
|
|
+import org.eclipse.milo.opcua.stack.core.types.structured.ReferenceDescription;
|
|
|
+
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.List;
|
|
|
+import java.util.concurrent.atomic.AtomicLong;
|
|
|
+
|
|
|
+import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
|
|
|
+
|
|
|
+/**
|
|
|
+ * OPC UA 客户端示例(Eclipse Milo 0.6.9)
|
|
|
+ *
|
|
|
+ * 使用场景:
|
|
|
+ * - 与西门子 1500、KEPServerEX、CodeSys、Beckhoff TwinCAT 等 OPC UA Server 通信
|
|
|
+ * - MES/SCADA 跨车间统一采集
|
|
|
+ * - 需要权限/加密的场合
|
|
|
+ */
|
|
|
+public class OpcUa {
|
|
|
+
|
|
|
+ /** OPC UA 服务器地址,替换为现场地址 */
|
|
|
+ private static final String ENDPOINT_URL = "opc.tcp://10.0.20.141:4840";
|
|
|
+
|
|
|
+ public static void main(String[] args) throws Exception {
|
|
|
+ OpcUaClient client = createClient(ENDPOINT_URL);
|
|
|
+
|
|
|
+ // 1. 连接
|
|
|
+ client.connect().get();
|
|
|
+ System.out.println("已连接:" + ENDPOINT_URL);
|
|
|
+
|
|
|
+ // 2. 从根节点开始浏览(只展示前两层,实际可递归)
|
|
|
+ browse(client, Identifiers.RootFolder, 0, 2);
|
|
|
+
|
|
|
+ // 3. 读单个节点的值
|
|
|
+ // NodeId 写法:ns=<命名空间索引>;s=<字符串标识> 或 ns=2;i=1001
|
|
|
+ NodeId tagNode = NodeId.parse("ns=2;s=/Channel/Spindle/actSpeed\n");
|
|
|
+ DataValue dv = client.readValue(0, TimestampsToReturn.Both, tagNode).get();
|
|
|
+ System.out.println("读取 " + tagNode + " => value=" + dv.getValue().getValue()
|
|
|
+ + ", status=" + dv.getStatusCode()
|
|
|
+ + ", serverTime=" + dv.getServerTime());
|
|
|
+
|
|
|
+ // 4. 写单个节点(按目标类型构造 Variant)
|
|
|
+ // client.writeValue(tagNode, DataValue.valueOnly(new Variant(123))).get();
|
|
|
+
|
|
|
+ // 5. 创建订阅,自动推送变化值(推荐用于 MES 实时采集)
|
|
|
+ subscribe(client, tagNode);
|
|
|
+
|
|
|
+ // 演示性等待一会儿接收订阅回调
|
|
|
+ Thread.sleep(10_000);
|
|
|
+
|
|
|
+ client.disconnect().get();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建并创建 OpcUaClient
|
|
|
+ * - 自动从服务器拉取 Endpoint 列表
|
|
|
+ * - 这里默认选用 None 安全策略 + 匿名登录,便于联调
|
|
|
+ * - 实际生产建议换成 Basic256Sha256 + 用户名密码或证书
|
|
|
+ */
|
|
|
+ private static OpcUaClient createClient(String endpointUrl) throws Exception {
|
|
|
+ // 拉取服务器所有 Endpoint
|
|
|
+ List<EndpointDescription> endpoints = DiscoveryClient.getEndpoints(endpointUrl).get();
|
|
|
+
|
|
|
+ // 选一个 Endpoint:这里挑选 SecurityPolicy=None 的,最简单
|
|
|
+ EndpointDescription endpoint = endpoints.stream()
|
|
|
+ .filter(e -> e.getSecurityPolicyUri().endsWith("#None"))
|
|
|
+ .findFirst()
|
|
|
+ .orElseThrow(() -> new IllegalStateException("没有可用的 Endpoint:" + endpointUrl));
|
|
|
+
|
|
|
+ // 身份认证:三选一
|
|
|
+ IdentityProvider identity = new AnonymousProvider(); // 匿名
|
|
|
+// IdentityProvider identity = new UsernameProvider("admin", "123456"); // 用户名密码
|
|
|
+ // IdentityProvider identity = new X509IdentityProvider(cert, privateKey); // 证书
|
|
|
+
|
|
|
+ OpcUaClientConfig config = OpcUaClientConfig.builder()
|
|
|
+ .setApplicationName(LocalizedText.english("MES OPC UA Client"))
|
|
|
+ .setApplicationUri("urn:mes:opcua:client")
|
|
|
+ .setEndpoint(endpoint)
|
|
|
+ .setIdentityProvider(identity)
|
|
|
+ .setRequestTimeout(uint(5000))
|
|
|
+ .build();
|
|
|
+
|
|
|
+ return OpcUaClient.create(config);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 递归浏览节点树
|
|
|
+ */
|
|
|
+ private static void browse(OpcUaClient client, NodeId nodeId, int depth, int maxDepth) throws Exception {
|
|
|
+ if (depth > maxDepth) return;
|
|
|
+
|
|
|
+ BrowseDescription browse = new BrowseDescription(
|
|
|
+ nodeId,
|
|
|
+ BrowseDirection.Forward,
|
|
|
+ Identifiers.References,
|
|
|
+ true,
|
|
|
+ uint(NodeClass.Object.getValue() | NodeClass.Variable.getValue()),
|
|
|
+ uint(BrowseResultMask.All.getValue())
|
|
|
+ );
|
|
|
+
|
|
|
+ BrowseResult result = client.browse(browse).get();
|
|
|
+ ReferenceDescription[] refs = result.getReferences();
|
|
|
+ if (refs == null) return;
|
|
|
+
|
|
|
+ String pad = repeat(" ", depth);
|
|
|
+ for (ReferenceDescription ref : refs) {
|
|
|
+ QualifiedName name = ref.getBrowseName();
|
|
|
+ System.out.println(pad + "- " + name.getName()
|
|
|
+ + " [" + ref.getNodeClass() + "] " + ref.getNodeId().toParseableString());
|
|
|
+
|
|
|
+ ref.getNodeId().toNodeId(client.getNamespaceTable())
|
|
|
+ .ifPresent(child -> {
|
|
|
+ try {
|
|
|
+ browse(client, child, depth + 1, maxDepth);
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 忽略个别节点访问错误
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 订阅节点值变化(事件驱动,避免轮询)
|
|
|
+ */
|
|
|
+ private static void subscribe(OpcUaClient client, NodeId nodeId) throws Exception {
|
|
|
+ // 发布周期 1000ms
|
|
|
+ UaSubscription subscription = client.getSubscriptionManager().createSubscription(1000.0).get();
|
|
|
+
|
|
|
+ ReadValueId readValueId = new ReadValueId(
|
|
|
+ nodeId, AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE);
|
|
|
+
|
|
|
+ AtomicLong clientHandle = new AtomicLong(1L);
|
|
|
+ MonitoringParameters params = new MonitoringParameters(
|
|
|
+ uint(clientHandle.getAndIncrement()),
|
|
|
+ 500.0, // 采样间隔 ms
|
|
|
+ null,
|
|
|
+ uint(10), // 队列大小
|
|
|
+ true
|
|
|
+ );
|
|
|
+
|
|
|
+ MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(
|
|
|
+ readValueId, MonitoringMode.Reporting, params);
|
|
|
+
|
|
|
+ UaSubscription.ItemCreationCallback onItemCreated =
|
|
|
+ (item, id) -> item.setValueConsumer((it, value) ->
|
|
|
+ System.out.println("订阅推送:" + it.getReadValueId().getNodeId()
|
|
|
+ + " => " + value.getValue().getValue()
|
|
|
+ + " @" + value.getServerTime()));
|
|
|
+
|
|
|
+ List<UaMonitoredItem> items = subscription.createMonitoredItems(
|
|
|
+ TimestampsToReturn.Both, Collections.singletonList(request), onItemCreated).get();
|
|
|
+
|
|
|
+ for (UaMonitoredItem item : items) {
|
|
|
+ if (item.getStatusCode().isGood()) {
|
|
|
+ System.out.println("订阅成功:" + item.getReadValueId().getNodeId());
|
|
|
+ } else {
|
|
|
+ System.out.println("订阅失败:" + item.getReadValueId().getNodeId()
|
|
|
+ + " status=" + item.getStatusCode());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String repeat(String s, int n) {
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (int i = 0; i < n; i++) sb.append(s);
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+}
|