天天看點

在Java 中 利用Milo通信庫,實作OPCUA用戶端,并生成證書

程式結構:

在Java 中 利用Milo通信庫,實作OPCUA用戶端,并生成證書

配置檔案resources:

在Java 中 利用Milo通信庫,實作OPCUA用戶端,并生成證書

opcua.properties

西門子PLC端口号為4840,kepserver為49320

#opcua服務端配置參數
#opcua.server.endpoint.url=opc.tcp://192.168.2.102:49320
opcua.server.endpoint.url=opc.tcp://192.168.2.11:4840
opcua.server.idp.username=Administrator
opcua.server.idp.password=123456

#opcua用戶端配置參數
opcua.client.app.name=zgOpcUaClient
opcua.client.app.uri=urn:Shanghai:Kx:KxAutomation:Zg
opcua.client.cert.path=D:/zhengshu/
opcua.client.cert.file=zg-client.pfx
opcua.client.cert.alias=zgclient-ai
opcua.client.cert.common.name=KxZg
opcua.client.cert.organization=Kaixin
opcua.client.cert.organization.unit=Kx
opcua.client.cert.locality.name=Terran
opcua.client.cert.state.name=Shanghai
opcua.client.cert.country.code=CN
opcua.client.cert.dns.name=Zg
opcua.client.cert.ip.address=127.0.0.1
opcua.client.cert.keystore.password=123456
           

它對應的實體類調用:

package com.zg.mymes.myConnPLC.myOPCUA.config;

import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

/**
 * @Auther: Zg
 * @Date: 2022/11/23 - 11 - 23 - 15:07
 * @Description: com.zg.mymes.myConnPLC.myOPCUA.config
 * @version: 1.0
 */
@Getter
@Configuration
@PropertySource("classpath:opcua.properties")
public class MyOPCUAProperties {
    @Value("${opcua.server.endpoint.url}")
    private String endpointUrl;
    @Value("${opcua.server.idp.username}")
    private String idpUsername;
    @Value("${opcua.server.idp.password}")
    private String idpPassword;
    @Value("${opcua.client.app.name}")
    private String appName;
    @Value("${opcua.client.app.uri}")
    private String appUri;
    @Value("${opcua.client.cert.path}")
    private String certPath;
    @Value("${opcua.client.cert.file}")
    private String certFile;
    @Value("${opcua.client.cert.alias}")
    private String certAlias;
    @Value("${opcua.client.cert.common.name}")
    private String commonName;
    @Value("${opcua.client.cert.organization}")
    private String organization;
    @Value("${opcua.client.cert.organization.unit}")
    private String orgUnit;
    @Value("${opcua.client.cert.locality.name}")
    private String localityName;
    @Value("${opcua.client.cert.state.name}")
    private String stateName;
    @Value("${opcua.client.cert.country.code}")
    private String countryCode;
    @Value("${opcua.client.cert.dns.name}")
    private String dnsName;
    @Value("${opcua.client.cert.ip.address}")
    private String ipAddress;
    @Value("${opcua.client.cert.keystore.password}")
    private String keyPassword;
}

           

opcnode.properties:

opcnode.index,西門子PLC為3,kepserver為2

opcnode.index=3
opcnode.var.var0="S7MesData"."S7Real"[0]
opcnode.var.var1="S7MesData"."S7Real"[1]
opcnode.var.var2="S7MesData"."S7Real"[2]
opcnode.var.var3="S7MesData"."S7Real"[3]
opcnode.var.var4="S7MesData"."S7Real"[4]
opcnode.var.var5="S7MesData"."S7Real"[5]
opcnode.var.var6="S7MesData"."S7Real"[100]
opcnode.var.var7="S7MesData"."S7String"[0]
opcnode.var.var8="S7MesData"."S7Int"[0]

           

它對應的實體類調用:

package com.zg.mymes.myConnPLC.myOPCUA.config;

import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

/**
 * @Auther: Zg
 * @Date: 2022/11/23 - 11 - 23 - 15:27
 * @Description: com.zg.mymes.myConnPLC.myOPCUA.config
 * @version: 1.0
 *
 * 相當于點表
 * */
@Getter
@Configuration
@PropertySource("classpath:opcnode.properties")
public class MyOPCNode {
    @Value("${opcnode.index}")
    private String index;

    @Value("${opcnode.var.var0}")
    private String var0;
    @Value("${opcnode.var.var1}")
    private String var1;
    @Value("${opcnode.var.var2}")
    private String var2;
    @Value("${opcnode.var.var3}")
    private String var3;
    @Value("${opcnode.var.var4}")
    private String var4;
    @Value("${opcnode.var.var5}")
    private String var5;
    @Value("${opcnode.var.var6}")
    private String var6;
    @Value("${opcnode.var.var7}")
    private String var7;
    @Value("${opcnode.var.var8}")
    private String var8;
}

           

生成證書類:KeyStoreLoader

package com.zg.mymes.myConnPLC.myOPCUA.cert;

import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateBuilder;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.regex.Pattern;

/**
 * @Auther: Zg
 * @Date: 2022/11/23 - 11 - 23 - 15:01
 * @Description: com.zg.mymes.myConnPLC.myOPCUA.cert
 * @version: 1.0
 */
@Component
public class KeyStoreLoader {

    private static final Pattern IP_ADDR_PATTERN = Pattern
            .compile("^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");

    // 證書别名
    private static final String CLIENT_ALIAS = "jlclient-ai";
    // 擷取私鑰的密碼
    private static final char[] PASSWORD = "123456".toCharArray();

    private final Logger logger = LoggerFactory.getLogger(getClass());

    // 證書對象
    private X509Certificate clientCertificate;
    // 密鑰對對象
    private KeyPair clientKeyPair;

    /**
     * @MethodName: load
     * @Description: load
     * @param baseDir
     * @return
     * @throws Exception
     * @CreateTime 2019年12月11日 下午4:02:43
     */
    public KeyStoreLoader load(Path baseDir) throws Exception {
        // 建立一個使用`PKCS12`加密标準的KeyStore。KeyStore在後面将作為讀取和生成證書的對象。
        KeyStore keyStore = KeyStore.getInstance("PKCS12");

        // PKCS12的加密标準的檔案字尾是.pfx,其中包含了公鑰和私鑰。
        // 而其他如.der等的格式隻包含公鑰,私鑰在另外的檔案中。
        Path serverKeyStore = baseDir.resolve("zg-client.pfx");

        logger.info("Loading KeyStore at {}", serverKeyStore);

        // 如果檔案不存在則建立.pfx證書檔案。
        if (!Files.exists(serverKeyStore)) {
            keyStore.load(null, PASSWORD);

            // 用2048位的RAS算法。`SelfSignedCertificateGenerator`為Milo庫的對象。
            KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048);

            // `SelfSignedCertificateBuilder`也是Milo庫的對象,用來生成證書。
            // 中間所設定的證書屬性可以自行修改。
            SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder(keyPair)
                    .setCommonName("KxZg")
                    .setOrganization("Kaixin")
                    .setOrganizationalUnit("Kx")
                    .setLocalityName("Terran")
                    .setStateName("Shanghai")
                    .setCountryCode("CN")
                    .setApplicationUri("urn:Shanghai:Kx:KxAutomation:Zg")
                    .addDnsName("Zg")
                    .addIpAddress("127.0.0.1");

            // Get as many hostnames and IP addresses as we can listed in the certificate.
            for (String hostname : HostnameUtil.getHostnames("0.0.0.0")) {
                if (IP_ADDR_PATTERN.matcher(hostname).matches()) {
                    builder.addIpAddress(hostname);
                } else {
                    builder.addDnsName(hostname);
                }
            }
            // 建立證書
            X509Certificate certificate = builder.build();

            // 設定對應私鑰的别名,密碼,證書鍊
            keyStore.setKeyEntry(CLIENT_ALIAS, keyPair.getPrivate(), PASSWORD, new X509Certificate[] { certificate });
            try (OutputStream out = Files.newOutputStream(serverKeyStore)) {
                // 儲存證書到輸出流
                keyStore.store(out, PASSWORD);
            }
        } else {
            try (InputStream in = Files.newInputStream(serverKeyStore)) {
                // 如果檔案存在則讀取
                keyStore.load(in, PASSWORD);
            }
        }

        // 用密碼擷取對應别名的私鑰。
        Key serverPrivateKey = keyStore.getKey(CLIENT_ALIAS, PASSWORD);
        if (serverPrivateKey instanceof PrivateKey) {
            // 擷取對應别名的證書對象。
            clientCertificate = (X509Certificate) keyStore.getCertificate(CLIENT_ALIAS);
            // 擷取公鑰
            PublicKey serverPublicKey = clientCertificate.getPublicKey();
            // 建立Keypair對象。
            clientKeyPair = new KeyPair(serverPublicKey, (PrivateKey) serverPrivateKey);
        }

        return this;
    }

    // 傳回證書
    public X509Certificate getClientCertificate() {
        return clientCertificate;
    }

    // 傳回密鑰對
    public KeyPair getClientKeyPair() {
        return clientKeyPair;
    }
}

           

OPCUA訂閱,寫入,讀取等工具類:

ClientRunner:

package com.zg.mymes.myConnPLC.myOPCUA.client;

import com.zg.mymes.myConnPLC.myOPCUA.cert.KeyStoreLoader;
import com.zg.mymes.myConnPLC.myOPCUA.config.MyOPCUAProperties;
import lombok.extern.slf4j.Slf4j;
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.Stack;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Predicate;

/**
 * @Auther: Zg
 * @Date: 2022/11/23 - 11 - 23 - 15:04
 * @Description: com.zg.mymes.myConnPLC.myOPCUA.client
 * @version: 1.0
 */
@Slf4j
@Component
public class ClientRunner {

    private final CompletableFuture<OpcUaClient> future = new CompletableFuture<>();

    @Autowired
    private MyOPCUAProperties properties;

    @Autowired
    private KeyStoreLoader keyStoreLoader;

    /**
     * @MethodName: run
     * @Description: 啟動
     * @return
     * @throws Exception
     * @CreateTime 2019年12月18日 下午4:03:47
     */
    public OpcUaClient run() throws Exception {

        OpcUaClient client = createClient();

        future.whenCompleteAsync((c, ex) -> {
            if (ex != null) {
                log.error("Error running example: {}", ex.getMessage(), ex);
            }

            try {
                c.disconnect().get();
                Stack.releaseSharedResources();
            } catch (InterruptedException | ExecutionException e) {
                log.error("Error disconnecting:", e.getMessage(), e);
            }
        });

        return client;
    }

    /**
     * @MethodName: createClient
     * @Description: 建立用戶端
     * @return
     * @throws Exception
     * @CreateTime 2019年12月18日 下午4:02:54
     */
    private OpcUaClient createClient() throws Exception {

        Path securityTempDir = Paths.get(properties.getCertPath(), "security");

        Files.createDirectories(securityTempDir);
        if (!Files.exists(securityTempDir)) {
            log.error("unable to create security dir: " + securityTempDir);
            return null;
        }

        KeyStoreLoader loader = keyStoreLoader.load(securityTempDir);

        // 搜尋OPC節點
        List<EndpointDescription> endpoints = null;
        try {
            endpoints = DiscoveryClient.getEndpoints(properties.getEndpointUrl()).get();
        } catch (Throwable e) {
            // try the explicit discovery endpoint as well
            String discoveryUrl = properties.getEndpointUrl();

            if (!discoveryUrl.endsWith("/")) {
                discoveryUrl += "/";
            }
            discoveryUrl += "discovery";

            log.info("Trying explicit discovery URL: {}", discoveryUrl);
            endpoints = DiscoveryClient.getEndpoints(discoveryUrl).get();
        }

        //OPC伺服器位址
        EndpointDescription endpoint = endpoints.stream()
                .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri())).filter(endpointFilter())
                .findFirst().orElseThrow(() -> new Exception("no desired endpoints returned"));

        //setKeyPair()接受一個KeyPair對象表示密匙對。
        //setEndpoint()接受一個EndpointDescription對象,就是設定剛剛我們選擇的節點就可以了。
        //setIdentityProvider()該方法表示指定用戶端使用的通路驗證方式
        OpcUaClientConfig config = OpcUaClientConfig.builder()
                .setApplicationName(LocalizedText.english("zgOpcUaClient"))
                .setApplicationUri("urn:Shanghai:Kx:KxAutomation:Zg")
                .setCertificate(loader.getClientCertificate())
                .setKeyPair(loader.getClientKeyPair())
                .setEndpoint(endpoint)
                .setIdentityProvider(new UsernameProvider("Administrator", "123456"))
//				.setIdentityProvider(new AnonymousProvider()) // 匿名驗證
                .setRequestTimeout(Unsigned.uint(5000)).build();

        return OpcUaClient.create(config);
    }

    /**
     * @MethodName: endpointFilter
     * @Description: endpointFilter
     * @return
     * @CreateTime 2019年12月18日 下午4:06:22
     */
    private Predicate<EndpointDescription> endpointFilter() {
        return e -> true;
    }

    /**
     * @return the future
     */
    public CompletableFuture<OpcUaClient> getFuture() {
        return future;
    }

}
           

ClientHandler:

package com.zg.mymes.myConnPLC.myOPCUA.client;

import com.google.common.collect.ImmutableList;
import com.zg.mymes.myConnPLC.myOPCUA.opcEntities.NodeEntity;
import com.zg.mymes.myConnPLC.myOPCUA.opcEntities.SubscriptNode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.nodes.VariableNode;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.BuiltinDataType;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UByte;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * @Auther: Zg
 * @Date: 2022/11/23 - 11 - 23 - 15:10
 * @Description: com.zg.mymes.myConnPLC.myOPCUA.client
 * @version: 1.0
 */
@Slf4j
@Service
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ClientHandler {

    // 用戶端執行個體
    private OpcUaClient client = null;

    @Autowired
    private ClientRunner clientRunner;

    private SubscriptNode myNode = new SubscriptNode();

    private List<NodeEntity> nodeEntities = new ArrayList<NodeEntity>();

    /**
     *
     * @MethodName: connect
     * @Description: connect
     * @throws Exception
     * @CreateTime 2019年12月18日 上午10:41:09
     */
    public String connect() throws Exception {

        if (client != null) {
            return "用戶端已建立";
        }

        client = clientRunner.run();

        if (client == null) {
            return "用戶端配置執行個體化失敗";
        }

        // 建立連接配接
        client.connect().get();
        return "建立連接配接成功";
    }

    /**
     * @MethodName: disconnect
     * @Description: 斷開連接配接
     * @return
     * @throws Exception
     * @CreateTime 2019年12月18日 上午10:45:21
     */
    public String disconnect() throws Exception {

        if (client == null) {
            return "連接配接已斷開";
        }

        // 斷開連接配接
        clientRunner.getFuture().complete(client);
        client = null;
        return "斷開連接配接成功";
    }

    /**
     * @MethodName: subscribe
     * @Description: 訂閱節點變量
     * @throws Exception
     * @CreateTime 2019年12月18日 上午10:38:11
     */
    public String subscribe(List<NodeEntity> nodes) throws Exception {
        this.nodeEntities = nodes;
        if (client == null) {
            return "找不到用戶端,操作失敗";
        }

//		List<Node> ns = client.getAddressSpace().browse(new NodeId(2, "模拟通道一.模拟裝置一")).get();

        // 查詢訂閱對象,沒有則建立
        UaSubscription subscription = null;
        ImmutableList<UaSubscription> subscriptionList = client.getSubscriptionManager().getSubscriptions();
        if (CollectionUtils.isEmpty(subscriptionList)) {
            subscription = client.getSubscriptionManager().createSubscription(1000.0).get();
        } else {
            subscription = subscriptionList.get(0);
        }

        // 監控項請求清單
        List<MonitoredItemCreateRequest> requests = new ArrayList<>();

        if (!CollectionUtils.isEmpty(nodes)) {
            for (NodeEntity node : nodes) {
                // 建立監控的參數
                MonitoringParameters parameters = new MonitoringParameters(subscription.nextClientHandle(), 1000.0, // sampling
                        // interval
                        null, // filter, null means use default
                        Unsigned.uint(10), // queue size
                        true // discard oldest
                );
                // 建立訂閱的變量, 建立監控項請 求
                MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(
                        new ReadValueId(new NodeId(node.getIndex(), node.getIdentifier()), AttributeId.Value.uid(),
                                null, null),
                        MonitoringMode.Reporting, parameters);
                requests.add(request);
            }
        }

        // 建立監控項,并且注冊變量值改變時候的回調函數
        subscription.createMonitoredItems(TimestampsToReturn.Both, requests, (item, id) -> {
            item.setValueConsumer((i, v) -> {
                log.info("========+++==========");
                log.info("item={}, value={}", i.getReadValueId(), v.getValue().getValue());
                log.info("========+++==========");
//                NodeId nodeId = i.getReadValueId().getNodeId();
//                Variant value = v.getValue();

                //将OPC讀取的資料存入nodeEntities
                for (NodeEntity nodeEntity:this.nodeEntities
                     ) {
                    if (i.getReadValueId().getNodeId().getIdentifier().equals(nodeEntity.getIdentifier())){
                        nodeEntity.setValue(v.getValue().getValue());
                    }
                }
            });
        }).get();

        return "訂閱成功";
    }

    /**
     * @MethodName: write
     * @Description: 變節點量寫入
     * @param node
     * @throws Exception
     * @CreateTime 2019年12月18日 上午9:51:40
     */
    public String write(NodeEntity node) throws Exception {

        if (client == null) {
            return "找不到用戶端,操作失敗";
        }

        NodeId nodeId = new NodeId(node.getIndex(), node.getIdentifier());
        Variant value = null;
        switch (node.getType()) {
            case "int":
                value = new Variant(Integer.parseInt(node.getValue().toString()));
                break;
            case "boolean":
                value = new Variant(Boolean.parseBoolean(node.getValue().toString()));
                break;
            case "Short":
                value = new Variant(Short.parseShort(node.getValue().toString()));
                break;
            case "String":
                value = new Variant(node.getValue().toString());
                break;
            case "Float":
                value = new Variant(Float.parseFloat(node.getValue().toString()));
                break;
            case "UByte":
                value = new Variant(UByte.valueOf(node.getValue().toString()));
                break;
        }
        DataValue dataValue = new DataValue(value, null, null);

        StatusCode statusCode = client.writeValue(nodeId, dataValue).get();

        return "節點【" + node.getIdentifier() + "】寫入狀态:" + statusCode.isGood();
    }

    /**
     * @MethodName: read
     * @Description: 讀取
     * @param node
     * @return
     * @throws Exception
     * @CreateTime 2019年12月19日 下午2:40:34
     */
    public String read(NodeEntity node) throws Exception {

        if (client == null) {
            return "找不到用戶端,操作失敗";
        }

        NodeId nodeId = new NodeId(node.getIndex(), node.getIdentifier());
        VariableNode vnode = client.getAddressSpace().createVariableNode(nodeId);
        DataValue value = vnode.readValue().get();
        log.info("Value={}", value);

        Variant variant = value.getValue();
        log.info("Variant={}", variant.getValue());

        log.info("BackingClass={}", BuiltinDataType.getBackingClass(variant.getDataType().get()));

        return "節點【" + node.getIdentifier() + "】:" + variant.getValue();
    }
}

           

生成證書,讀,寫,驗證等操作:

package com.zg.mymes.controller;

import com.zg.mymes.myConnPLC.myOPCUA.opcEntities.NodeEntity;
import com.zg.mymes.myConnPLC.myOPCUA.cert.KeyStoreLoader;
import com.zg.mymes.myConnPLC.myOPCUA.client.ClientHandler;
import com.zg.mymes.myConnPLC.myOPCUA.config.MyOPCNode;
import com.zg.mymes.myConnPLC.myOPCUA.config.MyOPCUAProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Auther: Zg
 * @Date: 2022/11/23 - 11 - 23 - 15:32
 * @Description: com.zg.mymes.controller
 * @version: 1.0
 */
@Controller
@Slf4j
public class OPCUAController {
    @Autowired
    private ClientHandler clientHandler;

    @Autowired
    public KeyStoreLoader keyStoreLoader;

    @Autowired
    private MyOPCUAProperties properties;

    @Autowired
    private MyOPCNode myOPCNode;

    /**
     *connect
     * @return
     */
    @RequestMapping("/connect")
    @ResponseBody
    public String connect() {

        try {
            return clientHandler.connect();
        } catch (Exception e) {
            e.printStackTrace();
            return "fail";
        }
    }


    @RequestMapping("/disconnect")
    @ResponseBody
    public String disconnect() {

        try {
            return clientHandler.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
            return "fail";
        }
    }

    @RequestMapping("/subscribe")
    @ResponseBody
    public String subscribe(HttpServletRequest request) {

        try {
            List<NodeEntity> nodes = Stream.of(request.getParameter("id").split(","))
                    .map(id -> NodeEntity.builder().index(2).identifier(id).build()).collect(Collectors.toList());

            return clientHandler.subscribe(nodes);
        } catch (Exception e) {
            e.printStackTrace();
            return "fail";
        }
    }



    @RequestMapping("/write")
    @ResponseBody
    public String write(HttpServletRequest request) {

        NodeEntity node = NodeEntity.builder().index(2).identifier(request.getParameter("id"))
                .value(request.getParameter("value")).type(request.getParameter("type")).build();

        try {
            return clientHandler.write(node);
        } catch (Exception e) {
            e.printStackTrace();
            return "fail";
        }
    }

    @RequestMapping("/read")
    @ResponseBody
    public String read(HttpServletRequest request) {

        NodeEntity node = NodeEntity.builder().index(2).identifier(request.getParameter("id")).build();

        try {
            return clientHandler.read(node);
        } catch (Exception e) {
            e.printStackTrace();
            return "fail";
        }
    }

    //建立證書
    @RequestMapping("/createCert")
    @ResponseBody
    public String Created() throws Exception {
        Path securityTempDir = Paths.get(properties.getCertPath(), "security");
        keyStoreLoader.load(securityTempDir);
        log.info("==========+++++");
        log.info(securityTempDir.toString());
        return securityTempDir.toString();
    }
}

           

開啟定時器,訂閱模式讀取變量:

package com.zg.mymes.helper;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import com.zg.mymes.entities.ActualData;
import com.zg.mymes.myConnPLC.myOPCUA.opcEntities.NodeEntity;
import com.zg.mymes.myConnPLC.myOPCUA.cert.KeyStoreLoader;
import com.zg.mymes.myConnPLC.myOPCUA.client.ClientHandler;
import com.zg.mymes.myConnPLC.myOPCUA.config.MyOPCNode;
import com.zg.mymes.myConnPLC.myS7.S7ConnHelper;
import com.zg.mymes.myConnPLC.myS7.myS7entities.MyS7Entity;
import com.zg.mymes.service.ActualDataService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.Timestamp;
import java.util.*;
import java.util.function.Consumer;

/**
 * @Auther: Zg
 * @Date: 2022/11/23 - 11 - 23 - 16:56
 * @Description: com.zg.mymes.helper
 * @version: 1.0
 */
@Service
@Slf4j
public class Listener {
    @Autowired
    private ClientHandler clientHandler;

    @Autowired
    public KeyStoreLoader keyStoreLoader;

    @Autowired
    private MyOPCNode myOPCNode;

    @Autowired
    public S7ConnHelper s7ConnHelper;

    @Autowired
    public ActualDataService actualDataService;


    Integer errorTimes = 0;
    Boolean last = false;
    Boolean trigIn = false;
    Boolean trigQ = false;

    //OPC
    @PostConstruct
    public void OpcUAConn() throws Exception {
        String connect = clientHandler.connect();
        log.info(connect);
        log.info("==========");


        ArrayList<NodeEntity> nodeEntities = new ArrayList<>();
        NodeEntity build0 = NodeEntity.builder()
                .identifier(myOPCNode.getVar0())
                .index(Integer.parseInt(myOPCNode.getIndex())).build();
        NodeEntity build1 = NodeEntity.builder()
                .identifier(myOPCNode.getVar1())
                .index(Integer.parseInt(myOPCNode.getIndex())).build();

        NodeEntity build2 = NodeEntity.builder()
                .identifier(myOPCNode.getVar2())
                .index(Integer.parseInt(myOPCNode.getIndex())).build();

        NodeEntity build3 = NodeEntity.builder()
                .identifier(myOPCNode.getVar3())
                .index(Integer.parseInt(myOPCNode.getIndex())).build();

        NodeEntity build4 = NodeEntity.builder()
                .identifier(myOPCNode.getVar4())
                .index(Integer.parseInt(myOPCNode.getIndex())).build();

        NodeEntity build5 = NodeEntity.builder()
                .identifier(myOPCNode.getVar5())
                .index(Integer.parseInt(myOPCNode.getIndex())).build();

        NodeEntity build6 = NodeEntity.builder()
                .identifier(myOPCNode.getVar6())
                .index(Integer.parseInt(myOPCNode.getIndex())).build();

        NodeEntity build7 = NodeEntity.builder()
                .identifier(myOPCNode.getVar7())
                .index(Integer.parseInt(myOPCNode.getIndex())).build();

        NodeEntity build8 = NodeEntity.builder()
                .identifier(myOPCNode.getVar8())
                .index(Integer.parseInt(myOPCNode.getIndex())).build();

        nodeEntities.add(build0);
        nodeEntities.add(build1);
        nodeEntities.add(build2);
        nodeEntities.add(build3);
        nodeEntities.add(build4);
        nodeEntities.add(build5);
        nodeEntities.add(build6);
        nodeEntities.add(build7);
        nodeEntities.add(build8);
        String subscribe = clientHandler.subscribe(nodeEntities);
        log.info(subscribe);

        //寫入,注意類型
//        NodeEntity build = NodeEntity.builder()
//                .identifier(myOPCNode.getVar8())
//                .index(Integer.parseInt(myOPCNode.getIndex()))
//                .type("Short")
//                .value("19")
//                .build(); //整數


//        NodeEntity buildFloat = NodeEntity.builder()
//                .identifier(myOPCNode.getVar6())
//                .index(Integer.parseInt(myOPCNode.getIndex()))
//                .type("Float")
//                .value("55.5")
//                .build(); //浮點數

//        NodeEntity build = NodeEntity.builder()
//                .identifier(myOPCNode.getVar7())
//                .index(Integer.parseInt(myOPCNode.getIndex()))
//                .type("String")
//                .value("abcde")
//                .build(); //字元
//        clientHandler.write(build);
    }



    //OPC
    @Async
    @Scheduled(cron = "*/1 * * * * *")
    public void OpcUATestPlc() throws Exception {
        log.info("===========-------============");
        log.info(clientHandler.getNodeEntities().toString());
        log.info("===========-------============");




    }