使用java的milo框架访问OPCUA服务的方法
最近接了一個項目,需要訪問工業(yè)的實時數(shù)據(jù)庫,數(shù)據(jù)庫的對外開放接口是OPCUA協(xié)議的,經(jīng)過多方比對,發(fā)現(xiàn)github上milo的評星較高,嘗試了一下,還比較方便好用,現(xiàn)在把使用過程中的坑和大家介紹一下,網(wǎng)上比較全的資料不多,下面是整個過程全部的資料:
本次采用KEPServerEX5模擬服務(wù)端,使用milo開發(fā)的程序作為客戶端
一、搭建服務(wù)端,KEPServerEX5的安裝省略掉,下面是配置過程
 設(shè)置通道、設(shè)備、標(biāo)簽
 
 設(shè)置訪問的用戶名和密碼
 
 設(shè)置通過opc-ua訪問的節(jié)點
 
 二、使用milo的框架,開發(fā)客戶端訪問opcua服務(wù)
 1、在pom文件中追擊以下依賴
2、OPC UA協(xié)議對象接口
package com.jndj.platform.common.milo;import org.eclipse.milo.opcua.sdk.client.OpcUaClient; 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.stack.core.security.SecurityPolicy; import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;import java.util.concurrent.CompletableFuture; import java.util.function.Predicate;/*** @author yaohj* @date 2020/7/30* OPC UA協(xié)議對象接口*/ public interface OpcUAClientService {/*** OPC UA服務(wù)器地址和接口*/default String getEndpointUrl() {return "opc.tcp://127.0.0.1:49320";}/*** 過濾返回的server endpoint*/default Predicate<EndpointDescription> endpointFilter() {return e -> true;}/*** 連接服務(wù)器的安全策略* None、Basic128Rsa15、Basic256、Basic256Sha256、Aes128_Sha256_RsaOaep、Aes256_Sha256_RsaPss*/default SecurityPolicy getSecurityPolicy() {return SecurityPolicy.None;}/*** 提供身份驗證*/default IdentityProvider getIdentityProvider() {return new AnonymousProvider();}/*** 實際操作服務(wù)、由實現(xiàn)類重寫實現(xiàn)*/void run(OpcUaClient client, CompletableFuture<OpcUaClient> future) throws Exception; }3、OPC UA協(xié)議對象實體類
package com.jndj.platform.common.milo;import com.google.common.collect.ImmutableList; import org.eclipse.milo.opcua.sdk.client.OpcUaClient; import org.eclipse.milo.opcua.stack.core.types.builtin.*; import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn; import org.springframework.stereotype.Service;import java.util.List; import java.util.concurrent.CompletableFuture;@Service("OpcUAClientService") public class OpcUAClientServiceImpl implements OpcUAClientService {/*** 覆蓋接口的方法,建立和OPC UA的服務(wù)*/@Overridepublic void run(OpcUaClient client, CompletableFuture<OpcUaClient> future) throws Exception {// 同步建立連接client.connect().get();// 異步讀取數(shù)據(jù)readTagData(client).thenAccept(values -> {DataValue nodeId_Tag1 = values.get(0);DataValue nodeId_Tag2 = values.get(1);System.out.println("#########Tag1=" + nodeId_Tag1.getValue().getValue());System.out.println("#########Tag2=" + nodeId_Tag2.getValue().getValue());future.complete(client);});}/*** 讀取標(biāo)簽點的數(shù)據(jù)*/private CompletableFuture<List<DataValue>> readTagData(OpcUaClient client) {NodeId nodeId_Tag1 = new NodeId(2, "Channel1.Device1.Tag1");NodeId nodeId_Tag2 = new NodeId(2, "Channel1.Device1.Tag2");List<NodeId> nodeIds = ImmutableList.of(nodeId_Tag1, nodeId_Tag2);return client.readValues(0.0, TimestampsToReturn.Both, nodeIds);} }4、OPC UA協(xié)議運行對象
package com.jndj.platform.common.milo;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.UaTcpStackClient; import org.eclipse.milo.opcua.stack.core.Stack; import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service;import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit;import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;@Service("OpcUAClientRunner") public class OpcUAClientRunner {private final Logger logger = LoggerFactory.getLogger(getClass());private final CompletableFuture<OpcUaClient> future = new CompletableFuture<>();private final OpcUAClientService opcUAClientService;public OpcUAClientRunner(OpcUAClientService opcUAClientService) {this.opcUAClientService = opcUAClientService;}/*** OPC UA的運行入口程序*/public void run() {try {// 創(chuàng)建OPC UA客戶端OpcUaClient opcUaClient = createClient();// future執(zhí)行完畢后,異步判斷狀態(tài)future.whenCompleteAsync((c, ex) -> {if (ex != null) {logger.error("連接OPC UA服務(wù)錯誤: {}", ex.getMessage(), ex);}// 關(guān)閉OPC UA客戶端try {opcUaClient.disconnect().get();Stack.releaseSharedResources();} catch (InterruptedException | ExecutionException e) {logger.error("OPC UA服務(wù)關(guān)閉錯誤: {}", e.getMessage(), e);}});try {// 獲取OPC UA服務(wù)器的數(shù)據(jù)opcUAClientService.run(opcUaClient, future);future.get(5, TimeUnit.SECONDS);} catch (Throwable t) {logger.error("OPC UA客戶端運行錯誤: {}", t.getMessage(), t);future.completeExceptionally(t);}} catch (Throwable t) {logger.error("OPC UA客戶端創(chuàng)建錯誤: {}", t.getMessage(), t);future.completeExceptionally(t);}}/*** 創(chuàng)建OPC UA的服務(wù)連接對象*/private OpcUaClient createClient() throws Exception {Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");Files.createDirectories(securityTempDir);if (!Files.exists(securityTempDir)) {throw new Exception("不能夠創(chuàng)建安全路徑: " + securityTempDir);}KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir);// 獲取OPC UA的服務(wù)器端節(jié)點EndpointDescription[] endpoints =UaTcpStackClient.getEndpoints(opcUAClientService.getEndpointUrl()).get();EndpointDescription endpoint = Arrays.stream(endpoints).filter(e -> e.getEndpointUrl().equals(opcUAClientService.getEndpointUrl())).findFirst().orElseThrow(() -> new Exception("沒有節(jié)點返回"));// 設(shè)置OPC UA的配置信息OpcUaClientConfig config =OpcUaClientConfig.builder().setApplicationName(LocalizedText.english("OPC UA SCREEN")).setApplicationUri("urn:DATA-TRANSFER:OPC UA SCREEN").setCertificate(loader.getClientCertificate()).setKeyPair(loader.getClientKeyPair()).setEndpoint(endpoint).setIdentityProvider(new UsernameProvider("Administrator", "123456")).setRequestTimeout(uint(5000)).build();// 創(chuàng)建OPC UA客戶端return new OpcUaClient(config);} }5、OPC UA訪問證書類
package com.jndj.platform.common.milo;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 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;class KeyStoreLoader {private final Logger logger = LoggerFactory.getLogger(getClass());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 = "client-ai";// 獲取私鑰的密碼private static final char[] PASSWORD = "password".toCharArray();// 證書對象private X509Certificate clientCertificate;// 密鑰對對象private KeyPair clientKeyPair;KeyStoreLoader load(Path baseDir) throws Exception {// 創(chuàng)建一個使用`PKCS12`加密標(biāo)準(zhǔn)的KeyStore。KeyStore在后面將作為讀取和生成證書的對象。KeyStore keyStore = KeyStore.getInstance("PKCS12");// PKCS12的加密標(biāo)準(zhǔn)的文件后綴是.pfx,其中包含了公鑰和私鑰。// 而其他如.der等的格式只包含公鑰,私鑰在另外的文件中。Path serverKeyStore = baseDir.resolve("example-client.pfx");logger.info("Loading KeyStore at {}", serverKeyStore);// 如果文件不存在則創(chuàng)建.pfx證書文件。if (!Files.exists(serverKeyStore)) {keyStore.load(null, PASSWORD);// 用2048位的RAS算法。`SelfSignedCertificateGenerator`為Milo庫的對象。KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048);// `SelfSignedCertificateBuilder`也是Milo庫的對象,用來生成證書。// 中間所設(shè)置的證書屬性可以自行修改。SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder(keyPair).setCommonName("Eclipse Milo Example Client").setOrganization("digitalpetri").setOrganizationalUnit("dev").setLocalityName("Folsom").setStateName("CA").setCountryCode("US").setApplicationUri("urn:eclipse:milo:examples:client").addDnsName("localhost").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);}}// 創(chuàng)建證書X509Certificate certificate = builder.build();// 設(shè)置對應(yīng)私鑰的別名,密碼,證書鏈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);}}// 用密碼獲取對應(yīng)別名的私鑰。Key serverPrivateKey = keyStore.getKey(CLIENT_ALIAS, PASSWORD);if (serverPrivateKey instanceof PrivateKey) {// 獲取對應(yīng)別名的證書對象。clientCertificate = (X509Certificate) keyStore.getCertificate(CLIENT_ALIAS);// 獲取公鑰PublicKey serverPublicKey = clientCertificate.getPublicKey();// 創(chuàng)建Keypair對象。clientKeyPair = new KeyPair(serverPublicKey, (PrivateKey) serverPrivateKey);}return this;}// 返回證書X509Certificate getClientCertificate() {return clientCertificate;}// 返回密鑰對KeyPair getClientKeyPair() {return clientKeyPair;} }6、業(yè)務(wù)service類
package com.jndj.platform.phase2.service.impl;import com.jndj.platform.common.milo.OpcUAClientRunner; import com.jndj.platform.common.milo.OpcUAClientService; import com.jndj.platform.phase2.service.Phase2Service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;/*** @author yaohj* @date 2020/7/22* 獲取二期發(fā)電數(shù)據(jù)(三、四號機(jī)組)*/ @Service("phase2Service") public class Phase2ServiceImpl implements Phase2Service {@Autowiredprivate OpcUAClientService opcUAClientService;/*** 獲取二期發(fā)電數(shù)據(jù)(三、四號機(jī)組),保存到數(shù)據(jù)庫中*/@Overridepublic void searchPhase2ElectricData() {new OpcUAClientRunner(opcUAClientService).run();} }7、業(yè)務(wù)Controller類、定時調(diào)度
package com.jndj.platform.phase2.controller;import com.jndj.platform.phase2.service.Phase2Service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Controller;import java.text.SimpleDateFormat; import java.util.Date;/*** @author yaohj* @date 2020/7/22* 獲取二期發(fā)電數(shù)據(jù)(三、四號機(jī)組)*/ @Controller public class Phase2Controller {private static final Logger logger = LoggerFactory.getLogger(Phase2Controller.class);private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy:mm:dd HH:mm:ss");@Autowiredprivate Phase2Service phase2Service;/*** 獲取二期發(fā)電數(shù)據(jù)(三、四號機(jī)組),保存到數(shù)據(jù)庫中(x分鐘調(diào)度一次)*/@Scheduled(initialDelay = 30000, fixedRate = 30000)public void searchGasData() {logger.info("####獲取二期發(fā)電數(shù)據(jù)(三、四號機(jī)組) - 定時任務(wù)執(zhí)行時間:"+ dateFormat.format(new Date()));phase2Service.searchPhase2ElectricData();} }8、運行結(jié)果、定時獲取到opcua服務(wù)中的數(shù)據(jù)
 
OK,以上是所有的源代碼,大家的問題基本能夠解決,如果還有疑問,可一個給我發(fā)私信
總結(jié)
以上是生活随笔為你收集整理的使用java的milo框架访问OPCUA服务的方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 如何对Firefox拓展程序进行修改
- 下一篇: cocos builder中使用九宫格
