流程图![读取opc属性流程图](https://img-blog.csdnimg.cn/fe6573aa25e14dd6a18f3194153fde27.png)
流程分析
获取协议连接
Thingsboard gateway中实现了很多协议连接类,代码根据配置的连接类型进行初始化并动态调用连接类,源码如下
if connector_config["config"][config] is not None:
connector_name = connector_config["name"]
if not self.available_connectors.get(connector_name):
connector = self._implemented_connectors[connector_type](self,
connector_config["config"][
config],
connector_type)
connector.setName(connector_name)
self.available_connectors[connector.get_name()] = connector
connector.open()
else:
break
else:
log.info("Config not found for %s", connector_type)
_implemented_connectors来源如下
if connector['type'] != "grpc":
connector_class = None
if connector.get('useGRPC', False):
module_name = f'Grpc{self._default_connectors.get(connector["type"], connector.get("class"))}'
connector_class = TBModuleLoader.import_module(connector['type'], module_name)
if self.__grpc_manager and self.__grpc_manager.is_alive() and connector_class:
connector_persistent_key = self._generate_persistent_key(connector,
connectors_persistent_keys)
else:
connector_class = TBModuleLoader.import_module(connector['type'],
self._default_connectors.get(
connector['type'],
connector.get('class')))
if connector_class is None:
log.warning("Connector implementation not found for %s", connector["name"])
else:
self._implemented_connectors[connector['type']] = connector_class
协议类型实现类,是根据TBModuleLoader进行导入的,connector_class = TBModuleLoader.import_module(connector[‘type’], module_name)
建立连接
connector.open()
获取节点
从流程图可以看出来,获取属性主要是__search_node方法,因此我们主要来看下他的逻辑。
一般来说,如果要连接一个服务获取指定的某个属性,我们直接获取服务端值就可以了
如:
node = self.client.get_node(fullpath)
我们看下源码如何实现的
#获取全路径表达式
fullpath_pattern = regex.compile(fullpath)
#转义路径分隔符
full1 = fullpath.replace('\\\\.', '.')
#获取当前节点路径
current_node_path = self.get_node_path(current_node)
#获取当前节点类型
child_node_parent_class = current_node.get_node_class()
new_parent = current_node
#遍历子节点
for child_node in current_node.get_children():
#获取子节点类型
new_node_class = child_node.get_node_class()
#获取子节点路径
new_node_path = self.get_node_path(child_node)
#子节点类型为view并且当前节点不为None时将当前节点路径替换为子节点路径方便下次循环
if child_node_parent_class == ua.NodeClass.View and new_parent is not None:
parent_path = self.get_node_path(new_parent)
fullpath = fullpath.replace(current_node_path, parent_path)
#转义路径分隔符
nnp1 = new_node_path.replace('\\\\.', '.')
nnp2 = new_node_path.replace('\\\\', '\\')
if self.__show_map:
self._log.debug("SHOW MAP: Current node path: %s", new_node_path)
# 判断是否能完全匹配
regex_fullmatch = regex.fullmatch(fullpath_pattern, nnp1) or \
nnp2 == full1 or \
nnp2 == fullpath or \
nnp1 == full1
if regex_fullmatch:
if self.__show_map:
self._log.debug("SHOW MAP: Current node path: %s - NODE FOUND", nnp2)
#能完全匹配则写入结果返回
result.append(child_node)
else:
#判断是否可以前缀匹配
regex_search = fullpath_pattern.fullmatch(nnp1, partial=True) or \
nnp2 in full1 or \
nnp1 in full1
#可以前缀匹配则进行循环遍历
if regex_search:
if self.__show_map:
self._log.debug("SHOW MAP: Current node path: %s - NODE FOUND", new_node_path)
#如果类型是object,则继续往下匹配,知道完全匹配
if new_node_class == ua.NodeClass.Object:
if self.__show_map:
self._log.debug("SHOW MAP: Search in %s", new_node_path)
self.__search_node(child_node, fullpath, result=result)
# 如果类型是method,则返回节点
elif new_node_class == ua.NodeClass.Method and search_method:
self._log.debug("Found in %s", new_node_path)
result.append(child_node)
写的比较复杂,原因是opc无法直接根据路径获取节点,只能通过遍历的方式进行反复对比,因此出现了源码的逻辑,对应的逻辑上我均加了注释说明
运行后发现实际上并没有进入完全匹配的逻辑,从而导致没有获取到节点,为什么会产生这样的原因是因为
if child_node_parent_class == ua.NodeClass.View and new_parent is not None:
parent_path = self.get_node_path(new_parent)
fullpath = fullpath.replace(current_node_path, parent_path)
这一步我这边遍历的属性里面压根就没有View类型的数据,不知道是否是我的模拟器问题,从代码逻辑上来看,这一步主要是要获取到父节点路径,并将当前节点路径拼接上父节点路径,因此直接对代码进行了二次修改,引入了parentPath变量,在遍历的时候将当前节点的路径拼接好传递下去,从而解决问题
def __search_node(self, current_node, fullpath, search_method=False, parentPath=None, result=None):
if result is None:
result = []
try:
if regex.match(r"ns=\d*;[isgb]=.*", fullpath, regex.IGNORECASE):
if self.__show_map:
self._log.debug("Looking for node with config")
node = self.client.get_node(fullpath)
if node is None:
self._log.warning("NODE NOT FOUND - using configuration %s", fullpath)
else:
self._log.debug("Found in %s", node)
result.append(node)
else:
fullpath_pattern = regex.compile(fullpath)
full1 = fullpath.replace('\\\\.', '.')
current_node_path = self.get_node_path(current_node)
if parentPath is None:
parentPath = current_node_path
else:
parentPath = parentPath + "\\." + current_node_path
# we are allways the parent
child_node_parent_class = current_node.get_node_class()
new_parent = current_node
for child_node in current_node.get_children():
child_node.parentPath = parentPath
new_node_class = child_node.get_node_class()
new_node_path = self.get_node_path(child_node)
if(new_node_path.startswith(parentPath) == False):
new_node_path = parentPath + '\\.' + new_node_path
nnp1 = new_node_path.replace('\\\\.', '.')
nnp2 = new_node_path.replace('\\\\', '\\')
if self.__show_map:
self._log.debug("SHOW MAP: Current node path: %s", new_node_path)
regex_fullmatch = regex.fullmatch(fullpath_pattern, nnp1) or \
nnp2 == full1 or \
nnp2 == fullpath or \
nnp1 == full1
if regex_fullmatch:
if self.__show_map:
self._log.debug("SHOW MAP: Current node path: %s - NODE FOUND", nnp2)
result.append(child_node)
else:
regex_search = fullpath_pattern.fullmatch(nnp1, partial=True) or \
nnp2 in full1 or \
nnp1 in full1
if regex_search:
if self.__show_map:
self._log.debug("SHOW MAP: Current node path: %s - NODE FOUND", new_node_path)
if new_node_class == ua.NodeClass.Object:
if self.__show_map:
self._log.debug("SHOW MAP: Search in %s", new_node_path)
self.__search_node(child_node, fullpath,parentPath=parentPath, result=result)
elif new_node_class == ua.NodeClass.Variable:
self._log.debug("Found in %s", new_node_path)
result.append(child_node)
elif new_node_class == ua.NodeClass.Method and search_method:
self._log.debug("Found in %s", new_node_path)
result.append(child_node)
except CancelledError:
self._log.error("Request during search has been canceled by the OPC-UA server.")
except BrokenPipeError:
self._log.error("Broken Pipe. Connection lost.")
except OSError:
self._log.debug("Stop on scanning.")
except Exception as e:
self._log.exception(e)
这样就能够获取到节点了,同时还需要修改__search_nodes_and_subscribe中的代码,获取到父节点路径传递进去
self.__search_node(device_info["deviceNode"], information_path,parentPath=device_info["deviceNode"].parentPath,result=information_nodes)
这样就可以正常监听数据变化了