Протокол связи Opc-UA, как клиент понимает доступные серверные узлы?

Поскольку я начинаю с opc ua, я просто хотел знать, что происходит под капотом коммуникационного уровня opc ua.

Давайте рассмотрим пример очень простой реализации сервера с 3 узлами в адресном пространстве. Эти узлы представляют данные, которые могут быть записаны и прочитаны клиентом opc-UA.

Прочитав часть кода, поставляемого с open62541, я узнал, что связь происходит через TCP. Это означает, что сервер инициирует сокет, к которому может подключиться клиент, и позволяет клиенту выполнять различные операции на узлах.

У меня вопрос: как клиент узнает о доступных узлах сервера? Я знаю, что он просматривает адресное пространство, но где именно он просматривает доступные узлы? какой механизм раскрытия использует opc-UA для представления доступных узлов клиенту? Записывает ли сервер доступную информацию и узлы в какой-либо XML-файл или где-нибудь еще, и, следовательно, когда клиенты подключаются, он пытается прочитать содержимое файла, чтобы понять структуру адресного пространства?

Пример реализации сервера для 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;
}

person salimsaid    schedule 19.12.2017    source источник
comment
Клиент OPC UA должен использовать сервисы FindServers и GetEndpoints. Я порекомендую вам сначала получить некоторую информацию об OPC UA. Здесь есть список книг по OPC UA: opcfoundation.org/resources/books   -  person Camille G.    schedule 19.12.2017


Ответы (3)


Есть несколько способов обнаружить узлы.

Прежде всего, вы должны знать, что AddressSpace - это не дерево, а граф. Узлами графа являются OPC UA Node, а ребрами графа являются OPC UA Reference.

Имя Node - это NodeId, которое является квалифицированным именем. Имя может быть целым числом (i=), строкой (s=) или непрозрачным объектом (o=). Квалификатор обозначает Namespace в таблице пространства имен сервера.

Что касается пространств имен, есть два зарезервированных индекса пространства имен:

  • 0, обозначающий пространство имен OPC UA.
  • 1, который обозначал сам сервер (IMHO, у вас должно быть по одному на каждый сервер)

OPC UA Foundation является хозяином пространства имен OPC UA и определяет целый набор стандартных узлов в пространстве имен 0. В частности, узлы Server, Objects и Types определены в пространстве имен 0 с хорошо известными целочисленными именами. Я не буду говорить о Attribute у Node, но, учитывая концепции Node, Reference, Namespace и Attribute, стандарт OPC UA берет себя за бутстрапы. Пространство имен 0 определяет базовую структуру узлов сервера, и все определенные узлы имеют хорошо известные NodeId. Я сказал «начальная загрузка», потому что, в частности, под узлом Server находится узел NamespaceTable, который связывает индексы пространства имен с соответствующими URN пространства имен. (включая стандартизованные индексы 0 и 1) Элементы таблицы могут быть Read, как и любой другой узел.

Короче говоря, чтобы начать отвечать на ваш вопрос, самый прямой способ получить доступ к определенному узлу на сервере - это узнать его NodeId.

Теперь, как узнать, какие узлы присутствуют на сервере, если у вас нет списка? Что ж, операция известна как Browsing, и у нее есть две разновидности: следование за References или телепортация через BrowsePaths.

Что касается следующих ссылок, помните, я сказал, что AddressSpace - это график. Что ж, Node указывает на другие от Node до Reference. Учитывая конкретный NodeId (например, хорошо известный NodeId или корень, который также имеет хорошо известный NodeId в пространстве имен 0), вы можете запросить ссылки Node, которые будут обозначать другие Node, и следовать по следу, который имеет интерес для вас от Node до Node, пока вы не найдете то, что ищете. Это подразумевает множество обменов между клиентом и сервером, и, честно говоря, редко стоит хлопот.

Что касается телепортации через BrowsePaths, существует служба под названием TranslateBrowsePath, реализуемая сервером, где, учитывая начало NodeId и путь просмотра, сервер выдает вам список узлов, соответствующих запросу. (т.е. достижимый из назначенного начала Node по пути Reference, совпадающему с путем просмотра). Язык пути просмотра очень богат, и с его помощью вы можете делать довольно сложные запросы.

person Laurent LA RIZZA    schedule 02.01.2019

Клиент может обнаруживать узлы на сервере с помощью службы обзора.

На каждом сервере есть несколько предопределенных узлов, с которых клиент может начать просмотр. Обычно это либо корневая папка, либо папка объектов.

person Kevin Herron    schedule 19.12.2017
comment
Спасибо за замечание. - person salimsaid; 20.12.2017

Узлы идентифицируются структурой типа NodeId.

Если вы запрограммируете клиент OPC UA, вам нужно будет добавить функцию навигации по дереву узлов, чтобы пользователь мог выбрать NodeId (s), которые он хочет прочитать или записать их атрибуты, среди них значение .

Для навигации по дереву вам необходимо использовать службу Обзор, для чтения атрибутов вам нужна служба Чтение. Обзор возвращает дочерние элементы данного узла.

Но чтобы использовать эти службы, вам сначала нужно создать сеанс, для которого вы должны сначала вызвать службы GetEndpoints, OpenSecureChannel, CreateSession, ActivateSession ....

person Lluis Felisart    schedule 19.12.2017
comment
Спасибо @ Tonteria24, мне пришлось перехватывать пакеты, чтобы выяснить последовательность вызовов от клиента к серверу, как в GetEndpoints, openSecureChannel ..... просмотр / чтение. - person salimsaid; 20.12.2017
comment
Я не думаю, что вам хватит обнюхивать пакеты ... это немного / довольно сложно, но это хорошо задокументировано. Вы можете бесплатно скачать спецификации с сайта OPC Foundation. Что касается услуг, части 4 и 6 были бы самыми важными. - person Lluis Felisart; 20.12.2017