- 提供OpcUA 数据服务应用示例
提供OpcUA 数据服务应用示例
- 使用了opcua的Lua模块: open62541-lua
- 作为OPCUA服务器
- 发布所有设备的数据项
代码:
local class = require 'middleclass'local opcua = require 'opcua'--- 注册对象(请尽量使用唯一的标识字符串)local app = class("IOT_OPCUA_SERVER_APP")--- 设定应用最小运行接口版本(目前版本为1,为了以后的接口兼容性)app.API_VER = 1----- 应用对象初始化函数-- @param name: 应用本地安装名称。 如modbus_com_1-- @param sys: 系统sys接口对象。参考API文档中的sys接口说明-- @param conf: 应用配置参数。由安装配置中的json数据转换出来的数据对象function app:initialize(name, sys, conf)self._name = nameself._sys = sysself._conf = conf--- 获取数据接口self._api = sys:data_api()--- 获取日志接口self._log = sys:logger()self._nodes = {}end--- 设定变量的默认值local default_vals = {int = 0,string = '',}--- 创建OPCUA变量-- @param idx: 命名空间-- @param devobj: 设备OPCUA对象-- @param input: 输入项名称-- @param device: 系统设备对象用以获取当前数值local function create_var(idx, devobj, input, device)local var, err = devobj:getChild(input.name)if var thenvar:setDescription(opcua.LocalizedText.new('zh_CN', input.desc))return varendlocal attr = opcua.VariableAttributes.new()attr.displayName = opcua.LocalizedText.new("zh_CN", input.name)if input.desc thenattr.description = opcua.LocalizedText.new("zh_CN", input.desc)endlocal current = device:get_input_prop(input.name, 'value')local val = input.vt and default_vals[input.vt] or 0.0attr.value = opcua.Variant.new(current or val)return devobj:addVariable(opcua.NodeId.new(idx, 0), input.name, attr)end--- 设定变量的当前值-- @param var: OPCUA变量对象-- @param value: 变量的当前值-- @param timestamp: 时间戳-- @param quality: 质量戳local function set_var_value(var, value, timestamp, quality)-- TODO: for timestamp and qualityvar:setValue(opcua.Variant.new(value))--[[local val = opcua.DataValue.new(opcua.Variant.new(value))val.status = qualitylocal tm = opcua.DateTime.fromUnixTime(math.floor(timestamp)) + math.floor((timestamp%1) * 100) * 100000val.sourceTimestamp = tm--var.dataValue = valvar:setDataValue(val)]]--end--- 创建数据回调对象-- @param app: 应用实例对象local function create_handler(app)local api = app._apilocal server = app._serverlocal log = app._loglocal idx = app._idxlocal nodes = app._nodesreturn {--- 处理设备对象添加消息on_add_device = function(app, sn, props)--- 获取对象目录local objects = server:getObjectsNode()--- 使用设备SN来生成设备对象的IDlocal id = opcua.NodeId.new(idx, sn)local device = api:get_device(sn)---检测OPCUA对象是否已经存在local devobj, err = objects:getChild(idx..":"..sn)if not r or not devobj then--- 设备对象不存在增加设备对象local attr = opcua.ObjectAttributes.new()--- 设定显示名称attr.displayName = opcua.LocalizedText.new("zh_CN", "Device "..sn)--- 添加OPCUA对象devobj, err = objects:addObject(opcua.NodeId.new(idx, sn), sn, attr)if not devobj thenlog:warning('Create device object failed, error', devobj)returnendend--- 记录设备对象local node = nodes[sn] or {device = device,devobj = devobj,vars = {}}local vars = node.vars--- 将设备的输入项映射成为OPCUA对象的变量for i, input in ipairs(props.inputs) dolocal var = vars[input.name]if not var thenvars[input.name] = create_var(idx, devobj, input, device)else--- 如果存在尝试修改变量描述var:setDescription(opcua.LocalizedText.new('zh_CN', input.desc))endendnodes[sn] = nodeend,--- 处理设备对象删除消息on_del_device = function(app, sn)local node = nodes[sn]if node then--- 删除设备对象server:deleteNode(node.devobj.id, true)nodes[sn] = nilendend,--- 处理设备对象修改消息on_mod_device = function(app, sn, props)local node = nodes[sn]if not node or not node.vars then-- TODO:endlocal vars = node.varsfor i, input in ipairs(props.inputs) dolocal var = vars[input.name]---不存在就增加变量,存在则修改描述,确保描述一致if not var thenvars[input.name] = create_var(idx, node.devobj, input, node.device)elsevar:setDescription(opcua.LocalizedText.new('zh_CN', input.desc))endendend,--- 处理设备输入项数值变更消息on_input = function(app, sn, input, prop, value, timestamp, quality)local node = nodes[sn]if not node or not node.vars thenlog:error("Unknown sn", sn)returnend--- 设定OPCUA变量的当前值local var = node.vars[input]if var and prop == 'value' thenset_var_value(var, value, timestamp, quality)endend,}end--- 应用启动函数function app:start()--- 处理OPCUA模块的日志local Level_Funcs = {}Level_Funcs[opcua.LogLevel.TRACE] = assert(self._log.trace)Level_Funcs[opcua.LogLevel.DEBUG] = assert(self._log.debug)Level_Funcs[opcua.LogLevel.INFO] = assert(self._log.info)Level_Funcs[opcua.LogLevel.WARNING] = assert(self._log.warning)Level_Funcs[opcua.LogLevel.ERROR] = assert(self._log.error)Level_Funcs[opcua.LogLevel.FATAL] = assert(self._log.fatal)Category_Names = {}Category_Names[opcua.LogCategory.NETWORK] = "network"Category_Names[opcua.LogCategory.SECURECHANNEL] = "channel"Category_Names[opcua.LogCategory.SESSION] = "session"Category_Names[opcua.LogCategory.SERVER] = "server"Category_Names[opcua.LogCategory.CLIENT] = "client"Category_Names[opcua.LogCategory.USERLAND] = "userland"Category_Names[opcua.LogCategory.SECURITYPOLICY] = "securitypolicy"self._logger = function(level, category, ...)Level_Funcs[level](self._log, Category_Names[category], ...)endopcua.setLogger(self._logger)--- 生成OPCUA服务器实例local server = opcua.Server.new()--- 设定服务器地址server.config:setServerURI("urn:://opcua.symid.com")--- 添加命名空间local id = self._sys:id()local idx = server:addNamespace("http://iot.symid.com/"..id)self._server = serverself._idx = idx--- 设定回调处理对象self._handler = create_handler(self)self._api:set_handler(self._handler, true)--- List all devices and then create opcua objectself._sys:fork(function()local devs = self._api:list_devices() or {}for sn, props in pairs(devs) do--- Calling handler for creating opcua objectself._handler.on_add_device(self, sn, props)endend)--- 启动服务器server:startup()self._log:notice("Started!!!!")return trueend--- 应用退出函数function app:close(reason)self._server:shutdown()self._server = nilend--- 应用运行入口function app:run(tms)--- OPCUA模块运行入口while self._server.running dolocal ms = self._server:run_once(false)--- 暂停OPCUA模块运行,处理FreeIOE系统消息self._sys:sleep(ms % 10)endprint('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')return 1000end--- 返回应用对象return app
