You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用open62541读取OPC UA复杂节点(自定义类型)的用法咨询

关于open62541读取OPC UA复杂节点的问题

我有一个关于OPC UA“复杂”节点的问题:我的OPC UA服务器包含一些复杂节点,这里的“复杂”指节点拥有多个简单类型的子节点(例如某个节点包含三个整数子节点),我希望通过一次读取操作获取该复杂节点。我尝试编写了如下C++代码实现该功能:

int main(void) {
    typedef struct {
        UA_Int32 firstInteger;
        UA_Int32 secondInteger;
        UA_Double someDouble;
    } MyComplexType;

    UA_Client *client = UA_Client_new();
    UA_ClientConfig_setDefault(UA_Client_getConfig(client));

    UA_StatusCode status = UA_Client_connect(client, "opc.tcp://localhost:4840");
    if (status != UA_STATUSCODE_GOOD) {
        printf("Failed to connect\n");
        UA_Client_delete(client);
        return status;
    }

    // Define the NodeId of the complex type variable
    UA_NodeId nodeId = UA_NODEID_STRING(1, "ComplexVariable");

    // Read the value
    UA_Variant value;
    UA_Variant_init(&value);
    status = UA_Client_readValueAttribute(client, nodeId, &value);

    if (status == UA_STATUSCODE_GOOD && UA_Variant_hasType(&value, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT])) {
        UA_ExtensionObject *extObj = (UA_ExtensionObject *)value.data;
        if (extObj->encoding == UA_EXTENSIONOBJECT_DECODED && extObj->content.decoded.type == &MyComplexType_desc) {
            MyComplexType *complexData = (MyComplexType *)extObj->content.decoded.data;
            printf("Read Complex Data: %d, %d, %f\n", 
                   complexData->firstInteger, complexData->secondInteger, complexData->someDouble);
        } else {
            printf("Failed to decode complex data\n");
        }
    } else {
        printf("Failed to read complex node\n");
    }

    UA_Variant_clear(&value);
    UA_Client_disconnect(client);
    UA_Client_delete(client);

    return 0;
}

请问这是否是open62541库的正确用法?另外,我假设extObj本质上是UA_Variant数组,这个假设是否正确?


问题解答

1. 代码的正确性:核心思路方向对,但存在关键缺失

这段代码仅适用于服务器端是自定义结构化类型(Struct)的Variable节点,但有几个必须补充的关键步骤:

  • 缺失MyComplexType_desc的定义与注册:open62541需要明确的UA_DataType类型描述符识别自定义类型,你必须通过open62541的代码生成工具(如ua_generator)基于XML类型定义生成,或手动构造UA_DataType结构体,并调用UA_Client_addDataType将其注册到客户端的类型字典中。否则extObj->content.decoded.type == &MyComplexType_desc的判断永远无法成立。
  • 服务器端节点类型必须匹配:如果你的“复杂节点”是包含子节点的Object节点(仅父节点下挂三个变量子节点),这段代码完全无效——因为Object节点的Value属性是空的,无法通过读取父节点Value获取子节点数据。这种场景下需要用批量读取接口获取子节点。
  • 可补充严谨性检查:比如在转换value.data为UA_ExtensionObject*前,确认value.data非空;对extObj的有效性做额外校验。

2. 关于extObj本质是UA_Variant数组的假设:完全错误

UA_ExtensionObject是OPC UA标准中专门用于封装自定义类型、非标类型或扩展类型的容器,和UA_Variant数组没有任何关系:

  • 它有三种编码状态:
    • UA_EXTENSIONOBJECT_ENCODED:存储编码后的二进制数据+类型ID,需解码后才能使用;
    • UA_EXTENSIONOBJECT_DECODED:存储解码后的具体数据(如你的MyComplexType实例)+对应的UA_DataType描述符;
    • UA_EXTENSIONOBJECT_ENCODED_NO_BODY:仅包含类型ID,无实际数据。
  • UA_Variant是用于存储任意类型值的通用容器,而UA_ExtensionObject是针对扩展类型的专用封装结构,二者设计目的完全不同。

补充:两种场景的正确实现方式

场景A:服务器端是自定义Struct类型的Variable节点

  1. 生成并注册自定义类型的UA_DataType到客户端;
  2. 保持你代码的核心逻辑,确保服务器端的ComplexVariable确实是该Struct类型的变量;
  3. 解码时通过匹配UA_DataType获取结构化数据。

场景B:服务器端是包含子节点的Object节点

使用批量读取接口一次性获取所有子节点的值,示例代码片段:

// 定义三个子节点的NodeId
UA_NodeId nodeIds[3] = {
    UA_NODEID_STRING(1, "ComplexVariable/FirstInteger"),
    UA_NODEID_STRING(1, "ComplexVariable/SecondInteger"),
    UA_NODEID_STRING(1, "ComplexVariable/SomeDouble")
};

UA_Variant values[3];
for(size_t i=0; i<3; i++)
    UA_Variant_init(&values[i]);

// 批量读取多个节点的Value属性
UA_StatusCode status = UA_Client_readValues(client, nodeIds, 3, values);

if(status == UA_STATUSCODE_GOOD) {
    // 解析每个子节点的值
    UA_Int32 first = *(UA_Int32*)values[0].data;
    UA_Int32 second = *(UA_Int32*)values[1].data;
    UA_Double dbl = *(UA_Double*)values[2].data;
    printf("Read Data: %d, %d, %f\n", first, second, dbl);
}

// 清理资源
for(size_t i=0; i<3; i++)
    UA_Variant_clear(&values[i]);

内容的提问来源于stack exchange,提问作者pepsilon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 06:44:52