192 lines
8.1 KiB
Java
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());
|
|
}
|
|
} |