バックグラウンド
PLC
先ほど、ローカルシミュレーション環境をセットアップし、上のデータをKEPServerEX6
読み取り、最後にそれをOPC クライアントとして使用して、この OPC サーバーからのデータ読み取りとサブスクリプション機能を完了しました。この記事では、接続、ノード トラバーサル、読み取り、書き込み、サブスクリプション、バッチ サブスクリプションなどの機能を含むクライアントを統合ライブラリを通じて実装します。PLC
UAExpert
KEPServerEX6
SpringBoot
Milo
OPC UA
ミロ図書館
Milo
ライブラリのアドレスGitHub
: https: //github.com/eclipse/milo
Milo
ライブラリはOPC UA
サーバーとクライアントを提供しますSDK
。明らかに、ここではOPC UA クライアント SDKのみを使用します。
依存関係を導入する
SpringBoot
Milo
ライブラリの依存関係 ( client SDK
) をバックエンド プロジェクトに導入します。
OPCUAクライアントの実装
接続
/**
* 创建OPC UA客户端
*
* @param ip
* @param port
* @param suffix
* @return
* @throws Exception
*/
public OpcUaClient connectOpcUaServer(String ip, String port, String suffix) throws Exception {
String endPointUrl = "opc.tcp://" + ip + ":" + port + suffix;
Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");
Files.createDirectories(securityTempDir);
if (!Files.exists(securityTempDir)) {
throw new Exception("unable to create security dir: " + securityTempDir);
}
OpcUaClient opcUaClient = OpcUaClient.create(endPointUrl,
endpoints ->
endpoints.stream()
.filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
.findFirst(),
configBuilder ->
configBuilder
.setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
.setApplicationUri("urn:eclipse:milo:examples:client")
//访问方式
.setIdentityProvider(new AnonymousProvider())
.setRequestTimeout(UInteger.valueOf(5000))
.build()
);
opcUaClient.connect().get();
Thread.sleep(2000); // 线程休眠一下再返回对象,给创建过程一个时间。
return opcUaClient;
}
ノードをトラバースする
/**
* 遍历树形节点
*
* @param client OPC UA客户端
* @param uaNode 节点
* @throws Exception
*/
public void listNode(OpcUaClient client, UaNode uaNode) throws Exception {
List<? extends UaNode> nodes;
if (uaNode == null) {
nodes = client.getAddressSpace().browseNodes(Identifiers.ObjectsFolder);
} else {
nodes = client.getAddressSpace().browseNodes(uaNode);
}
for (UaNode nd : nodes) {
//排除系统性节点,这些系统性节点名称一般都是以"_"开头
if (Objects.requireNonNull(nd.getBrowseName().getName()).contains("_")) {
continue;
}
System.out.println("Node= " + nd.getBrowseName().getName());
listNode(client, nd);
}
}
指定されたノードを読み取ります
/**
* 读取节点数据
*
* namespaceIndex可以通过UaExpert客户端去查询,一般来说这个值是2。
* identifier也可以通过UaExpert客户端去查询,这个值=通道名称.设备名称.标记名称
*
* @param client
* @param namespaceIndex
* @param identifier
* @throws Exception
*/
public void readNodeValue(OpcUaClient client, int namespaceIndex, String identifier) throws Exception {
//节点
NodeId nodeId = new NodeId(namespaceIndex, identifier);
//读取节点数据
DataValue value = client.readValue(0.0, TimestampsToReturn.Neither, nodeId).get();
// 状态
System.out.println("Status: " + value.getStatusCode());
//标识符
String id = String.valueOf(nodeId.getIdentifier());
System.out.println(id + ": " + value.getValue().getValue());
}
指定されたノードに書き込む
/**
* 写入节点数据
*
* @param client
* @param namespaceIndex
* @param identifier
* @param value
* @throws Exception
*/
public void writeNodeValue(OpcUaClient client, int namespaceIndex, String identifier, Float value) throws Exception {
//节点
NodeId nodeId = new NodeId(namespaceIndex, identifier);
//创建数据对象,此处的数据对象一定要定义类型,不然会出现类型错误,导致无法写入
DataValue newValue = new DataValue(new Variant(value), null, null);
//写入节点数据
StatusCode statusCode = client.writeValue(nodeId, newValue).join();
System.out.println("结果:" + statusCode.isGood());
}
指定されたノードをサブスクライブします
/**
* 订阅(单个)
*
* @param client
* @param namespaceIndex
* @param identifier
* @throws Exception
*/
private static final AtomicInteger atomic = new AtomicInteger();
public void subscribe(OpcUaClient client, int namespaceIndex, String identifier) throws Exception {
//创建发布间隔1000ms的订阅对象
client
.getSubscriptionManager()
.createSubscription(1000.0)
.thenAccept(t -> {
//节点
NodeId nodeId = new NodeId(namespaceIndex, identifier);
ReadValueId readValueId = new ReadValueId(nodeId, AttributeId.Value.uid(), null, null);
//创建监控的参数
MonitoringParameters parameters = new MonitoringParameters(UInteger.valueOf(atomic.getAndIncrement()), 1000.0, null, UInteger.valueOf(10), true);
//创建监控项请求
//该请求最后用于创建订阅。
MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters);
List<MonitoredItemCreateRequest> requests = new ArrayList<>();
requests.add(request);
//创建监控项,并且注册变量值改变时候的回调函数。
t.createMonitoredItems(
TimestampsToReturn.Both,
requests,
(item, id) -> item.setValueConsumer((it, val) -> {
System.out.println("nodeid :" + it.getReadValueId().getNodeId());
System.out.println("value :" + val.getValue().getValue());
})
);
}).get();
//持续订阅
Thread.sleep(Long.MAX_VALUE);
}
指定したノードをバッチでサブスクライブします
/**
* 批量订阅
*
* @param client
* @throws Exception
*/
public void subscribeBatch(OpcUaClient client) throws Exception {
final CountDownLatch eventLatch = new CountDownLatch(1);
//处理订阅业务
handlerMultipleNode(client);
//持续监听
eventLatch.await();
}
/**
* 处理订阅业务
*
* @param client OPC UA客户端
*/
private void handlerMultipleNode(OpcUaClient client) {
try {
//创建订阅
ManagedSubscription subscription = ManagedSubscription.create(client);
List<NodeId> nodeIdList = new ArrayList<>();
for (String id : batchIdentifiers) {
nodeIdList.add(new NodeId(batchNamespaceIndex, id));
}
//监听
List<ManagedDataItem> dataItemList = subscription.createDataItems(nodeIdList);
for (ManagedDataItem managedDataItem : dataItemList) {
managedDataItem.addDataValueListener((t) -> {
System.out.println(managedDataItem.getNodeId().getIdentifier().toString() + ":" + t.getValue().getValue().toString());
});
}
} catch (Exception e) {
e.printStackTrace();
}
}
切断・再接続の一括サブスクリプションについては記事末尾のソースコードを参照してください。実際にテストしたわけではありません。
テスト
KEPServerEX6のOPC UAサーバーに接続します
前回の記事のものをサーバーKEPServerEX6
として使用して、実装したクライアント機能をテストします。OPC UA
ここでnamespaceIndex
は、の構成または の右上隅の表示identifier
を参照してください。KEPServerEX6
UAExpert
Attribute
public class OpcUaStart {
public void start() throws Exception {
OpcUaClientService opcUaClientService = new OpcUaClientService();
// 与OPC UA服务端建立连接,并返回客户端实例
OpcUaClient client = opcUaClientService.connectOpcUaServer("127.0.0.1", "49320", "");
// 遍历所有节点
opcUaClientService.listNode(client, null);
// 读取指定节点的值
// opcUaClientService.readNodeValue(client, 2, "Demo.1500PLC.D1");
// opcUaClientService.readNodeValue(client, 2, "Demo.1500PLC.D2");
// 向指定节点写入数据
opcUaClientService.writeNodeValue(client, 2, "Demo.1500PLC.D1", 6f);
// 订阅指定节点
// opcUaClientService.subscribe(client, 2, "Demo.1500PLC.D1");
// 批量订阅多个节点
List<String> identifiers = new ArrayList<>();
identifiers.add("Demo.1500PLC.D1");
identifiers.add("Demo.1500PLC.D2");
opcUaClientService.setBatchNamespaceIndex(2);
opcUaClientService.setBatchIdentifiers(identifiers);
// opcUaClientService.subscribeBatch(client);
opcUaClientService.subscribeBatchWithReconnect(client);
}
}
スタートアップ クラスでOPC UA
クライアントを有効にすることを忘れないでください。
@SpringBootApplication
public class SpringbootOpcuaApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(SpringbootOpcuaApplication.class, args);
OpcUaStart opcUa = new OpcUaStart();
opcUa.start();
}
}
Milo が提供するテスト OPC UA サーバーに接続します
Milo
公式はオープンOPC UA
サーバーを提供しています: を使用opc.tcp://milo.digitalpetri.com:62541/milo
して、最初にUAExpert
接続をテストし (私は匿名接続を使用します)、その中のノードとアドレス情報を確認します。
public class OpcUaStart {
public void start() throws Exception {
OpcUaClientService opcUaClientService = new OpcUaClientService();
// 与OPC UA服务端建立连接,并返回客户端实例
OpcUaClient client = opcUaClientService.connectOpcUaServer("milo.digitalpetri.com", "62541", "/milo");
// 遍历所有节点
// opcUaClientService.listNode(client, null);
// 读取指定节点的值
opcUaClientService.readNodeValue(client, 2, "Dynamic/RandomInt32");
opcUaClientService.readNodeValue(client, 2, "Dynamic/RandomInt64");
// 向指定节点写入数据
// opcUaClientService.writeNodeValue(client, 2, "Demo.1500PLC.D1", 6f);
// 订阅指定节点
// opcUaClientService.subscribe(client, 2, "Dynamic/RandomDouble");
// 批量订阅多个节点
List<String> identifiers = new ArrayList<>();
identifiers.add("Dynamic/RandomDouble");
identifiers.add("Dynamic/RandomFloat");
opcUaClientService.setBatchNamespaceIndex(2);
opcUaClientService.setBatchIdentifiers(identifiers);
// opcUaClientService.subscribeBatch(client);
opcUaClientService.subscribeBatchWithReconnect(client);
}
}
テスト結果は次のとおりです。
考えられる問題
UaException: status=Bad_SessionClosed、message=セッションはクライアントによって閉じられました。
理由分析:opcUaClient.connect().get();
非同期プロセスであるため、読み取りおよび書き込み時に接続が確立されない可能性があります。
解決策: Thread.sleep(2000);
// スレッドはしばらくスリープしてからオブジェクトに戻り、作成プロセスに時間を与えます。
参照
https://blog.csdn.net/u013457145/article/details/121283612
ソースコード
https://github.com/heartsuit/demo-spring-boot/tree/master/springboot-opcua
ご質問やバグが見つかった場合は、お気軽にご連絡ください。
ご意見やご提案は大歓迎です。