1.0 node-opcua简介
node-opcua是一个完全用Typescript为NodeJS编写的OPC UA栈的实现。不依赖于外部第三方库。它依赖于一些仅在服务器端可用的javascript模块,如fs (FileSystem)或net(Socket)。
2.0 创建一个简单的node-opcua 服务器
在这个示例中,我们创建一个OPC UA服务器,它公开3个读/写变量,服务器将在名为"我的设备"的新对象下公开这些变量。
Plain
+ 跟文件夹
+ 项目
+ 我的设备
+ 变量1
+ 变量2
2.1 首先创建一个项目:
bash
$ mkdir myserver
$ cd myserver
$ npm init # 初始化项目创建一个package.json文件
$ npm install node-opcua --save # 添加依赖node-opcua模块
2.2 编辑项目
在项目目录项创建并编辑一个sample_server.js文件
2.2.1 通过'require'语句导入node-opcua模块:
javascript
const { OPCUAServer, Variant, DataType, StatusCodes} = require("node-opcua");
2.2.2 服务器实例化
创建一个OPCUAServer实例。可以将选项传递给OPCUAServer来定制行为。对于简单的服务器,您只需要指定一个TCP端口。
javascript
// 创建一个OPCUAServer
const server = new OPCUAServer({
port: 4334, // 服务器套接字的监听端口
resourcePath: "/UA/MyLittleServer", //此路径将添加到端点资源名称中
buildInfo: {
productName: "我的示例服务器1",
buildNumber: "7658",
buildDate: new Date(2014, 5, 2)
}
});
资源路径将用于构造服务器的端点统一资源标识符(url)。在我们的例子中,服务器的端点urn将是:
javascript
opc.tcp://<hostname>:4334/UA/MyLittleServer
hostname替换为您的计算机名称或完全合格的域名。 客户端必须使用这个URN连接到服务器。
设置服务器信息,可以在这个阶段设置其他信息,例如:
javascript
buildInfo: {
productName: "我的示例服务器1",
buildNumber: "7658",
buildDate: new Date(2022, 9, 8)
}
2.2.3 服务器初始化
一旦创建,服务器将被初始化。在初始化期间,服务器将加载其默认节点集,并准备所有标准OPC UA变量的绑定。initialize方法是一个异步操作,需要一个"回调"函数,该函数将在初始化过程完成时执行。
javascript
await server.initialize();
console.log("已初始化");
2.2.4 初始化后
让我们创建一个函数,它将使用一些我们想要公开的变量来扩展服务器默认地址空间。这个函数将在初始化回调函数中调用。
addressSpace用于定制我们的服务器将向外部世界公开的对象模型。
javascript
const addressSpace = server.engine.addressSpace;
const namespace = addressSpace.getOwnNamespace();
2.2.5 在objects文件夹中添加一个新对象
javascript
// 声明一个新对象
const device = namespace.addObject({
organizedBy: addressSpace.rootFolder.objects,
browseName: "我的设备"
});
2.2.6 添加一些变量
在服务器名称空间中添加变量1, 模拟"变量1"每500毫秒改变一次。
javascript
// 将"变量1"添加到新创建的文件夹"我的设备"中
const variable1 = namespace.addVariable({
componentOf: device,
browseName: "变量1",
dataType: "Double",
minimumSamplingInterval: 100,
});
let counter = 0;
// 模拟"变量1"每500毫秒改变一次
const timerId = setInterval(() => { counter += 1; variable1.setValueFromSource({ dataType: DataType.Double, value: counter }) }, 500);
addressSpace.registerShutdownTask(() => { clearInterval(timerId); });
注意,上面的例子,我们没有为变量指定NodeId。服务器会自动为我们分配一个新的nodeId。
添加变量2,这个示例我们指定节点ID:
javascript
// 在新创建的文件夹我的设备中添加一个名为"变量2"的变量
namespace.addVariable({
componentOf: device,
nodeId: "ns=1;b=1020FFAA", // 命名空间中的节点ID
browseName: "变量2",
dataType: "Double",
minimumSamplingInterval: 0, // 这个变量受事件驱动
});
下面再创建一个变量,来公开运行中的机器上的可用内存百分比。 我们写一个函数来计算这个值。
javascript
const os = require("os");
/**
* 返回运行中的机器上的空闲内存百分比
* @return {double}
*/
function available_memory() {
// var value = process.memoryUsage().heapUsed / 1000000;
const percentageMemUsed = os.freemem() / os.totalmem() * 100.0;
return percentageMemUsed;
}
namespace.addVariable({
componentOf: device,
nodeId: "s=free_memory", // 一个字符串节点ID
browseName: "空闲内存",
dataType: "Double",
minimumSamplingInterval: 1234,
value: {
get: () => new Variant({ dataType: DataType.Double, value: available_memory() })
}
});
2.2.7 启动服务器 创建并初始化服务器之后,我们使用start异步方法让服务器初始化它的所有端点并开始监听客户端。显示端点url
javascript
await server.start();
console.log("服务器正在监听 ... ( 按 CTRL+C 停止)");
console.log("端口 ", server.endpoints[0].port);
console.log(" 主服务器端点url是 ", server.getEndpointUrl());
2.2.8 下面,是完整sample_server.js文件
javascript
const os = require("os");
const { OPCUAServer, Variant, DataType, StatusCodes } = require("node-opcua");
(async () => {
// 创建一个OPCUAServer
const server = new OPCUAServer({
port: 4334, // 服务器套接字的监听端口
resourcePath: "/UA/MyLittleServer", //此路径将添加到端点资源名称中
buildInfo: {
productName: "我的示例服务器1",
buildNumber: "7658",
buildDate: new Date(2022, 9, 8)
}
});
await server.initialize();
console.log("已初始化");
const addressSpace = server.engine.addressSpace;
const namespace = addressSpace.getOwnNamespace();
// 声明一个新对象
const device = namespace.addObject({
organizedBy: addressSpace.rootFolder.objects,
browseName: "我的设备"
});
// 添加一些变量
// 将"变量1"添加到新创建的文件夹"我的设备"中
const variable1 = namespace.addVariable({
componentOf: device,
browseName: "变量1",
dataType: "Double",
minimumSamplingInterval: 100,
});
let counter = 0;
// 模拟"变量1"每500毫秒改变一次
const timerId = setInterval(() => { counter += 1; variable1.setValueFromSource({ dataType: DataType.Double, value: counter }) }, 500);
addressSpace.registerShutdownTask(() => { clearInterval(timerId); });
// 在新创建的文件夹我的设备中添加一个名为"变量2"的变量
namespace.addVariable({
componentOf: device,
nodeId: "ns=1;b=1020FFAA", // 命名空间中的节点ID
browseName: "变量2",
dataType: "Double",
minimumSamplingInterval: 0, // 这个变量受事件驱动
});
/**
* 返回运行中的机器上的空闲内存百分比
* @return {double}
*/
function available_memory() {
// var value = process.memoryUsage().heapUsed / 1000000;
const percentageMemUsed = os.freemem() / os.totalmem() * 100.0;
return percentageMemUsed;
}
namespace.addVariable({
componentOf: device,
nodeId: "s=free_memory", // 一个字符串节点ID
browseName: "空闲内存",
dataType: "Double",
minimumSamplingInterval: 1234,
value: {
get: () => new Variant({ dataType: DataType.Double, value: available_memory() })
}
});
await server.start();
console.log("服务器正在监听 ... ( 按 CTRL+C 停止)");
console.log("端口 ", server.endpoints[0].port);
console.log(" 主服务器端点url是 ", server.getEndpointUrl());
process.once("SIGINT", async () => {
console.log("正在关闭");
await server.shutdown();
});
})();
2.2.9 执行并测试服务器
javascript
$ node sample_server.js

3.0 使用UaExpert测试服务
3.1 打开UaExpert,右键选择"Project|Server"在上下文菜单点击"Add..."添加

3.2 打开添加服务器窗口,添加服务器

3.3 添加服务器URL地址,本例中为opc.tcp://DESKTOP-MGFMSQP:4334/UA/MyLittleServer
3.4 服务器被找到,选择加密方式,确认添加
3.5 在Servers文件夹下,选择新添加的服务器NodeOPCUA,右键上下文菜单点击Connect连接服务器。
3.6 在下面的Adddress Space(地址空间),列出了我的设备和变量
3.7 把变量拖入Data Access View(数据访问视图),可以看到变量值的变化。 