UA Server SDK C++ Bundle
1.3.2.200
|
This lesson explains how to create a UA Server Address Space in order to describe a real world system with UA. The lesson is based on a simple building automation scenario including aircondition and furnace control.
Content:
The lesson requires basic knowledge of OPC UA. See OPC UA Fundamentals - especially Address Space Concepts and OPC UA Node Classes - in order to become familiar with OPC UA concepts and terms.
Real world example
The real world example used for this getting started is a temperature controller with two different specialized types, an air conditioner controller and a furnace controller. Both controllers are providing state, temperature and temperature setpoint variables. Since both controllers share some variables, the object type representation defines a base ControllerType and the two derived object types AirConditionerControllerType and FurnaceControllerType. Figure 2-1 shows the object types and their variables in the OPC UA notation.
This lesson creates only the object types and object instances with data variables and properties. Additional features like methods and events are added in the following lessons.
Figure 2-1 gives you an overview of the OPC UA object types ControllerType, AirConditionerControllerType and FurnaceControllerType. The ControllerType is directly derived from the BaseObjectType. Instances of the AirConditionerControllerType and the FurnaceControllerType have also the variables defined on the ControllerType. The variables Temperature, TemperatureSetPoint, Humidity and HumiditySetPoint use the AnalogItemType which defines the properties EURange and EngineeringUnits. The variable types of the different variable components are indicated in the figure.
Figure 2-1 The Controller Object Type and the derived types
Figure 2-2 provides an overall view of the classes (and their dependencies) which will be implemented in the follwing steps of this lesson:
Step 1: Creating a new NodeManager : Class NmBuildingAutomation
Step 3: Creating the ControllerObject class : Class ControllerObject
Step 4: Creating the AirConditionerControllerObject : Class AirConditionerControllerObject
Step 5: Creating the FurnaceControllerObject : Class FurnaceControllerObject
The classes implemented in this lesson are white. The yellow and green classes are provided by the SDK. The green ones are interfaces for the different node classes defined by OPC UA. The yellow classes are implementations of these interfaces and the interfaces NodeManager and IOManager provided by the SDK. The interface NodeManager provides access to the nodes of the address space by UA services like Browse. The IOManager interface defines methods for Read, Write and Monitoring access to the attribute values of the nodes.
Figure 2-2 Class Overview
The class NmBuildingAutomation implements the interfaces NodeManager and IOManager by deriving from the SDK class NodeManagerBase. This covers already all services necessary for OPC Data Access functionality. The objects and variables providing the information about the controllers are created in this class and are then managed by the SDK class NodeManagerUaNode.
The classes ControllerObject , AirConditionerControllerObject and FurnaceControllerObject are representing the different controller types of the real world example by implementing the interface UaObject and by aggregating variables based on the UaVariable interface. This implementation classes are using classes provided by the SDK for the interface implementation.
NodeManagers are responsible for managing a set of nodes for one OPC UA Namespace. They provide methods to add and remove Nodes and References, they implement the OPC UA Browse Services and they resolve NodeIds to the IO information that IOManagers need for accessing the underlying data source.
For our example we create a new class NmBuildingAutomation which inherits from NodeManagerBase wich is derived from NodeManagerUaNode and IOManagerUaNode. These two classes are two spezialized classes which implement the SDK interfaces NodeManager, NodeManagerConfig and IOManager. These classes give you a default implementation for these SDK interfaces which work on an in-memory UA Node Model.
NodeManagerUaNode provides you with an implementation of NodeManagerConfig used to add and remove Nodes and References.
Figure 2-3 shows the class hierarchy and class members in detail.
Figure 2-3 NodeManager Class Hierarchy
IOManagerUaNode implements IOManager. For details about IOManager and IOManagerUaNode see Lesson 3: Connecting the nodes to real time data. By inheriting from both - NodeManagerUaNode and IOManagerUaNode - we get a class which is a NodeManager and an IOManager.
The class definition defines constructor and destructor and the two pure virtual functions afterStartUp and beforeShutDown of NodeManagerUaNode that we are going to implement. Additionally we define the method getInstanceDeclarationVariable that returns instance declaration nodes and a private createTypeNodes() method that will create our Type Model in the Address Space.
The macro UA_DISABLE_COPY disables copy constructor and assignment operator of this class to avoid misuses of this class.
Implementing this class is straight forward. First we have to implement the two pure virtual functions from NodeManagerUaNode. That is to say afterStartup() and beforeShutdown().
The construtor needs two parameters which initialize its base class. The first parameter is the NamespaceURI, that this NodeManager is responsible for. The second (optional) parameter is the HashTable size which the NodeManagerUaNode uses for managing the UaNodes. You should make this parameter huge enough to hold all your UaNodes and minimize the number of hash collisions.
In the destructor you can add cleanup code later. For now this is empty.
The afterStartUp() method is called right after the NodeManager has been created and intialized. Here we can create our UaNodes. For now we call only our private createTypeNodes() method that will create our Type Model for this NodeManager.
In beforeShutDown() you could implement any cleanup code. The created nodes are cleaned up automatically so this can stay empty here.
createTypeNodes() creates a server-specific type model. This is done by defining and adding Type Nodes to Address Space. Type Nodes represent more or less complex ObjectTypes. The latter are defined once by the server and can be used in several places. Several instances of the ObjectType can be instantiated, using the AddNodes Service. A server exposing complex ObjectTypes (and instances of that type) gives clients the possibility to program their application with knowledge of the type information and use this on all instances.
As a start createTypeNodes() adds the ObjectType "ControllerType". In our example BrowseName and DisplayName have the same string and only one language is supported for the Type system. Thus we can use the class UaObjectTypeSimple.
The NodeId for this Type can be numeric because our Type system is static. We use a define which is created in buildingautomationtypeids.h. This header also contains defines of the InstanceDeclarations according to ControllerType. InstanceDeclarations will be introduced beneath.
addNodeAndReference() adds the ObjectType Node to Adress Space and creates a HasSubtype Reference to ObjectType BaseObjectType.
This code section creates and adds the Variable "State" to the Address Space. This Variable is one of the entities, so-called InstanceDeclarations, used to define an ObjectType. InstanceDeclarations are defined as Variables, Objects, and Methods exposed beneath the ObjectType. Note that InstanceDeclarations are typically added with HasComponent or HasProperty.
This code section creates and adds the remainig Variables Temperature, TemperatureSetPoint and PowerConsumption. The two analog items Temperature and TemperatureSetPoint provide additional properties providing engineering information like range and units. The property nodes are created automatically. The values are set by using the methods setEURange and setEngineeringUnits.
To integrate the new NodeManager into the server it must be instantiated from OpcServerMain.
First open servermain.cpp and add the include to your new NodeManager to the existing list of includes.
The next step is the instantiation of the NodeManager.
After the NodeManager has been created you have to pass it to the addNodeManager() method. Thus the NodeManager will be integrated into the server Address Space. In this context a new entry in the server's NameSpaceTable will be created automatically.
We have already created ControllerType Nodes in NmBuildingAutomation::createTypeNodes() which define how an OPC UA ControllerType looks like.
We need also a C++ class which implements the functionality of the OPC UA ControllerType instances. We call this class ControllerObject. To be precise this class will be an abstract class, because the OPC UA ControllerType is declared as abstract, too. This means that this class will never be instantiated but will serve as base class for derived OPC UA ControllerType subclasses.
The class definition defines constructor, destructor, and eventNotifier(), a pure virtual function of UaObject that we are going to implement. The macro UA_DISABLE_COPY disables copy constructor and assignment operator of this class to avoid misuses of this class.
Indeed, we will implement eventNotifier() of UaObject, whereas the pure virtual function typeDefinition() of UaNode remains being unimplemented and with it results in abstract class ControllerObject.
The first three parameters to be passed to the constructor initialize its base class. The first parameter is the name of the ControllerObject used in BrowseName and DisplayName. The remaining ones are the NodeId and the default localeId of the object, rsp. The last parameter permits access to NodeManagerUaNode functionality, in particular addNodeAndReferences().
In addition the constructor creates the components of ControllerObject similarly to the InstanceDeclarations of "ControllerType" ObjectType.
The destructor is releasing the shared mutex reference of this class.
OpcUa_Byte ControllerObject::eventNotifier() const { return Ua_EventNotifier_None; }
eventnotifier() returns the value of the EventNotifier attribute. Events are introduced in Lesson 5: Adding support for Events.
The next two steps of this tutorial demonstrate the implementation of classes which inherit from ControllerObject and can be instantiated.
The class definition defines constructor, destructor, and typeDefinition(), a pure virtual function of UaNode. The macro UA_DISABLE_COPY disables copy constructor and assignment operator of this class.
The constructor demands four parameters being passed to its base class. It also creates the components of ControllerObject similarly to the InstanceDeclarations of "ControllerType" ObjectType.
For now the destructor is empty.
typeDefinitionId() returns the TypeDefinition NodeId using the Ba_AirConditionerControllerType define created in buildingautomationtypeids.h.
We are going to define and implement class FurnaceControllerObject as we did for AirConditionerControllerObject.
And in source file:
In the end we have to complete NmBuildingAutomation::afterStartUp() in order to create controller instances as Objects below the Objects Folder.