Jelajahi Sumber

点位测试

zhanghe 4 bulan lalu
induk
melakukan
775586231d

+ 194 - 0
src/main/java/com/jeesite/modules/OpcUa.java

@@ -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();
+    }
+}

+ 155 - 0
src/main/java/com/jeesite/modules/OpcUaTestSinumerik.java

@@ -0,0 +1,155 @@
+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.UsernameProvider;
+import org.eclipse.milo.opcua.stack.client.DiscoveryClient;
+import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
+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.enumerated.MessageSecurityMode;
+import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
+import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
+import org.eclipse.milo.opcua.stack.core.util.EndpointUtil;
+import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateBuilder;
+import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator;
+
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
+
+/**
+ * SINUMERIK OPC UA 采集点位测试(PLC: 10.0.20.141)
+ *
+ * 已包含的点位(OPC UA 标准 NCK 变量路径):
+ *   - 设定进给速度    /Channel/State/cmdFeedRateIpo
+ *   - 设定主轴转速    /Channel/Spindle/cmdSpeed
+ *   - 面板进给倍率    /Channel/MachineAxis/feedRateOvr
+ *   - 面板主轴倍率    /Channel/Spindle/speedOvr
+ *   - 实际主轴转速    /Channel/Spindle/actSpeed
+ *   - 实际进给速度    /Channel/State/actFeedRateIpo
+ *
+ * 未包含:
+ *   - 「进给量」未提供地址
+ *   - I6.2 / I3.5 是 PLC 输入位地址(S7 协议风格),不是 OPC UA NodeId,
+ *     如需通过 OPC UA 读取,请向设备方索取这两个信号在命名空间下的 OPC UA 路径。
+ */
+public class OpcUaTestSinumerik {
+
+    private static final String ENDPOINT_URL = "opc.tcp://10.0.20.141:4840";
+    private static final String USERNAME = "OpcUaclient";
+    private static final String PASSWORD = "ABCabc_123";
+
+    public static void main(String[] args) throws Exception {
+        // 待采集点位:保持顺序便于看结果
+        Map<String, String> points = new LinkedHashMap<>();
+        points.put("设定进给速度",   "ns=2;s=/Channel/State/cmdFeedRateIpo");
+        points.put("设定主轴转速",   "ns=2;s=/Channel/Spindle/cmdSpeed");
+        points.put("面板进给倍率",   "ns=2;s=/Channel/MachineAxis/feedRateOvr");
+        points.put("面板主轴倍率",   "ns=2;s=/Channel/Spindle/speedOvr");
+        points.put("实际主轴转速",   "ns=2;s=/Channel/Spindle/actSpeed");
+        points.put("实际进给速度",   "ns=2;s=/Channel/State/actFeedRateIpo");
+
+        OpcUaClient client = createClient(ENDPOINT_URL);
+        try {
+            client.connect().get();
+            System.out.println("已连接:" + ENDPOINT_URL);
+            System.out.println("--------------------------------------------------");
+
+            for (Map.Entry<String, String> e : points.entrySet()) {
+                String label = e.getKey();
+                String path = e.getValue();
+                try {
+                    NodeId nodeId = NodeId.parse(path);
+                    DataValue dv = client.readValue(0, TimestampsToReturn.Both, nodeId).get();
+                    Object val = dv.getValue() == null ? null : dv.getValue().getValue();
+                    System.out.printf("%-12s | %-50s | value=%s | status=%s%n",
+                            label, path, val, dv.getStatusCode());
+                } catch (Exception ex) {
+                    System.out.printf("%-12s | %-50s | ERROR: %s%n",
+                            label, path, ex.getMessage());
+                }
+            }
+        } finally {
+            try { client.disconnect().get(); } catch (Exception ignore) {}
+        }
+    }
+
+    /**
+     * 构建 OpcUaClient:自签名证书 + Basic256Sha256 加密 + 用户名密码登录
+     */
+    private static OpcUaClient createClient(String endpointUrl) throws Exception {
+        List<EndpointDescription> endpoints = DiscoveryClient.getEndpoints(endpointUrl).get();
+        if (endpoints.isEmpty()) {
+            throw new IllegalStateException("没有可用的 Endpoint:" + endpointUrl);
+        }
+
+        // 优先 Basic256Sha256 SignAndEncrypt
+        EndpointDescription endpoint = endpoints.stream()
+                .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.Basic256Sha256.getUri())
+                        && e.getSecurityMode() == MessageSecurityMode.SignAndEncrypt)
+                .findFirst()
+                .orElseGet(() -> endpoints.stream()
+                        .filter(e -> !e.getSecurityPolicyUri().endsWith("#None"))
+                        .findFirst()
+                        .orElse(endpoints.get(0)));
+
+        // 改写端点地址,避免服务器返回的是它自己的主机名
+        endpoint = EndpointUtil.updateUrl(endpoint, parseHost(endpointUrl), parsePort(endpointUrl));
+        System.out.println("选用端点: " + endpoint.getEndpointUrl()
+                + " | " + endpoint.getSecurityPolicyUri()
+                + " | " + endpoint.getSecurityMode());
+
+        // 生成自签名证书(加密端点必须)
+        String appUri = "urn:mes:opcua:test";
+        KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048);
+        X509Certificate cert = new SelfSignedCertificateBuilder(keyPair)
+                .setCommonName("MES OPC UA Test")
+                .setOrganization("MES")
+                .setOrganizationalUnit("dev")
+                .setLocalityName("CN")
+                .setStateName("CN")
+                .setCountryCode("CN")
+                .setApplicationUri(appUri)
+                .addDnsName("localhost")
+                .addIpAddress("127.0.0.1")
+                .build();
+
+        OpcUaClientConfig config = OpcUaClientConfig.builder()
+                .setApplicationName(LocalizedText.english("MES OPC UA Test"))
+                .setApplicationUri(appUri)
+                .setEndpoint(endpoint)
+                .setIdentityProvider(new UsernameProvider(USERNAME, PASSWORD))
+                .setRequestTimeout(uint(10000))
+                .setCertificate(cert)
+                .setKeyPair(keyPair)
+                .build();
+
+        return OpcUaClient.create(config);
+    }
+
+    private static String parseHost(String url) {
+        try {
+            String s = url.substring(url.indexOf("://") + 3);
+            String hp = s.contains("/") ? s.substring(0, s.indexOf("/")) : s;
+            return hp.contains(":") ? hp.substring(0, hp.indexOf(":")) : hp;
+        } catch (Exception e) {
+            return "127.0.0.1";
+        }
+    }
+
+    private static int parsePort(String url) {
+        try {
+            String s = url.substring(url.indexOf("://") + 3);
+            String hp = s.contains("/") ? s.substring(0, s.indexOf("/")) : s;
+            return hp.contains(":") ? Integer.parseInt(hp.substring(hp.indexOf(":") + 1)) : 4840;
+        } catch (Exception e) {
+            return 4840;
+        }
+    }
+}

+ 50 - 0
src/main/java/com/jeesite/modules/S7TestRl.java

@@ -0,0 +1,50 @@
+package com.jeesite.modules;
+
+import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
+import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
+
+/**
+ * 熔炼炉 S7 采集点位测试(PLC: 10.0.20.111)
+ *
+ * 数据类型对应:
+ *   Real  -> readFloat32  (4 字节)
+ *   int   -> readInt16    (2 字节)
+ *   Dint  -> readInt32    (4 字节)
+ *
+ * 地址格式:DBxx.字节偏移
+ */
+public class S7TestRl {
+
+    public static void main(String[] args) {
+        S7PLC s7PLC = new S7PLC(EPlcType.S1200, "10.0.20.103");
+        try {
+            // ===== 温度(Real, DB11) =====
+            System.out.println("一区温控PV: " + s7PLC.readFloat32("DB11.0")  + " °C");
+            System.out.println("一区温控SP: " + s7PLC.readFloat32("DB11.4")  + " °C");
+            System.out.println("二区温控PV: " + s7PLC.readFloat32("DB11.16") + " °C");
+            System.out.println("二区温控SP: " + s7PLC.readFloat32("DB11.20") + " °C");
+            System.out.println("一区监控PV: " + s7PLC.readFloat32("DB11.32") + " °C");
+            System.out.println("一区监控SP: " + s7PLC.readFloat32("DB11.36") + " °C");
+            System.out.println("二区监控PV: " + s7PLC.readFloat32("DB11.40") + " °C");
+            System.out.println("二区监控SP: " + s7PLC.readFloat32("DB11.44") + " °C");
+
+            // ===== 主驱动(int, DB4) =====
+            System.out.println("主驱动运行频率: " + (s7PLC.readInt16("DB4.316") / 10.0) + "Hz");
+            System.out.println("主驱动设定频率: " + (s7PLC.readInt16("DB4.318") / 10.0) + "Hz");
+
+            // ===== 产量(Dint, DB6) =====
+            System.out.println("入炉数: " + s7PLC.readInt32("DB6.12"));
+
+            // ===== 风机转速(Dint, DB4) =====
+            System.out.println("风机转速-一区: " + s7PLC.readInt32("DB4.272"));
+            System.out.println("风机转速-二区: " + s7PLC.readInt32("DB4.276"));
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                s7PLC.close();
+            } catch (Exception ignore) {
+            }
+        }
+    }
+}

+ 60 - 0
src/main/java/com/jeesite/modules/S7TestSaw.java

@@ -0,0 +1,60 @@
+package com.jeesite.modules;
+
+import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
+import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
+
+/**
+ * 半自动锯切站 S7 采集点位测试(PLC: 10.0.20.150,DB28)
+ *
+ * 数据类型对应:
+ *   UINT -> readUInt16  (2 字节)
+ *   BOOL -> readBoolean (按位)
+ *
+ * 地址格式:
+ *   字节型:DB号.字节偏移              如 DB28.0
+ *   位型  :DB号.字节偏移.位偏移        如 DB28.8.0
+ */
+public class S7TestSaw {
+
+    public static void main(String[] args) {
+        S7PLC s7PLC = new S7PLC(EPlcType.S1200, "10.0.20.150");
+        try {
+            // ===== 节拍 / 转速(UINT, DB28) =====
+            System.out.println("一序节拍(秒):     " + s7PLC.readUInt16("DB28.0"));
+            System.out.println("二序节拍(秒):     " + s7PLC.readUInt16("DB28.2"));
+            System.out.println("锯切转速(r/min):  " + s7PLC.readUInt16("DB28.4"));
+            System.out.println("铣削转速(r/min):  " + s7PLC.readUInt16("DB28.6"));
+
+            // ===== 报警信号(BOOL, DB28.8.x / 9.x / 10.x) =====
+            System.out.println("伺服报警:                  " + s7PLC.readBoolean("DB28.8.0"));
+            System.out.println("变频器异常(A1/C1):         " + s7PLC.readBoolean("DB28.8.1"));
+            System.out.println("变频器过负载OL:            " + s7PLC.readBoolean("DB28.8.2"));
+            System.out.println("油雾器液位低:              " + s7PLC.readBoolean("DB28.8.3"));
+            System.out.println("排屑机跳匝:                " + s7PLC.readBoolean("DB28.8.4"));
+            System.out.println("液压站跳匝:                " + s7PLC.readBoolean("DB28.8.5"));
+            System.out.println("水冷机停止中:              " + s7PLC.readBoolean("DB28.8.6"));
+            System.out.println("人工位安全相机检测报警:    " + s7PLC.readBoolean("DB28.8.7"));
+
+            System.out.println("锯切站内安全相机检测报警:  " + s7PLC.readBoolean("DB28.9.0"));
+            System.out.println("转台定位气缸1缩回检测故障: " + s7PLC.readBoolean("DB28.9.1"));
+            System.out.println("转台定位气缸1伸出检测故障: " + s7PLC.readBoolean("DB28.9.2"));
+            System.out.println("转台定位气缸2缩回检测故障: " + s7PLC.readBoolean("DB28.9.3"));
+            System.out.println("转台定位气缸2伸出检测故障: " + s7PLC.readBoolean("DB28.9.4"));
+            System.out.println("转台定位气缸3缩回检测故障: " + s7PLC.readBoolean("DB28.9.5"));
+            System.out.println("转台定位气缸3伸出检测故障: " + s7PLC.readBoolean("DB28.9.6"));
+            System.out.println("转台定位气缸4缩回检测故障: " + s7PLC.readBoolean("DB28.9.7"));
+
+            System.out.println("转台定位气缸4伸出检测故障: " + s7PLC.readBoolean("DB28.10.0"));
+            System.out.println("急停(电柜上):              " + s7PLC.readBoolean("DB28.10.1"));
+            System.out.println("人工位急停:                " + s7PLC.readBoolean("DB28.10.2"));
+            System.out.println("主轴转速未达到报警:        " + s7PLC.readBoolean("DB28.10.3"));
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                s7PLC.close();
+            } catch (Exception ignore) {
+            }
+        }
+    }
+}