Initial upload opcua simulator
This commit is contained in:
14
src/main/java/de/opcua/app/model/ExportData.java
Normal file
14
src/main/java/de/opcua/app/model/ExportData.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package de.opcua.app.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import java.util.List;
|
||||
|
||||
/** Wurzel des OPC UA JSON-Exports. */
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ExportData {
|
||||
public String timestamp;
|
||||
public String exportType;
|
||||
public int nodeCount;
|
||||
public List<ExportNode> tree;
|
||||
public List<ExportNode> monitoredNodes;
|
||||
}
|
||||
97
src/main/java/de/opcua/app/model/ExportNode.java
Normal file
97
src/main/java/de/opcua/app/model/ExportNode.java
Normal file
@@ -0,0 +1,97 @@
|
||||
package de.opcua.app.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Ein Knoten im OPC UA JSON-Export.
|
||||
* Felder entsprechen dem opcua-export.json Format.
|
||||
* referenceTypeId ist optional (z.B. "i=35", "i=46", "i=47").
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ExportNode {
|
||||
|
||||
public String name;
|
||||
public String id;
|
||||
public String browseName;
|
||||
public String nodeClass;
|
||||
public String dataType;
|
||||
public String accessLevel;
|
||||
public String value;
|
||||
public String nodeId; // Feld im opcua-export Format
|
||||
public String displayName; // Feld im opcua-export Format
|
||||
public String referenceType; // z.B. "Organizes", "HasProperty", "HasComponent"
|
||||
public String referenceTypeId; // z.B. "i=35" (Organizes), "i=46" (HasProperty), "i=47" (HasComponent)
|
||||
public Boolean containsNoLoops; // OPC UA View-Attribut (null = unbekannt)
|
||||
public Long viewVersion; // OPC UA View-Attribut UInt32 (null = unbekannt)
|
||||
public List<ExportNode> children;
|
||||
|
||||
/** Effektiver Anzeigename: displayName > name > browseName. */
|
||||
public String effectiveName() {
|
||||
if (displayName != null && !displayName.isBlank()) return displayName;
|
||||
if (name != null && !name.isBlank()) return name;
|
||||
if (browseName != null && !browseName.isBlank()) return browseName;
|
||||
return "Node";
|
||||
}
|
||||
|
||||
/** Effektive NodeId: nodeId > id. */
|
||||
public String effectiveNodeId() {
|
||||
if (nodeId != null && !nodeId.isBlank()) return nodeId;
|
||||
return id;
|
||||
}
|
||||
|
||||
public String effectiveDataType() { return dataType; }
|
||||
|
||||
public boolean isVariable() {
|
||||
if (nodeClass != null)
|
||||
return "Variable".equals(nodeClass) || "VariableType".equals(nodeClass);
|
||||
return value != null || dataType != null;
|
||||
}
|
||||
|
||||
private static String referenceTypeToId(String ref) {
|
||||
if (ref == null || ref.isBlank()) return null;
|
||||
return switch (ref) {
|
||||
case "Organizes" -> "i=35";
|
||||
case "HasTypeDefinition" -> "i=40";
|
||||
case "HasSubtype" -> "i=45";
|
||||
case "HasProperty" -> "i=46";
|
||||
case "HasComponent" -> "i=47";
|
||||
case "HasNotifier" -> "i=48";
|
||||
default -> ref;
|
||||
};
|
||||
}
|
||||
|
||||
/** Konvertiert zu TreeNodeRef für GUI. */
|
||||
public TreeNodeRef toTreeNodeRef() {
|
||||
// 9-Arg-Konstruktor: alle Felder inkl. View-Attribute
|
||||
return new TreeNodeRef(
|
||||
effectiveName(),
|
||||
effectiveNodeId(),
|
||||
browseName != null ? browseName : effectiveName(),
|
||||
nodeClass != null ? nodeClass : (isVariable() ? "Variable" : "Object"),
|
||||
dataType,
|
||||
accessLevel != null ? accessLevel : "ReadWrite",
|
||||
referenceTypeId != null ? referenceTypeId : referenceTypeToId(referenceType),
|
||||
containsNoLoops != null ? containsNoLoops : true, // Default: true (sicher)
|
||||
viewVersion != null ? viewVersion : 0L // Default: 0 (unversioniert)
|
||||
);
|
||||
}
|
||||
|
||||
/** Konvertiert TreeNodeRef-Daten zurück in ExportNode. */
|
||||
public static ExportNode fromTreeNodeRef(TreeNodeRef ref) {
|
||||
ExportNode en = new ExportNode();
|
||||
en.name = ref.displayName();
|
||||
en.id = ref.nodeId();
|
||||
en.nodeId = ref.nodeId();
|
||||
en.displayName = ref.displayName();
|
||||
en.browseName = ref.browseName();
|
||||
en.nodeClass = ref.nodeClass();
|
||||
en.dataType = ref.dataType();
|
||||
en.accessLevel = ref.accessLevel();
|
||||
en.referenceTypeId = ref.referenceTypeId();
|
||||
en.referenceType = ref.referenceTypeId() != null ? TreeNodeRef.resolveRefTypeId(ref.referenceTypeId()) : null;
|
||||
en.containsNoLoops = ref.containsNoLoops();
|
||||
en.viewVersion = ref.viewVersion();
|
||||
return en;
|
||||
}
|
||||
}
|
||||
25
src/main/java/de/opcua/app/model/SimulatorSettings.java
Normal file
25
src/main/java/de/opcua/app/model/SimulatorSettings.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package de.opcua.app.model;
|
||||
|
||||
/** Einstellungen für den OPC UA Simulator. */
|
||||
public class SimulatorSettings {
|
||||
|
||||
private int port = 4840;
|
||||
private String endpointPath = "/";
|
||||
private String applicationName = "OPC UA Simulator";
|
||||
private String applicationUri = "urn:opcua-gui:simulator";
|
||||
private int updateIntervalSeconds = 1;
|
||||
private double noisePercent = 2.0;
|
||||
|
||||
public int getPort() { return port; }
|
||||
public void setPort(int v) { this.port = v; }
|
||||
public String getEndpointPath() { return endpointPath; }
|
||||
public void setEndpointPath(String v) { this.endpointPath = v; }
|
||||
public String getApplicationName() { return applicationName; }
|
||||
public void setApplicationName(String v) { this.applicationName = v; }
|
||||
public String getApplicationUri() { return applicationUri; }
|
||||
public void setApplicationUri(String v) { this.applicationUri = v; }
|
||||
public int getUpdateIntervalSeconds() { return updateIntervalSeconds; }
|
||||
public void setUpdateIntervalSeconds(int v){ this.updateIntervalSeconds = v; }
|
||||
public double getNoisePercent() { return noisePercent; }
|
||||
public void setNoisePercent(double v) { this.noisePercent = v; }
|
||||
}
|
||||
201
src/main/java/de/opcua/app/model/TreeNodeRef.java
Normal file
201
src/main/java/de/opcua/app/model/TreeNodeRef.java
Normal file
@@ -0,0 +1,201 @@
|
||||
package de.opcua.app.model;
|
||||
|
||||
/**
|
||||
* Immutable Record für einen OPC UA Knoten-Verweis.
|
||||
*
|
||||
* OPC UA ReferenceType-NodeIds (referenceTypeId):
|
||||
* "i=35" = Organizes
|
||||
* "i=36" = HasEventSource
|
||||
* "i=37" = HasModellingRule
|
||||
* "i=38" = HasDescription
|
||||
* "i=40" = HasTypeDefinition
|
||||
* "i=44" = HasEncoding
|
||||
* "i=45" = HasSubtype
|
||||
* "i=46" = HasProperty
|
||||
* "i=47" = HasComponent
|
||||
* "i=48" = HasNotifier
|
||||
* null = unbekannt / nicht gesetzt
|
||||
*
|
||||
* View-spezifische Felder (OPC UA Spec Part 3, §5.4):
|
||||
* containsNoLoops – Server garantiert azyklischen Graphen
|
||||
* viewVersion – UInt32 Versionsnummer (0 = unversioniert)
|
||||
*/
|
||||
public record TreeNodeRef(
|
||||
String displayName,
|
||||
String nodeId,
|
||||
String browseName,
|
||||
String nodeClass,
|
||||
String dataType,
|
||||
String accessLevel,
|
||||
String referenceTypeId, // z.B. "i=35", "i=46", "i=47"
|
||||
boolean containsNoLoops, // OPC UA View-Attribut
|
||||
long viewVersion // OPC UA View-Attribut (UInt32)
|
||||
) {
|
||||
|
||||
// ── Konstruktoren (vollständig kaskadiert) ──────────────────────────────
|
||||
|
||||
/** 2-Arg: minimaler Konstruktor (Legacy / Tests). */
|
||||
public TreeNodeRef(String displayName, String nodeId) {
|
||||
this(displayName, nodeId,
|
||||
displayName, "Unknown", "Unknown", "ReadWrite",
|
||||
null, true, 0L);
|
||||
}
|
||||
|
||||
/** 6-Arg: Backward-Kompatibilität (ohne View-Felder). */
|
||||
public TreeNodeRef(String displayName, String nodeId, String browseName,
|
||||
String nodeClass, String dataType, String accessLevel) {
|
||||
this(displayName, nodeId, browseName, nodeClass, dataType, accessLevel,
|
||||
null, true, 0L);
|
||||
}
|
||||
|
||||
/** 7-Arg: mit referenceTypeId, ohne View-Attribute. */
|
||||
public TreeNodeRef(String displayName, String nodeId, String browseName,
|
||||
String nodeClass, String dataType, String accessLevel,
|
||||
String referenceTypeId) {
|
||||
this(displayName, nodeId, browseName, nodeClass, dataType, accessLevel,
|
||||
referenceTypeId, true, 0L);
|
||||
}
|
||||
|
||||
// ── Hilfsmethoden ───────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (displayName == null || displayName.isBlank()) return nodeId != null ? nodeId : "";
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/** true für Variable und VariableType. */
|
||||
public boolean isVariable() {
|
||||
return "Variable".equals(nodeClass) || "VariableType".equals(nodeClass);
|
||||
}
|
||||
|
||||
/** true für browsebare Container-Knoten. */
|
||||
public boolean isContainer() {
|
||||
if (nodeClass == null) return false;
|
||||
return switch (nodeClass) {
|
||||
case "Object", "View", "ObjectType", "ReferenceType", "DataType", "Method" -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
/** true wenn dieser Knoten ein OPC UA View ist. */
|
||||
public boolean isView() {
|
||||
return "View".equals(nodeClass);
|
||||
}
|
||||
|
||||
/** Icon für TreeView-Darstellung. */
|
||||
public String icon() {
|
||||
if (nodeClass == null) return "❓"; // ❓
|
||||
return switch (nodeClass) {
|
||||
case "Variable" -> "📊"; // 📊
|
||||
case "VariableType" -> "📐"; // 📐
|
||||
case "Object" -> "📁"; // 📁
|
||||
case "View" -> "👁"; // 👁
|
||||
case "Method" -> "⚡"; // ⚡
|
||||
case "ObjectType" -> "🔷"; // 🔷
|
||||
case "ReferenceType" -> "🔗"; // 🔗
|
||||
case "DataType" -> "🔤"; // 🔤
|
||||
default -> "❓"; // ❓
|
||||
};
|
||||
}
|
||||
|
||||
/** Lesbarer DataType-Name aus NodeId-String. */
|
||||
public String shortDataType() {
|
||||
if (dataType == null) return "";
|
||||
return switch (dataType) {
|
||||
case "ns=0;i=1" -> "Boolean";
|
||||
case "ns=0;i=2" -> "SByte";
|
||||
case "ns=0;i=3" -> "Byte";
|
||||
case "ns=0;i=4" -> "Int16";
|
||||
case "ns=0;i=5" -> "UInt16";
|
||||
case "ns=0;i=6" -> "Int32";
|
||||
case "ns=0;i=7" -> "UInt32";
|
||||
case "ns=0;i=8" -> "Int64";
|
||||
case "ns=0;i=9" -> "UInt64";
|
||||
case "ns=0;i=10" -> "Float";
|
||||
case "ns=0;i=11" -> "Double";
|
||||
case "ns=0;i=12" -> "String";
|
||||
case "ns=0;i=13" -> "DateTime";
|
||||
case "ns=0;i=15" -> "ByteString";
|
||||
case "ns=0;i=63" -> "BaseVar";
|
||||
case "ns=0;i=68" -> "BaseData";
|
||||
case "ns=0;i=2365" -> "Float";
|
||||
case "ns=0;i=2368" -> "Double";
|
||||
default -> dataType.length() > 16 ? dataType.substring(0, 14) + "…" : dataType;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Lesbarer ReferenceType-Name aus NodeId-String.
|
||||
* Unterstützt alle Standard-OPC-UA-ReferenceTypes.
|
||||
*/
|
||||
public String shortRefType() {
|
||||
if (referenceTypeId == null) return "";
|
||||
return resolveRefTypeId(referenceTypeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Statische Hilfsmethode: ReferenceType-NodeId → lesbarer Name.
|
||||
* Wird auch von OpcUaRestApi.determineReferenceType() genutzt.
|
||||
*/
|
||||
public static String resolveRefTypeId(String refTypeId) {
|
||||
if (refTypeId == null) return "";
|
||||
return switch (refTypeId) {
|
||||
case "i=31" -> "References";
|
||||
case "i=32" -> "NonHierarchicalReferences";
|
||||
case "i=33" -> "HierarchicalReferences";
|
||||
case "i=34" -> "HasChild";
|
||||
case "i=35" -> "Organizes";
|
||||
case "i=36" -> "HasEventSource";
|
||||
case "i=37" -> "HasModellingRule";
|
||||
case "i=38" -> "HasDescription";
|
||||
case "i=39" -> "HasEncoding";
|
||||
case "i=40" -> "HasTypeDefinition";
|
||||
case "i=41" -> "GeneratesEvent";
|
||||
case "i=44" -> "Aggregates";
|
||||
case "i=45" -> "HasSubtype";
|
||||
case "i=46" -> "HasProperty";
|
||||
case "i=47" -> "HasComponent";
|
||||
case "i=48" -> "HasNotifier";
|
||||
case "i=49" -> "HasOrderedComponent";
|
||||
case "i=51" -> "HasInputVariables";
|
||||
case "i=52" -> "HasOutputVariables";
|
||||
case "i=54" -> "HasInterface";
|
||||
case "i=56" -> "HasAddIn";
|
||||
case "i=17603" -> "HasOptionalInputArgumentDescription";
|
||||
default -> refTypeId;
|
||||
};
|
||||
}
|
||||
|
||||
/** TreeView-Anzeigetext mit Icon. */
|
||||
public String treeLabel() {
|
||||
String icon = icon();
|
||||
if (isVariable()) {
|
||||
String dt = shortDataType();
|
||||
return icon + " " + displayName + (dt.isEmpty() ? "" : " [" + dt + "]");
|
||||
}
|
||||
if (isView()) {
|
||||
String vInfo = viewVersion > 0 ? " (v" + viewVersion + ")" : "";
|
||||
return icon + " " + displayName + vInfo;
|
||||
}
|
||||
return icon + " " + displayName;
|
||||
}
|
||||
|
||||
/** Konvertiert zurück zu ExportNode (für Server-Aufbau und JSON-Export). */
|
||||
public ExportNode toExportNode() {
|
||||
ExportNode en = new ExportNode();
|
||||
en.name = displayName;
|
||||
en.id = nodeId;
|
||||
en.nodeId = nodeId;
|
||||
en.displayName = displayName;
|
||||
en.browseName = browseName;
|
||||
en.nodeClass = nodeClass;
|
||||
en.dataType = dataType;
|
||||
en.accessLevel = accessLevel;
|
||||
en.referenceTypeId = referenceTypeId;
|
||||
en.referenceType = referenceTypeId != null ? resolveRefTypeId(referenceTypeId) : null;
|
||||
en.containsNoLoops = containsNoLoops;
|
||||
en.viewVersion = viewVersion;
|
||||
return en;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user