Initial upload opcua simulator

This commit is contained in:
Christoph Hehl
2026-05-10 14:17:32 +02:00
commit 83052f99b6
37 changed files with 66609 additions and 0 deletions

View 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;
}

View 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;
}
}

View 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; }
}

View 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;
}
}