提供OpcUA 数据服务应用示例

  • 使用了opcua的Lua模块: open62541-lua
  • 作为OPCUA服务器
  • 发布所有设备的数据项

代码:

  1. local class = require 'middleclass'
  2. local opcua = require 'opcua'
  3. --- 注册对象(请尽量使用唯一的标识字符串)
  4. local app = class("IOT_OPCUA_SERVER_APP")
  5. --- 设定应用最小运行接口版本(目前版本为1,为了以后的接口兼容性)
  6. app.API_VER = 1
  7. ---
  8. -- 应用对象初始化函数
  9. -- @param name: 应用本地安装名称。 modbus_com_1
  10. -- @param sys: 系统sys接口对象。参考API文档中的sys接口说明
  11. -- @param conf: 应用配置参数。由安装配置中的json数据转换出来的数据对象
  12. function app:initialize(name, sys, conf)
  13. self._name = name
  14. self._sys = sys
  15. self._conf = conf
  16. --- 获取数据接口
  17. self._api = sys:data_api()
  18. --- 获取日志接口
  19. self._log = sys:logger()
  20. self._nodes = {}
  21. end
  22. --- 设定变量的默认值
  23. local default_vals = {
  24. int = 0,
  25. string = '',
  26. }
  27. --- 创建OPCUA变量
  28. -- @param idx: 命名空间
  29. -- @param devobj: 设备OPCUA对象
  30. -- @param input: 输入项名称
  31. -- @param device: 系统设备对象用以获取当前数值
  32. local function create_var(idx, devobj, input, device)
  33. local var, err = devobj:getChild(input.name)
  34. if var then
  35. var:setDescription(opcua.LocalizedText.new('zh_CN', input.desc))
  36. return var
  37. end
  38. local attr = opcua.VariableAttributes.new()
  39. attr.displayName = opcua.LocalizedText.new("zh_CN", input.name)
  40. if input.desc then
  41. attr.description = opcua.LocalizedText.new("zh_CN", input.desc)
  42. end
  43. local current = device:get_input_prop(input.name, 'value')
  44. local val = input.vt and default_vals[input.vt] or 0.0
  45. attr.value = opcua.Variant.new(current or val)
  46. return devobj:addVariable(opcua.NodeId.new(idx, 0), input.name, attr)
  47. end
  48. --- 设定变量的当前值
  49. -- @param var: OPCUA变量对象
  50. -- @param value: 变量的当前值
  51. -- @param timestamp: 时间戳
  52. -- @param quality: 质量戳
  53. local function set_var_value(var, value, timestamp, quality)
  54. -- TODO: for timestamp and quality
  55. var:setValue(opcua.Variant.new(value))
  56. --[[
  57. local val = opcua.DataValue.new(opcua.Variant.new(value))
  58. val.status = quality
  59. local tm = opcua.DateTime.fromUnixTime(math.floor(timestamp)) + math.floor((timestamp%1) * 100) * 100000
  60. val.sourceTimestamp = tm
  61. --var.dataValue = val
  62. var:setDataValue(val)
  63. ]]--
  64. end
  65. --- 创建数据回调对象
  66. -- @param app: 应用实例对象
  67. local function create_handler(app)
  68. local api = app._api
  69. local server = app._server
  70. local log = app._log
  71. local idx = app._idx
  72. local nodes = app._nodes
  73. return {
  74. --- 处理设备对象添加消息
  75. on_add_device = function(app, sn, props)
  76. --- 获取对象目录
  77. local objects = server:getObjectsNode()
  78. --- 使用设备SN来生成设备对象的ID
  79. local id = opcua.NodeId.new(idx, sn)
  80. local device = api:get_device(sn)
  81. ---检测OPCUA对象是否已经存在
  82. local devobj, err = objects:getChild(idx..":"..sn)
  83. if not r or not devobj then
  84. --- 设备对象不存在增加设备对象
  85. local attr = opcua.ObjectAttributes.new()
  86. --- 设定显示名称
  87. attr.displayName = opcua.LocalizedText.new("zh_CN", "Device "..sn)
  88. --- 添加OPCUA对象
  89. devobj, err = objects:addObject(opcua.NodeId.new(idx, sn), sn, attr)
  90. if not devobj then
  91. log:warning('Create device object failed, error', devobj)
  92. return
  93. end
  94. end
  95. --- 记录设备对象
  96. local node = nodes[sn] or {
  97. device = device,
  98. devobj = devobj,
  99. vars = {}
  100. }
  101. local vars = node.vars
  102. --- 将设备的输入项映射成为OPCUA对象的变量
  103. for i, input in ipairs(props.inputs) do
  104. local var = vars[input.name]
  105. if not var then
  106. vars[input.name] = create_var(idx, devobj, input, device)
  107. else
  108. --- 如果存在尝试修改变量描述
  109. var:setDescription(opcua.LocalizedText.new('zh_CN', input.desc))
  110. end
  111. end
  112. nodes[sn] = node
  113. end,
  114. --- 处理设备对象删除消息
  115. on_del_device = function(app, sn)
  116. local node = nodes[sn]
  117. if node then
  118. --- 删除设备对象
  119. server:deleteNode(node.devobj.id, true)
  120. nodes[sn] = nil
  121. end
  122. end,
  123. --- 处理设备对象修改消息
  124. on_mod_device = function(app, sn, props)
  125. local node = nodes[sn]
  126. if not node or not node.vars then
  127. -- TODO:
  128. end
  129. local vars = node.vars
  130. for i, input in ipairs(props.inputs) do
  131. local var = vars[input.name]
  132. ---不存在就增加变量,存在则修改描述,确保描述一致
  133. if not var then
  134. vars[input.name] = create_var(idx, node.devobj, input, node.device)
  135. else
  136. var:setDescription(opcua.LocalizedText.new('zh_CN', input.desc))
  137. end
  138. end
  139. end,
  140. --- 处理设备输入项数值变更消息
  141. on_input = function(app, sn, input, prop, value, timestamp, quality)
  142. local node = nodes[sn]
  143. if not node or not node.vars then
  144. log:error("Unknown sn", sn)
  145. return
  146. end
  147. --- 设定OPCUA变量的当前值
  148. local var = node.vars[input]
  149. if var and prop == 'value' then
  150. set_var_value(var, value, timestamp, quality)
  151. end
  152. end,
  153. }
  154. end
  155. --- 应用启动函数
  156. function app:start()
  157. --- 处理OPCUA模块的日志
  158. local Level_Funcs = {}
  159. Level_Funcs[opcua.LogLevel.TRACE] = assert(self._log.trace)
  160. Level_Funcs[opcua.LogLevel.DEBUG] = assert(self._log.debug)
  161. Level_Funcs[opcua.LogLevel.INFO] = assert(self._log.info)
  162. Level_Funcs[opcua.LogLevel.WARNING] = assert(self._log.warning)
  163. Level_Funcs[opcua.LogLevel.ERROR] = assert(self._log.error)
  164. Level_Funcs[opcua.LogLevel.FATAL] = assert(self._log.fatal)
  165. Category_Names = {}
  166. Category_Names[opcua.LogCategory.NETWORK] = "network"
  167. Category_Names[opcua.LogCategory.SECURECHANNEL] = "channel"
  168. Category_Names[opcua.LogCategory.SESSION] = "session"
  169. Category_Names[opcua.LogCategory.SERVER] = "server"
  170. Category_Names[opcua.LogCategory.CLIENT] = "client"
  171. Category_Names[opcua.LogCategory.USERLAND] = "userland"
  172. Category_Names[opcua.LogCategory.SECURITYPOLICY] = "securitypolicy"
  173. self._logger = function(level, category, ...)
  174. Level_Funcs[level](self._log, Category_Names[category], ...)
  175. end
  176. opcua.setLogger(self._logger)
  177. --- 生成OPCUA服务器实例
  178. local server = opcua.Server.new()
  179. --- 设定服务器地址
  180. server.config:setServerURI("urn:://opcua.symid.com")
  181. --- 添加命名空间
  182. local id = self._sys:id()
  183. local idx = server:addNamespace("http://iot.symid.com/"..id)
  184. self._server = server
  185. self._idx = idx
  186. --- 设定回调处理对象
  187. self._handler = create_handler(self)
  188. self._api:set_handler(self._handler, true)
  189. --- List all devices and then create opcua object
  190. self._sys:fork(function()
  191. local devs = self._api:list_devices() or {}
  192. for sn, props in pairs(devs) do
  193. --- Calling handler for creating opcua object
  194. self._handler.on_add_device(self, sn, props)
  195. end
  196. end)
  197. --- 启动服务器
  198. server:startup()
  199. self._log:notice("Started!!!!")
  200. return true
  201. end
  202. --- 应用退出函数
  203. function app:close(reason)
  204. self._server:shutdown()
  205. self._server = nil
  206. end
  207. --- 应用运行入口
  208. function app:run(tms)
  209. --- OPCUA模块运行入口
  210. while self._server.running do
  211. local ms = self._server:run_once(false)
  212. --- 暂停OPCUA模块运行,处理FreeIOE系统消息
  213. self._sys:sleep(ms % 10)
  214. end
  215. print('XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')
  216. return 1000
  217. end
  218. --- 返回应用对象
  219. return app