• 提供OpcUA 数据服务应用示例

    提供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