opcua-service/main/java/de/opcua/app/opc/OpcUaService.java
2026-05-11 19:40:18 +02:00

192 lines
8.1 KiB
Java

package de.opcua.app.opc;
import de.opcua.app.model.TreeNodeRef;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
import org.eclipse.milo.opcua.stack.core.types.structured.ReferenceDescription;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public final class OpcUaService {
private volatile OpcUaClient client;
// ------------------------------------------------------------
// CONNECTION
// ------------------------------------------------------------
public boolean isConnected() {
return client != null;
}
public CompletableFuture<Void> connect(String endpointUrl) {
try {
OpcUaClient c = OpcUaClient.create(endpointUrl);
this.client = c;
return c.connect().thenApply(ignored -> null);
} catch (Exception e) {
CompletableFuture<Void> failed = new CompletableFuture<>();
failed.completeExceptionally(e);
return failed;
}
}
public CompletableFuture<Void> disconnect() {
OpcUaClient c = client;
client = null;
if (c == null) return CompletableFuture.completedFuture(null);
return c.disconnect().thenApply(ignored -> null);
}
// ------------------------------------------------------------
// BROWSE — async (für GUI)
// ------------------------------------------------------------
public CompletableFuture<List<TreeNodeRef>> browseRoot() {
return browseNode(Identifiers.RootFolder);
}
public CompletableFuture<List<TreeNodeRef>> browse(String nodeId) {
return browseNode(NodeId.parse(nodeId));
}
private CompletableFuture<List<TreeNodeRef>> browseNode(NodeId nodeId) {
try {
List<ReferenceDescription> refs = client.getAddressSpace().browse(nodeId);
return CompletableFuture.completedFuture(mapRefs(refs));
} catch (Exception e) {
CompletableFuture<List<TreeNodeRef>> f = new CompletableFuture<>();
f.completeExceptionally(e);
return f;
}
}
// ------------------------------------------------------------
// BROWSE — synchron (nur für Digital Twin Build Thread!)
// ------------------------------------------------------------
public List<TreeNodeRef> browseRootSync() throws Exception {
return browseNodeSync(Identifiers.RootFolder);
}
public List<TreeNodeRef> browseSync(String nodeId) throws Exception {
return browseNodeSync(NodeId.parse(nodeId));
}
private List<TreeNodeRef> browseNodeSync(NodeId nodeId) throws Exception {
List<ReferenceDescription> refs = client.getAddressSpace().browse(nodeId);
return mapRefs(refs);
}
// ── shared mapping ───────────────────────────────────────────────────────
private List<TreeNodeRef> mapRefs(List<ReferenceDescription> refs) {
return refs.stream()
.map(r -> {
String displayName = r.getDisplayName() != null
? r.getDisplayName().getText()
: r.getBrowseName().getName();
String nodeClass = r.getNodeClass() != null
? r.getNodeClass().name() : "Unknown";
String dataType = "Unknown";
try {
if (r.getTypeDefinition() != null)
dataType = r.getTypeDefinition().toParseableString();
} catch (Exception ignored) {}
String referenceTypeId = null;
try {
if (r.getReferenceTypeId() != null)
referenceTypeId = r.getReferenceTypeId().toParseableString();
} catch (Exception ignored) {}
return new TreeNodeRef(
displayName,
r.getNodeId().toParseableString(),
r.getBrowseName().getName(),
nodeClass, dataType, "ReadWrite", referenceTypeId);
})
.collect(Collectors.toList());
}
// ------------------------------------------------------------
// READ VALUE — async (für GUI)
// ------------------------------------------------------------
public CompletableFuture<String> readValue(String nodeId) {
NodeId id = NodeId.parse(nodeId);
return client.readValue(0, TimestampsToReturn.Neither, id)
.thenApply(dv -> {
if (dv == null || dv.getValue() == null) return "<null>";
Object v = dv.getValue().getValue();
return v == null ? "<null>" : v.toString();
});
}
// ------------------------------------------------------------
// READ VALUE — synchron (nur für Digital Twin Build Thread!)
// ------------------------------------------------------------
public String readValueSync(String nodeId) throws Exception {
NodeId id = NodeId.parse(nodeId);
var dv = client.readValue(0, TimestampsToReturn.Neither, id)
.get(5, TimeUnit.SECONDS);
if (dv == null || dv.getValue() == null) return "<null>";
Object v = dv.getValue().getValue();
return v == null ? "<null>" : v.toString();
}
// ------------------------------------------------------------
// WRITE VALUE
// ------------------------------------------------------------
public CompletableFuture<Boolean> writeValue(String nodeId, Object value) {
NodeId id = NodeId.parse(nodeId);
try {
Variant variant = convertToVariant(value);
org.eclipse.milo.opcua.stack.core.types.builtin.DataValue dataValue =
new org.eclipse.milo.opcua.stack.core.types.builtin.DataValue(variant);
return client.writeValue(id, dataValue)
.thenApply(statusCode -> {
if (statusCode != null && statusCode.isGood()) {
return true;
}
System.err.println("Write failed for " + nodeId + ": " + statusCode);
return false;
})
.exceptionally(ex -> {
System.err.println("Write exception for " + nodeId + ": " + ex.getMessage());
return false;
});
} catch (Exception e) {
CompletableFuture<Boolean> failed = new CompletableFuture<>();
failed.completeExceptionally(e);
return failed;
}
}
private Variant convertToVariant(Object value) {
if (value == null) return new Variant(null);
if (value instanceof String) {
String str = (String) value;
try {
return str.contains(".")
? new Variant(Double.parseDouble(str))
: new Variant(Integer.parseInt(str));
} catch (NumberFormatException e) {
return new Variant(str);
}
} else if (value instanceof Integer) return new Variant((Integer) value);
else if (value instanceof Long) return new Variant((Long) value);
else if (value instanceof Double) return new Variant((Double) value);
else if (value instanceof Float) return new Variant((Float) value);
else if (value instanceof Boolean) return new Variant((Boolean) value);
else if (value instanceof Short) return new Variant((Short) value);
else if (value instanceof Byte) return new Variant((Byte) value);
return new Variant(value.toString());
}
}