Opc-UA通信协议,客户端如何理解可用的服务器节点?

时间:2017-12-19 14:38:39

标签: sockets opc opc-ua node-opcua

当我开始使用opc ua时,我只是想知道在opc ua的通信层的引擎下发生了什么。

让我们举一个非常简单的服务器实现的例子,它在地址空间中有3个节点。这些节点提供可由opc-UA客户端写入和读取的数据。

通过阅读open62541附带的部分代码,我了解到通过TCP进行通信。这意味着服务器启动客户端可以连接到的套接字并启用客户端 在节点上执行各种操作。

我的问题是,客户如何了解可用的服务器节点?我知道它浏览地址空间,但它究竟在哪里浏览以查找可用节点?  opc-UA使用什么暴露机制向客户端提供可用节点?服务器是否写入可用信息&某个xml文件或其他任何地方的节点,因此当客户端连接时,它会尝试读取文件的内容以了解addressSpace结构吗?

open62541的示例服务器实现

#include <stdio.h>
#include <open62541.h>
#include <signal.h>

static void
addVariable(UA_Server *server) {
    /* Define the attribute of the myInteger variable node */
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    UA_Int32 myInteger = 43;
    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
    attr.description = UA_LOCALIZEDTEXT("en-US", "the answer");
    attr.displayName = UA_LOCALIZEDTEXT("en-US", "the answer");
    attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;

    /* Add the variable node to the information model */
    UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer");
    UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "the answer");
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
        parentReferenceNodeId, myIntegerName,
        UA_NODEID_NULL, attr, NULL, NULL);
}

static void
addThirdVariable(UA_Server *server) {
    /* Define the attribute of the myInteger variable node */
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    UA_String myInteger = UA_STRING("My name is variable 3"); // variable name
    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_STRING]);
    attr.description = UA_LOCALIZEDTEXT("en-US", "the answer");
    attr.displayName = UA_LOCALIZEDTEXT("en-US", "the answer");
    attr.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;

    /* Add the variable node to the information model */
    UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "third.variable");
    UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "third varaible");
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
        parentReferenceNodeId, myIntegerName,
        UA_NODEID_NULL, attr, NULL, NULL);
}

void addSecondVariable(UA_Server * server) {
    //variable attributes
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    UA_String machine_name = UA_STRING("My name is a machine"); // variable name
    UA_Variant_setScalar(&attr.value, &machine_name, &UA_TYPES[UA_TYPES_STRING]);

    attr.description = UA_LOCALIZEDTEXT("en-US", "machine name");
    attr.displayName = UA_LOCALIZEDTEXT("en-US", "machine name");
    attr.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
    //setting access level not important

    //add the variable to the information model
    UA_NodeId myStringNodeID = UA_NODEID_STRING(1, "the.machine");
    UA_QualifiedName myStringName = UA_QUALIFIEDNAME(1, "the machine");
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);

    UA_Server_addVariableNode(server, myStringNodeID, parentNodeId,
        parentReferenceNodeId, myStringName,
        UA_NODEID_NULL, attr, NULL, NULL);



}

UA_Boolean running = true;
static void stopHandler(int sign) {
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
    running = false;
}

int main(void) {
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

    UA_ServerConfig *config = UA_ServerConfig_new_default();
    UA_Server *server = UA_Server_new(config);
    addVariable(server);
    addSecondVariable(server);
    addThirdVariable(server);
    UA_StatusCode retval = UA_Server_run(server, &running);


    UA_Server_delete(server);
    UA_ServerConfig_delete(config);
    return (int)retval;
}

3 个答案:

答案 0 :(得分:0)

客户端可以使用Browse服务发现服务器中的节点。

客户端可以开始浏览的每个服务器中都有一些预定义的节点。通常,它可以是Root文件夹或Objects文件夹。

答案 1 :(得分:0)

节点由 NodeId 类型的结构标识。

如果您对OPC UA客户端进行编程,则必须添加在节点树中导航的功能,以便用户可以选择他想要读取或写入的 NodeId(s)他们的属性,其中包括价值。

要浏览树,您需要使用浏览服务,以阅读读取服务所需的属性。 Browse返回给定节点的子节点。

但是要使用这些服务,首先必须创建一个会话,您必须首先调用GetEndpoints,OpenSecureChannel,CreateSession,ActivateSession .... services

答案 2 :(得分:0)

有两种发现节点的方法。

首先,您应该意识到AddressSpace不是树,而是图。图的节点是OPC UA Node,图的边缘是OPC UA Reference

Node的名称是NodeId,这是一个限定名称。名称可以是整数(i=),字符串(s=)或不透明的对象(o=)。限定符在服务器的名称空间表中指定一个Namespace

关于名称空间,有两个保留的名称空间索引:

  • 0,它指定OPC UA命名空间
  • 1,它指定了服务器本身(恕我直言,每个服务器应该有一个不同的主机)

OPC UA Foundation是OPC UA名称空间的母版,并在0名称空间中定义了一大堆标准节点。特别是,节点ServerObjectsTypes0命名空间中用众所周知的整数名称定义。我不会谈论Attribute的{​​{1}},但是鉴于NodeNodeReferenceNamespace的概念, OPC UA Standard通过引导程序来完成任务。 Attribute命名空间定义了服务器节点的基本结构,并且所有定义的节点都有众所周知的0。我之所以说“引导程序”,是因为NodeId位于Server节点下,该节点将名称空间索引与相应的名称空间URN相关联。 (包括标准化索引NamespaceTable0),表元素可以像其他任何节点一样为1

简而言之,要开始回答您的问题,访问服务器内特定节点的最直接方法是了解其Read

现在,如果没有列表,如何知道服务器上存在哪些节点?嗯,该操作被称为NodeId,它有两种形式:跟随Browsing或通过Reference传送。

关于以下参考文献,请记住我说过BrowsePath是图。好吧,AddressSpaceNode指向其他Node。给定特定的Reference(例如,知名的NodeId或根,在名称空间NodeId中也具有知名的NodeId),您可以查询引用0中的Node中将指定其他Node,并按照您感兴趣的路径从NodeNode,直到找到所需的内容。这意味着客户端和服务器之间需要进行大量的交换,并且说实话,这很少值得您麻烦。

关于通过BrowsePath进行传送,服务器实现了一项名为TranslateBrowsePath的服务,其中给定了开始NodeId和浏览路径,服务器会为您提供节点列表匹配查询。 (即,从指定的起点Node到与浏览路径匹配的Reference路径均可到达)浏览路径语言非常丰富,您可以使用它进行相当复杂的查询。