Avatar

Please consider registering
guest

sp_LogInOut Log In sp_Registration Register

Register | Lost password?
Advanced Search

— Forum Scope —




— Match —





— Forum Options —





Minimum search word length is 3 characters - maximum search word length is 84 characters

sp_Feed Topic RSS sp_TopicIcon
Dynamically create Structure and Enum DataTypes in Code
September 24, 2020
12:35, EEST
Avatar
hbrackel
Member
Members
Forum Posts: 144
Member Since:
February 21, 2014
sp_UserOfflineSmall Offline

Hi,

after being able to read values of Structure DataType instances at runtime without using generated code, now also the need arises to define such data types (TypeDefinitions) at runtime without using nodesets or generated code. Is this possible at all using the SDK? If so, I would appreciate codes snippet demonstrating the creation of an enum DataType as well as a Structure DataType TypeDefinition.

Thanks,
Hans-Uwe

September 24, 2020
18:43, EEST
Avatar
Bjarne Boström
Moderator
Moderators
Forum Posts: 1026
Member Since:
April 3, 2012
sp_UserOfflineSmall Offline

Hi,

See below snippet, note that it is based on one of our tests (as-is). I’ll try to patch up a self-contained one, but hopefully this is of immediate use.

In general, the answer is yes and no, depending what you consider “using the SDK”. As long as it is just about DataTypeDefinitions (the new Attribute defined in 1.04), then it is relatively easy (I do recommend creating the encodings nodes, see code; the ids are needed as that is what are used in ExtensionObjects i.e. it is NOT the DataType NodeId, but the encoding NodeId, however the need for the “physical nodes” is not 100% clear with DataTypeDefinition, but makes things a lot easier for clients, even though the current logic of UaClient/TypeDictionary would work without, but is one of the reasons that we need to read all types on connect nowadays).

If you also need to support clients that do not understand the DataTypeDefinition, then you will need to create an XML DataTypeDictionary document by yourself and encode it as binary value to the correct place and have mapping nodes to construct the path the DataType nodes (I’ll write better explanation, but it is like 1-3 A4papers/screens worth of text, see https://reference.opcfoundation.org/v104/Core/docs/Part5/#D, i.e. Annex D and E). For a generic-works-in-all-cases that can be .. a pain to do. Since the dictionaries are sort of being deprecated with the new Attribute, I’m sort of hoping we could avoid those in the future (backwars compability is a problem though; it might be that at some point the SDK should be able to write the XML, but it is somewhat complicated and in some cases not one-to-one mapping if we take all erratas and options into account). If you have a simple Structure with e.g. one Int32 and String fields, then it is trivial. Additionally, the DataTypeDictionaries cannot define multidimensional Structure fields at all (those plus their binary encoding did not exist when the DataTypedictionary was made in the first published iteration of the Specification), thus you might not be able to convert all DataTypeDefinitions to DataTypeDictionary data (and also in theory the dictionary could contain ones that cannot be converted to the Part 6 encoding rules for Structures, but I have made an assumption that those cannot really exist in practice).

In both cases you should do the encoding nodes, and for the dictionary you will need to also make the nodes that consists the path from the dictionary node to the datatype node (dictionary to description to encoding to datatype, I will probably need to point then later, but the References directions are non-obivous).

private void createCustomStructure() throws StatusException {
// This creates a simple custom Structure type and an instance using it.
// NOTE! This wont create DataTypeDictionaries, thus only 1.04 clients capable of using 1.04
// introduced Attribute DataTypeDefinition can handle this.

final String structureTypeName = MY_STRUCTURE_TYPE;
final NodeId typeId = new NodeId(myNodeManager.getNamespaceIndex(), structureTypeName);

// NOTE, encoding ids are the ones transmitted in the binary form
// so they should be Numeric NodeIds.
final NodeId binaryEncodingId = new NodeId(myNodeManager.getNamespaceIndex(), 12345);
// Note, this example ignores XML and JSON encoding ids

// Field 1
FieldSpecification.Builder field1Builder = FieldSpecification.builder();
field1Builder.setDataTypeId(UaNodeId.fromLocal(DataTypeIdentifiers.Int32, myNodeManager.getNamespaceTable()));
field1Builder.setName("Field1");
field1Builder.setJavaClass(Integer.class);
final FieldSpecification field1 = field1Builder.build();

// Field 2
FieldSpecification.Builder field2Builder = FieldSpecification.builder();
field2Builder.setDataTypeId(UaNodeId.fromLocal(DataTypeIdentifiers.String, myNodeManager.getNamespaceTable()));
field2Builder.setName("Field2");
field2Builder.setJavaClass(String.class);
final FieldSpecification field2 = field2Builder.build();

// StructureSpecification
StructureSpecification.Builder builder = StructureSpecification.builder();
builder.setStructureType(StructureType.NORMAL);
builder.setName(structureTypeName);
builder.setTypeId(UaNodeId.fromLocal(typeId, myNodeManager.getNamespaceTable()));
builder.setBinaryEncodeId(UaNodeId.fromLocal(binaryEncodingId, myNodeManager.getNamespaceTable()));
builder.addField(field1);
builder.addField(field2);

// This makes the StructureSpecification known to the SDK
StructureSpecification structureSpecification = builder.build();
myNodeManager.getServer().getEncoderContext().addStructureSpecification(structureSpecification);

// This makes the actual DataType node in the address space
UaDataTypeNode dataTypeNode = new UaDataTypeNode(myNodeManager, typeId,
new QualifiedName(myNodeManager.getNamespaceIndex(), structureTypeName), new LocalizedText(structureTypeName));
UaType structureTypeNode = myNodeManager.getNodeManagerTable().getType(DataTypeIdentifiers.Structure);
structureTypeNode.addSubType(dataTypeNode);

// Make an instance using this custom type
BaseVariableTypeNode instance = myNodeManager.createInstance(BaseVariableTypeNode.class, MY_STRUCTURE);
instance.setDataTypeId(typeId);

DynamicStructure value = new DynamicStructure(structureSpecification);
value.set("Field1", Integer.valueOf(1337)); // You can use the field name
value.set(field2, "Test"); // or the FieldSpecification object
instance.setValue(value);

// Linking the instance to the address space
myNodeManager.getNodeManagerTable().getNodeManagerRoot().getObjectsFolder().addReference(instance,
ReferenceTypeIdentifiers.Organizes, false);

// Create Encodings nodes
DataTypeEncodingTypeNode binaryEncodingNode = myNodeManager.createInstance(DataTypeEncodingTypeNode.class,
binaryEncodingId, new QualifiedName(0, "Default Binary"), new LocalizedText("Default Binary"));
myNodeManager.addNode(binaryEncodingNode);
dataTypeNode.addReference(binaryEncodingNode, ReferenceTypeIdentifiers.HasEncoding, false);
}

For Enumerations, there is 4 ways, the DataTypeDefinition Attribute, 2 different Properties for compact and sparse numberings and the DataTypeDictionary.

As a summary, SDK does abstact the information from multiple sources to StructureSpecification, but in order to go to the other direction you might need to replicate all sources.

P.S. It is very important that the BrowseName of the encoding id is in the 0-namespace, as that is what is used to search for that node from the references.

September 28, 2020
13:35, EEST
Avatar
Bjarne Boström
Moderator
Moderators
Forum Posts: 1026
Member Since:
April 3, 2012
sp_UserOfflineSmall Offline

OK, this is a modified MyNodeManager, which should be directly plug-able to the sampleconsoleserver example. See methods createCustomStructure and createCustomStructureWithCustomStructureField:

public class MyNodeManager extends NodeManagerUaNode {
public static final String NAMESPACE = "http://www.prosysopc.com/OPCUA…..";
private static final Logger logger = LoggerFactory.getLogger(MyNodeManager.class);
private static boolean stackTraceOnException;

private static void printException(Exception e) {
if (stackTraceOnException) {
e.printStackTrace();
} else {
println(e.toString());
if (e.getCause() != null) {
println("Caused by: " + e.getCause());
}
}
}

protected static void println(String string) {
System.out.println(string);
}

private ExclusiveLevelAlarmTypeNode myAlarm;

private UaObjectNode myDevice;

// private MyEventType myEvent;

private UaVariableNode myLevel;

private PlainMethod myMethod;

private CallableListener myMethodManagerListener;

private FolderTypeNode myObjectsFolder;

private PlainVariable<Boolean> mySwitch;

double dx = 1;

final MyEventManagerListener myEventManagerListener = new MyEventManagerListener();

/**
* Creates a new instance of MyNodeManager
*
* @param server the server in which the node manager is created.
* @param namespaceUri the namespace URI for the nodes
* @throws StatusException if something goes wrong in the initialization
* @throws UaInstantiationException if something goes wrong regarding object instantiation
*/
public MyNodeManager(UaServer server, String namespaceUri) throws StatusException, UaInstantiationException {
super(server, namespaceUri);
}

/**
* Defines the objects for which to store event history.
*
* @return the objects to historize
*/
public UaObjectNode[] getHistorizableEvents() {
return new UaObjectNode[] {myObjectsFolder, myDevice};
}

/**
* Defines the variables for which to store history.
*
* @return the variables to historize
*/
public UaVariableNode[] getHistorizableVariables() {
return new UaVariableNode[] {myLevel, mySwitch};
}

/**
* An example of triggering a custom event.
*/
public void sendEvent() {
// If the type has TypeDefinitionId, you can use the class with createEvent()
MyEventType ev = createEvent(MyEventType.class);
ev.setMessage(new LocalizedText("MyEvent"));
ev.setMyVariable(new Random().nextInt());
ev.setMyProperty("Property Value " + ev.getMyVariable());
ev.triggerEvent(null);
}

/**
* Runs one round of simulation.
*/
public void simulate() {
final DataValue v = myLevel.getValue();
Double nextValue = v.isNull() ? 0 : v.getValue().doubleValue() + dx;
if (nextValue <= 0) {
dx = 1;
} else if (nextValue >= 100) {
dx = -1;
}
try {
((CacheVariable) myLevel).updateValue(nextValue);
if (nextValue > myAlarm.getHighHighLimit()) {
activateAlarm(700, ExclusiveLimitState.HighHigh);
} else if (nextValue > myAlarm.getHighLimit()) {
activateAlarm(500, ExclusiveLimitState.High);
} else if (nextValue < myAlarm.getLowLowLimit()) {
activateAlarm(700, ExclusiveLimitState.Low);
} else if (nextValue < myAlarm.getLowLimit()) {
activateAlarm(500, ExclusiveLimitState.LowLow);
} else {
inactivateAlarm();
}
} catch (Exception e) {
logger.error("Error while simulating", e);
// printException(e);
throw new RuntimeException(e); // End the task
}

}

/**
* Activates an alarm, if it is not active already, or if the severity changes.
*
* @param severity the severity to set for the alarm
* @param limitState the limit state to set
*/
private void activateAlarm(int severity, ExclusiveLimitState limitState) {
if (myAlarm.isEnabled() && (!myAlarm.isActive() || (myAlarm.getSeverity().getValue() != severity))) {
println("Simulating alarm, MyNodeManager.activateAlarm: severity=" + severity);
myAlarm.setActive(true);
myAlarm.setRetain(true);
myAlarm.setAcked(false); // Also sets confirmed to false
myAlarm.setSeverity(severity);
myAlarm.getLimitStateNode().setCurrentLimitState(limitState);

triggerEvent(myAlarm);

// If you wish to check whether any clients are monitoring your
// alarm, you can use the following

// logger.info("myAlarm is monitored=" +
// myAlarm.isMonitoredForEvents());
}
}

/**
* A sample implementation of creating different types and instances manually.
*
* @throws StatusException if something goes wrong in the initialization
* @throws UaInstantiationException if something goes wrong regarding object instantiation
*/
private void createAddressSpace() throws StatusException, UaInstantiationException {
// +++ My nodes +++

int ns = getNamespaceIndex();

// My Event Manager Listener
this.getEventManager().setListener(myEventManagerListener);

// UA types and folders which we will use
final UaObject objectsFolder = getServer().getNodeManagerRoot().getObjectsFolder();
final UaType baseObjectType = getServer().getNodeManagerRoot().getType(Identifiers.BaseObjectType);
final UaType baseDataVariableType = getServer().getNodeManagerRoot().getType(Identifiers.BaseDataVariableType);

// Folder for my objects
final NodeId myObjectsFolderId = new NodeId(ns, "MyObjectsFolder");
myObjectsFolder = createInstance(FolderTypeNode.class, "MyObjects", myObjectsFolderId);

this.addNodeAndReference(objectsFolder, myObjectsFolder, Identifiers.Organizes);

// My Device Type

// The preferred way to create types is to use Information Models, but this example shows how
// you can do that also with your own code

final NodeId myDeviceTypeId = new NodeId(ns, "MyDeviceType");
UaObjectType myDeviceType = new UaObjectTypeNode(this, myDeviceTypeId, "MyDeviceType", Locale.ENGLISH);
this.addNodeAndReference(baseObjectType, myDeviceType, Identifiers.HasSubtype);

// My Device

final NodeId myDeviceId = new NodeId(ns, "MyDevice");
myDevice = new UaObjectNode(this, myDeviceId, "MyDevice", Locale.ENGLISH);
myDevice.setTypeDefinition(myDeviceType);
myObjectsFolder.addReference(myDevice, Identifiers.HasComponent, false);

// My Level Type

final NodeId myLevelTypeId = new NodeId(ns, "MyLevelType");
UaType myLevelType = this.addType(myLevelTypeId, "MyLevelType", baseDataVariableType);

// My Level Measurement

final NodeId myLevelId = new NodeId(ns, "MyLevel");
UaDataType doubleType = getServer().getNodeManagerRoot().getDataType(Identifiers.Double);
myLevel = new CacheVariable(this, myLevelId, "MyLevel", LocalizedText.NO_LOCALE);
myLevel.setDataType(doubleType);
myLevel.setTypeDefinition(myLevelType);
myDevice.addComponent(myLevel);

// My Switch
// Use PlainVariable and addComponent() to add it to myDevice
// Note that we use NodeIds instead of UaNodes to define the data type
// and type definition

NodeId mySwitchId = new NodeId(ns, "MySwitch");
mySwitch = new PlainVariable<Boolean>(this, mySwitchId, "MySwitch", LocalizedText.NO_LOCALE);
mySwitch.setDataTypeId(Identifiers.Boolean);
mySwitch.setTypeDefinitionId(Identifiers.BaseDataVariableType);
myDevice.addComponent(mySwitch); // addReference(…Identifiers.HasComponent…);

// Initial value
mySwitch.setCurrentValue(false);

// A sample alarm node
createAlarmNode(myLevel);

// A sample custom event type
createMyEventType();

// A sample enumeration type
createMyEnumNode();

// A sample method node
createMethodNode();

// A sample custom Structure type
createCustomStructure();

// A sample custom Structure type with a custom Structure field
// Uses the type from createCustomStructure, so it must be created called first.
createCustomStructureWithCustomStructureField();
}

/**
* Create a sample alarm node structure.
*
* @param source the variable that is the source of the alarm
*
* @throws StatusException if something goes wrong in the initialization
* @throws UaInstantiationException if something goes wrong regarding object instantiation
*/
private void createAlarmNode(UaVariable source) throws StatusException, UaInstantiationException {

// Level Alarm from the LevelMeasurement

// See the Spec. Part 9. Appendix B.2 for a similar example

int ns = this.getNamespaceIndex();
final NodeId myAlarmId = new NodeId(ns, source.getNodeId().getValue() + ".Alarm");
String name = source.getBrowseName().getName() + "Alarm";

// Since the HighHighLimit and others are Optional nodes,
// we need to define them to be instantiated.
TypeDefinitionBasedNodeBuilderConfiguration.Builder conf = TypeDefinitionBasedNodeBuilderConfiguration.builder();
conf.addOptional(UaBrowsePath.from(Ids.LimitAlarmType, UaQualifiedName.standard("HighHighLimit")));
conf.addOptional(UaBrowsePath.from(Ids.LimitAlarmType, UaQualifiedName.standard("HighLimit")));
conf.addOptional(UaBrowsePath.from(Ids.LimitAlarmType, UaQualifiedName.standard("LowLimit")));
conf.addOptional(UaBrowsePath.from(Ids.LimitAlarmType, UaQualifiedName.standard("LowLowLimit")));

// The configuration must be set to be used
// this.getNodeManagerTable().setNodeBuilderConfiguration(conf.build()); //global
// this.setNodeBuilderConfiguration(conf.build()); //local to this NodeManager
// createNodeBuilder(ExclusiveLevelAlarmTypeNode.class, conf.build()); //NodeBuilder specific
// (createInstance uses this internally)

// for purpose of this sample program, it is set to this manager, normally this would be set
// once after creating this NodeManager
this.setNodeBuilderConfiguration(conf.build());

myAlarm = createInstance(ExclusiveLevelAlarmTypeNode.class, name, myAlarmId);

// ConditionSource is the node which has this condition
myAlarm.setSource(source);
// Input is the node which has the measurement that generates the alarm
myAlarm.setInput(source);

myAlarm.setMessage(new LocalizedText("Level exceeded"));
myAlarm.setSeverity(500); // Medium level warning
myAlarm.setHighHighLimit(90.0);
myAlarm.setHighLimit(70.0);
myAlarm.setLowLimit(30.0);
myAlarm.setLowLowLimit(10.0);
myAlarm.setEnabled(true);
myDevice.addComponent(myAlarm); // addReference(…Identifiers.HasComponent…)

// + HasCondition, the SourceNode of the reference should normally
// correspond to the Source set above
source.addReference(myAlarm, Identifiers.HasCondition, false);

// + EventSource, the target of the EventSource is normally the
// source of the HasCondition reference
myDevice.addReference(source, Identifiers.HasEventSource, false);

// + HasNotifier, these are used to link the source of the EventSource
// up in the address space hierarchy
myObjectsFolder.addReference(myDevice, Identifiers.HasNotifier, false);
}

private void createCustomStructure() throws StatusException {
// This creates a simple custom Structure type and an instance using it.
// NOTE! This wont create DataTypeDictionaries, thus only 1.04 clients capable of using 1.04
// introduced Attribute DataTypeDefinition can handle this.

final String structureTypeName = "MyStructureType";
final NodeId typeId = new NodeId(getNamespaceIndex(), structureTypeName);

// NOTE, encoding ids are the ones transmitted in the binary form
// so they should be Numeric NodeIds.
final NodeId binaryEncodingId = new NodeId(getNamespaceIndex(), 12345);
// Note, this example ignores XML and JSON encoding ids

// Field 1
FieldSpecification.Builder field1Builder = FieldSpecification.builder();
field1Builder.setDataTypeId(UaNodeId.fromLocal(DataTypeIdentifiers.Int32, getNamespaceTable()));
field1Builder.setName("Field1");
field1Builder.setJavaClass(Integer.class);
final FieldSpecification field1 = field1Builder.build();

// Field 2
FieldSpecification.Builder field2Builder = FieldSpecification.builder();
field2Builder.setDataTypeId(UaNodeId.fromLocal(DataTypeIdentifiers.String, getNamespaceTable()));
field2Builder.setName("Field2");
field2Builder.setJavaClass(String.class);
final FieldSpecification field2 = field2Builder.build();

// StructureSpecification
StructureSpecification.Builder builder = StructureSpecification.builder();
builder.setStructureType(StructureType.NORMAL);
builder.setName(structureTypeName);
builder.setTypeId(UaNodeId.fromLocal(typeId, getNamespaceTable()));
builder.setBinaryEncodeId(UaNodeId.fromLocal(binaryEncodingId, getNamespaceTable()));
builder.addField(field1);
builder.addField(field2);

// This makes the StructureSpecification known to the SDK
StructureSpecification structureSpecification = builder.build();
getServer().getEncoderContext().addStructureSpecification(structureSpecification);

// This makes the actual DataType node in the address space
UaDataTypeNode dataTypeNode = new UaDataTypeNode(this, typeId,
new QualifiedName(getNamespaceIndex(), structureTypeName), new LocalizedText(structureTypeName));
UaType structureTypeNode = getNodeManagerTable().getType(DataTypeIdentifiers.Structure);
structureTypeNode.addSubType(dataTypeNode);

// Make an instance using this custom type
BaseVariableTypeNode instance = createInstance(BaseVariableTypeNode.class, "MyStructure");
instance.setDataTypeId(typeId);

DynamicStructure value = new DynamicStructure(structureSpecification);
value.set("Field1", Integer.valueOf(1337)); // You can use the field name
value.set(field2, "Test"); // or the FieldSpecification object
instance.setValue(value);

// Linking the instance to the address space
myDevice.addReference(instance, ReferenceTypeIdentifiers.Organizes, false);

// Create Encodings nodes
DataTypeEncodingTypeNode binaryEncodingNode = createInstance(DataTypeEncodingTypeNode.class, binaryEncodingId,
new QualifiedName(0, "Default Binary"), new LocalizedText("Default Binary"));
addNode(binaryEncodingNode);
dataTypeNode.addReference(binaryEncodingNode, ReferenceTypeIdentifiers.HasEncoding, false);
}

private void createCustomStructureWithCustomStructureField() throws StatusException {
// This creates a simple custom Structure type and an instance using it.
// NOTE! This wont create DataTypeDictionaries, thus only 1.04 clients capable of using 1.04
// introduced Attribute DataTypeDefinition can handle this.

final String structureTypeName = "MyNestedStructureType";
final NodeId typeId = new NodeId(getNamespaceIndex(), structureTypeName);

// NOTE, encoding ids are the ones transmitted in the binary form
// so they should be Numeric NodeIds.
final NodeId binaryEncodingId = new NodeId(getNamespaceIndex(), 23456);
// Note, this example ignores XML and JSON encoding ids

// Field 1
FieldSpecification.Builder field1Builder = FieldSpecification.builder();
field1Builder.setDataTypeId(UaNodeId.fromLocal(DataTypeIdentifiers.Int32, getNamespaceTable()));
field1Builder.setName("IntegerField");
field1Builder.setJavaClass(Integer.class);
final FieldSpecification field1 = field1Builder.build();

// Field 2, a custom Structure of the type created in createCustomStructure()
FieldSpecification.Builder field2Builder = FieldSpecification.builder();
field2Builder.setName("StructureField");
// Custom Structures (that are not codegenerated) will use the DynamicStructure.class here
field2Builder.setJavaClass(DynamicStructure.class);
final UaNodeId field2DataTypeId = UaNodeId.from(getNamespaceUri(), "MyStructureType");
field2Builder.setDataTypeId(field2DataTypeId);
final FieldSpecification field2 = field2Builder.build();

// StructureSpecification
StructureSpecification.Builder builder = StructureSpecification.builder();
builder.setStructureType(StructureType.NORMAL);
builder.setName(structureTypeName);
builder.setTypeId(UaNodeId.fromLocal(typeId, getNamespaceTable()));
builder.setBinaryEncodeId(UaNodeId.fromLocal(binaryEncodingId, getNamespaceTable()));
builder.addField(field1);
builder.addField(field2);

// This makes the StructureSpecification known to the SDK
StructureSpecification structureSpecification = builder.build();
getServer().getEncoderContext().addStructureSpecification(structureSpecification);

// This makes the actual DataType node in the address space
UaDataTypeNode dataTypeNode = new UaDataTypeNode(this, typeId,
new QualifiedName(getNamespaceIndex(), structureTypeName), new LocalizedText(structureTypeName));
UaType structureTypeNode = getNodeManagerTable().getType(DataTypeIdentifiers.Structure);
structureTypeNode.addSubType(dataTypeNode);

// Make an instance using this custom type
BaseVariableTypeNode instance = createInstance(BaseVariableTypeNode.class, "MyNestedStructure");
instance.setDataTypeId(typeId);

DynamicStructure value = new DynamicStructure(structureSpecification);
value.set("IntegerField", Integer.valueOf(1337)); // You can use the field name

// Creating a builder that will make Structure objects of the StructureSpecification of the
// field
StructureSpecification field2StructureSpecification =
getServer().getEncoderContext().getStructureSpecification(field2DataTypeId);
Structure.Builder field2ValueBuilder = field2StructureSpecification.toStructureBuilder();
field2ValueBuilder.set(field2StructureSpecification.getField("Field1"), Integer.valueOf(1));
field2ValueBuilder.set(field2StructureSpecification.getField("Field2"), "Field2Value");

value.set(field2, field2ValueBuilder.build()); // or the FieldSpecification object
instance.setValue(value);

// Linking the instance to the address space
myDevice.addReference(instance, ReferenceTypeIdentifiers.Organizes, false);

// Create Encodings nodes
DataTypeEncodingTypeNode binaryEncodingNode = createInstance(DataTypeEncodingTypeNode.class, binaryEncodingId,
new QualifiedName(0, "Default Binary"), new LocalizedText("Default Binary"));
addNode(binaryEncodingNode);
dataTypeNode.addReference(binaryEncodingNode, ReferenceTypeIdentifiers.HasEncoding, false);
}

/**
* Create a sample method.
*
* @throws StatusException if something goes wrong in the initialization
*/
private void createMethodNode() throws StatusException {
int ns = this.getNamespaceIndex();
final NodeId myMethodId = new NodeId(ns, "MyMethod");
myMethod = new PlainMethod(this, myMethodId, "MyMethod", Locale.ENGLISH);
Argument[] inputs = new Argument[2];
inputs[0] = new Argument();
inputs[0].setName("Operation");
inputs[0].setDataType(Identifiers.String);
inputs[0].setValueRank(ValueRanks.Scalar);
inputs[0].setArrayDimensions(null);
inputs[0].setDescription(new LocalizedText(
"The operation to perform on parameter: valid functions are sin, cos, tan, pow", Locale.ENGLISH));
inputs[1] = new Argument();
inputs[1].setName("Parameter");
inputs[1].setDataType(Identifiers.Double);
inputs[1].setValueRank(ValueRanks.Scalar);
inputs[1].setArrayDimensions(null);
inputs[1].setDescription(new LocalizedText("The parameter for operation", Locale.ENGLISH));
myMethod.setInputArguments(inputs);

Argument[] outputs = new Argument[1];
outputs[0] = new Argument();
outputs[0].setName("Result");
outputs[0].setDataType(Identifiers.Double);
outputs[0].setValueRank(ValueRanks.Scalar);
outputs[0].setArrayDimensions(null);
outputs[0].setDescription(new LocalizedText("The result of ‘operation(parameter)’", Locale.ENGLISH));
myMethod.setOutputArguments(outputs);

this.addNodeAndReference(myDevice, myMethod, Identifiers.HasComponent);

// Create the listener that handles the method calls
myMethodManagerListener = new MyMethodManagerListener(myMethod);
MethodManagerUaNode m = (MethodManagerUaNode) this.getMethodManager();
m.addCallListener(myMethodManagerListener);
}

/**
* @throws StatusException if the necessary type node(s) are not found
*
*/
private void createMyEnumNode() throws StatusException {
// An example showing how a new enumeration type can be defined in code.
// Note that it is usually easier to define new types using information models and
// generating Java code out of those. See the more about that in the
// ‘codegen’ documentation.

// 1. Create the type node…

NodeId myEnumTypeId = new NodeId(this.getNamespaceIndex(), "MyEnumType");

UaDataType myEnumType = new UaDataTypeNode(this, myEnumTypeId, "MyEnumType", LocalizedText.NO_LOCALE);

// … as sub type of Enumeration
UaType enumerationType = getServer().getNodeManagerRoot().getType(Identifiers.Enumeration);
enumerationType.addSubType(myEnumType);

// 2. Add the EnumStrings property …

NodeId myEnumStringsId = new NodeId(this.getNamespaceIndex(), "MyEnumType_EnumStrings");;
PlainProperty<LocalizedText[]> enumStringsProperty = new PlainProperty<LocalizedText[]>(this, myEnumStringsId,
new QualifiedName("EnumStrings"), new LocalizedText("EnumStrings", LocalizedText.NO_LOCALE));
enumStringsProperty.setDataTypeId(Identifiers.LocalizedText);
enumStringsProperty.setValueRank(ValueRanks.OneDimension);
enumStringsProperty.setArrayDimensions(new UnsignedInteger[] {UnsignedInteger.ZERO});
enumStringsProperty.setAccessLevel(AccessLevelType.CurrentRead);
enumStringsProperty.addReference(Identifiers.ModellingRule_Mandatory, Identifiers.HasModellingRule, false);

myEnumType.addProperty(enumStringsProperty);

// … with Value
enumStringsProperty.setCurrentValue(new LocalizedText[] {new LocalizedText("Zero"), new LocalizedText("One"),
new LocalizedText("Two"), new LocalizedText("Three")});

EnumerationSpecification myEnumSpecification = EnumerationSpecification.builder().setName("MyEnumType")
.setTypeId(UaNodeId.fromLocal(myEnumTypeId, getNamespaceTable())).addMapping(0, "Zero").addMapping(1, "One")
.addMapping(2, "Two").addMapping(3, "Three").build();
// This makes DataTypeDefinition Attribute work for the enum
getNodeManagerTable().getEncoderContext().addEnumerationSpecification(myEnumSpecification);

// 3. Create the instance
NodeId myEnumObjectId = new NodeId(this.getNamespaceIndex(), "MyEnumObject");
CacheVariable myEnumVariable = new CacheVariable(this, myEnumObjectId, "MyEnumObject", LocalizedText.NO_LOCALE);
myEnumVariable.setDataType(myEnumType);

// .. as a component of myDevice
myDevice.addComponent(myEnumVariable);

// 4. Initialize the value
myEnumVariable.setValue(new DataValue(new Variant(myEnumSpecification.createEnumerationFromInteger(1))));
}

/**
* A sample custom event type.
* <p>
* NOTE that it is usually easier to create new types using the Information Models and import them
* from XML to the server. You can also generate the respective Java types with the ‘codegen’ from
* the same XML. In this example, we will construct the type into the address space "manually".
* MyEventType is also hand-coded and is registered to be used to create the instances of that
* type.
* <p>
* When the type definition is in the address space, and the respective Java class is registered
* to the server, it will create those instances, for example as shown in {@link #sendEvent()}.
*
* @throws StatusException if something goes wrong in the initialization
*/
private void createMyEventType() throws StatusException {
int ns = this.getNamespaceIndex();

NodeId myEventTypeId = new NodeId(ns, MyEventType.MY_EVENT_ID);
UaObjectType myEventType = new UaObjectTypeNode(this, myEventTypeId, "MyEventType", LocalizedText.NO_LOCALE);
getServer().getNodeManagerRoot().getType(Identifiers.BaseEventType).addSubType(myEventType);

NodeId myVariableId = new NodeId(ns, MyEventType.MY_VARIABLE_ID);
PlainVariable<Integer> myVariable =
new PlainVariable<Integer>(this, myVariableId, MyEventType.MY_VARIABLE_NAME, LocalizedText.NO_LOCALE);
myVariable.setDataTypeId(Identifiers.Int32);
// The modeling rule must be defined for the mandatory elements to
// ensure that the event instances will also get the elements.
myVariable.addModellingRule(ModellingRule.Mandatory);
myEventType.addComponent(myVariable);

NodeId myPropertyId = new NodeId(ns, MyEventType.MY_PROPERTY_ID);
PlainProperty<Integer> myProperty =
new PlainProperty<Integer>(this, myPropertyId, MyEventType.MY_PROPERTY_NAME, LocalizedText.NO_LOCALE);
myProperty.setDataTypeId(Identifiers.String);
myProperty.addModellingRule(ModellingRule.Mandatory);
myEventType.addProperty(myProperty);

getServer().registerClass(MyEventType.class, myEventTypeId);
}

/**
* Changes myAlarm to inactive state and triggers an event about the change. If the event is not
* acknowledged, it will keep the retain state set, meaning that it can still show up in the
* client applications alarm view, for example.
*/
private void inactivateAlarm() {
if (myAlarm.isEnabled() && myAlarm.isActive()) {
println("Simulating alarm (inactive), MyNodeManager.inactivateAlarm");
myAlarm.setActive(false);
myAlarm.setRetain(!myAlarm.isAcked());
myAlarm.getLimitStateNode().setCurrentLimitState(ExclusiveLimitState.None);

triggerEvent(myAlarm);
}
}

/**
* Send an event notification.
*
* @param event The event to trigger.
*/
private void triggerEvent(BaseEventTypeNode event) {
// Trigger event
final DateTime now = DateTime.currentTime();
// Use your own EventId to keep track of your events, if you need to (for example when alarms
// are acknowledged)
ByteString myEventId = getNextUserEventId();
// If you wish, you can record the full event ID that is provided by triggerEvent, although your
// own ‘myEventId’ is usually enough to keep track of the event.
/* ByteString fullEventId = */event.triggerEvent(now, now, myEventId);
}

protected ByteString getNextUserEventId() {
return myEventManagerListener.getNextUserEventId();
}

/*
* (non-Javadoc)
*
* @see com.prosysopc.ua.server.NodeManagerUaNode#init()
*/
@Override
protected void init() throws StatusException, UaNodeFactoryException {
super.init();

createAddressSpace();
}

void addNode(String name) {
// Initialize NodeVersion property, to enable ModelChangeEvents
myObjectsFolder.initNodeVersion();

getServer().getNodeManagerRoot().beginModelChange();
try {
NodeId nodeId = new NodeId(this.getNamespaceIndex(), UUID.randomUUID());

UaNode node =
this.getNodeFactory().createNode(NodeClass.Variable, nodeId, name, Locale.ENGLISH, Identifiers.PropertyType);
myObjectsFolder.addComponent(node);
} catch (UaNodeFactoryException e) {
printException(e);
} catch (IllegalArgumentException e) {
printException(e);
} finally {
getServer().getNodeManagerRoot().endModelChange();
}
}

void deleteNode(QualifiedName nodeName) throws StatusException {
UaNode node = myObjectsFolder.getComponent(nodeName);
if (node != null) {
getServer().getNodeManagerRoot().beginModelChange();
try {
this.deleteNode(node, true, true);
} finally {
getServer().getNodeManagerRoot().endModelChange();
}
} else {
println("MyObjects does not contain a component with name " + nodeName);
}
}
}

We’ll probably ship some variation of this in the next release (though the code is somewhat long and complicated still so there is room for improvements). In addition no DataTypeDictionary generation yet as well. But we do have a beta build with this currently if needed.

For enumerations it pretty much goes the same way, but instead of encoding nodes you’ll set the Property EnumStrings or EnumValues for the DataType node (see https://reference.opcfoundation.org/v104/Core/docs/Part3/5.8.3/).

Forum Timezone: Europe/Helsinki

Most Users Ever Online: 1919

Currently Online:
12 Guest(s)

Currently Browsing this Page:
1 Guest(s)

Top Posters:

Heikki Tahvanainen: 402

hbrackel: 144

rocket science: 88

pramanj: 86

Francesco Zambon: 83

Ibrahim: 78

Sabari: 62

kapsl: 57

gjevremovic: 49

Xavier: 43

Member Stats:

Guest Posters: 0

Members: 735

Moderators: 7

Admins: 1

Forum Stats:

Groups: 3

Forums: 15

Topics: 1523

Posts: 6449

Newest Members:

rust, christamcdowall, redaahern07571, nigelbdhmp, travistimmons, AnnelCib, dalenegettinger, howardkennerley, Thomassnism, biancacraft16

Moderators: Jouni Aro: 1026, Pyry: 1, Petri: 0, Bjarne Boström: 1026, Jimmy Ni: 26, Matti Siponen: 346, Lusetti: 0

Administrators: admin: 1