Initial upload opcua service
This commit is contained in:
192
main/java/de/opcua/app/opc/OpcUaService.java
Normal file
192
main/java/de/opcua/app/opc/OpcUaService.java
Normal file
@@ -0,0 +1,192 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user