In this example, we want to create a OPCUA Server that exposes 3 read/write variables
The server will expose the variable under a new object named "MyDevice".
+ RootFolder
+ Objects
+ MyDevice
+ MyVariable1
+ MyVariable2
The first steps assumes that you are running a shell in a terminal on Linux or Mac, or under Git Bash cmd on Windows.
- (note: please make sure node.js is installed. Follow the instructions here ).
Let's create a node project for our server.
$ mkdir myserver
$ cd myserver
$ npm init # create a package.json
$ npm install node-opcua --save # add the node-opcua
Now edit the sample_server.js script.
The script will be organised around the following four steps:
_"declaration"
(async ()=>{
_"server instantiation"
_"server initialization"
})();
Let visit each step in order:
The node-opcua sdk is made available to the application by this 'require' statement:
const { OPCUAServer, Variant, DataType, StatusCodes} = require("node-opcua");
A OPCUAServer instance need to be created. Options can be passed to the OPCUAServer to customize the behavior. For a simple server, you just need to specify a TCP port.
// Let's create an instance of OPCUAServer
const server = new OPCUAServer({
port: 4334, // the port of the listening socket of the server
resourcePath: "/UA/MyLittleServer", // this path will be added to the endpoint resource name
_"setting server info"
});
The resource path will be used to construct the endpoint uniform resource identifier (uri) of our server. In our case, the endpoint urn of our server will be
opc.tcp://<hostname>:4334/UA/MyLittleServerwhere
hostnameshall be replaced with your computer name or fully qualified domain name.
Client will have to use this URN to connect to the server.
additional information can be set at this stage such as the server buildInfo.
buildInfo : {
productName: "MySampleServer1",
buildNumber: "7658",
buildDate: new Date(2014,5,2)
}
Once created the server shall be initialized. During initialisation, the server will load its default nodeset and prepare the binding of all standard OPCUA variables. The initialize method is a asynchronous operation that requires a 'callback' function that will get executed when the initialization process is completed. the callback is function that contains the post_initialisation steps that we want to execute.
await server.initialize();
console.log("initialized");
_"post initialization"
_"start the server"
Once the server has been initialized, it is a good idea to extend the default server namespace with our variables.
Lets create a function that will extend the server default address space with some variables that we want to expose. This function will be called inside the initialize callback.
The addressSpace
is used to customize the objet model that our server will expose to the external world.
const addressSpace = server.engine.addressSpace;
const namespace = addressSpace.getOwnNamespace();
// declare a new object
_"add a new object into the objects folder"
// add some variables
_"add some variables"
const device = namespace.addObject({
organizedBy: addressSpace.rootFolder.objects,
browseName: "MyDevice"
});
Adding a read-only variable inside the server namespace requires only a getter function. This function returns a Variant containing the value of the variable to scan.
// add a variable named MyVariable1 to the newly created folder "MyDevice"
let variable1 = 1;
// emulate variable1 changing every 500 ms
setInterval(() => { variable1+=1; }, 500);
namespace.addVariable({
componentOf: device,
browseName: "MyVariable1",
dataType: "Double",
value: {
get: () => new Variant({dataType: DataType.Double, value: variable1 })
}
});
Note that we haven't specified a NodeId for the variable.The server will automatically assign a new nodeId for us.
Let's create a more comprehensive Read-Write variable with a fancy nodeId
// add a variable named MyVariable2 to the newly created folder "MyDevice"
let variable2 = 10.0;
namespace.addVariable({
componentOf: device,
nodeId: "ns=1;b=1020FFAA", // some opaque NodeId in namespace 4
browseName: "MyVariable2",
dataType: "Double",
minimumSamplingInterval: 1234, // we need to specify a minimumSamplingInterval when using a getter
value: {
get: () => new Variant({dataType: DataType.Double, value: variable2 }),
set: (variant) => {
variable2 = parseFloat(variant.value);
return StatusCodes.Good;
}
}
});
Lets create a variable that expose the percentage of free memory on the running machine.
Let's write a small utility function that calculate this value.
const os = require("os");
/**
* returns the percentage of free memory on the running machine
* @return {double}
*/
function available_memory() {
// var value = process.memoryUsage().heapUsed / 1000000;
const percentageMemUsed = os.freemem() / os.totalmem() * 100.0;
return percentageMemUsed;
}
Now let's expose our OPCUA Variable
namespace.addVariable({
componentOf: device,
nodeId: "s=free_memory", // a string nodeID
browseName: "FreeMemory",
dataType: "Double",
value: {
get: () => new Variant({dataType: DataType.Double, value: available_memory() })
}
});
Once the server has been created and initialised, we use the start asynchronous method to let the server initiate all its endpoints and start listening to clients.
server.start(function() {
console.log("Server is now listening ... ( press CTRL+C to stop)");
console.log("port ", server.endpoints[0].port);
_"display endpoint url"
});
Once the server has been created and configured, it is possible to retrieve the endpoint url.
const endpointUrl = server.endpoints[0].endpointDescriptions()[0].endpointUrl;
console.log(" the primary server endpoint url is ", endpointUrl );
$ node sample_server.js