c++ - 使用自定义数据类型输入参数调用方法时 open62541 客户端失败

标签 c++ c client opc-ua open62541

我正在使用 open62541 连接到 OPC/UA 服务器,并尝试调用该服务器上某个对象提供的方法。这些方法将自定义类型作为输入参数;例如,以下方法采用三个 bool 值的结构:

    <opc:Method SymbolicName="SetStatusMethodType" ModellingRule="Mandatory">
      <opc:InputArguments>
        <opc:Argument Name="Status" DataType="VisionStatusDataType" ValueRank="Scalar"/>
      </opc:InputArguments>
      <opc:OutputArguments />
    </opc:Method>

这里,VisionStatusDataType 是以下结构:

  <opc:DataType SymbolicName="VisionStatusDataType" BaseType="ua:Structure">
    <opc:ClassName>VisionStatus</opc:ClassName>
    <opc:Fields>
      <opc:Field Name="Camera" DataType="ua:Boolean" ValueRank="Scalar"/>
      <opc:Field Name="StrobeController" DataType="ua:Boolean" ValueRank="Scalar"/>
      <opc:Field Name="Server" DataType="ua:Boolean" ValueRank="Scalar"/>
    </opc:Fields>
  </opc:DataType>

现在,在调用该方法时,我将数据编码到 UA_ExtensionObject 中,并将其包装为 UA_Variant 以将其提供给 UA_Client_call。编码如下所示:

void encode(const QVariantList& vecqVar, size_t& nIdx, const DataType& dt, std::back_insert_iterator<std::vector<UAptr<UA_ByteString>>> itOut)
{
    if (dt.isSimple())
    {
        auto&& qVar = vecqVar.at(nIdx++);
        auto&& uaVar = convertToUaVar(qVar, dt.uaType());
        auto pOutBuf = create<UA_ByteString>();
        auto nStatus = UA_encodeBinary(uaVar.data, dt.uaType(), pOutBuf.get());
        statusCheck(nStatus);
        itOut = std::move(pOutBuf);
    }
    else
    {
        for (auto&& dtMember : dt.members())
            encode(vecqVar, nIdx, dtMember, itOut);
    }
}

UA_Variant ToUAVariant(const QVariant& qVar, const DataType& dt)
{
    if (dt.isSimple())
        return convertToUaVar(qVar, dt.uaType());
    else
    {
        std::vector<UAptr<UA_ByteString>> vecByteStr;
        auto&& qVarList = qVar.toList();
        size_t nIdx = 0UL;
        encode(qVarList, nIdx, dt, std::back_inserter(vecByteStr));

        auto pExtObj = UA_ExtensionObject_new();
        pExtObj->encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;
        auto nSizeAll = std::accumulate(vecByteStr.cbegin(), vecByteStr.cend(), 0ULL, [](size_t nSize, const UAptr<UA_ByteString>& pByteStr) {
            return nSize + pByteStr->length;
        });
        auto&& uaEncoded = pExtObj->content.encoded;
        uaEncoded.typeId = dt.uaType()->typeId;
        uaEncoded.body.length = nSizeAll;
        auto pData = uaEncoded.body.data = new UA_Byte[nSizeAll];
        nIdx = 0UL;
        for (auto&& pByteStr : vecByteStr)
        {
            memcpy_s(pData + nIdx, nSizeAll - nIdx, pByteStr->data, pByteStr->length);
            nIdx += pByteStr->length;
        }

        UA_Variant uaVar;
        UA_Variant_init(&uaVar);
        UA_Variant_setScalar(&uaVar, pExtObj, &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]);
        return uaVar;
    }
}

DataType 类是 UA_DataType 结构的包装器;原始的 open62541 类型可以通过 DataType::uaType() 访问。

现在,一旦 a 有了变体(包含扩展对象),方法调用如下所示:

    auto uavarInput = ToUAVariant(qvarArg, dtInput);
    UA_Variant* pvarOut;
    size_t nOutSize = 0UL;
    auto nStatus = UA_Client_call(m_pClient, objNode.nodeId(), m_uaNodeId, 1UL, &uavarInput, &nOutSize, &pvarOut);

状态为 2158690304,即根据 UA_StatusCode_nameBadInvalidArgument

方法参数真的有问题吗?我们是否应该发送 ExtensionObjects,或者变体应该包含什么数据类型? 服务器本身(使用 .NET OPC/UA 堆栈创建)是否可能未正确配置?

注意,这里的类型是自定义类型;也就是说,编码是通过将所有成员的字节表示形式存储在 UA_ByteString 中手动完成的(见上文) - 与我在读取变量或输出参数时所做的相反,效果很好。

最佳答案

问题出在编码对象的typeId上。对于服务器来说,为了理解接收到的数据,它需要知道编码的 NodeId,而不是类型本身的实际 NodeId。可以通过以下类型的 HasEncoding 引用(名为“默认二进制”)找到该编码:

        auto pRequest = create<UA_BrowseRequest>();
        auto pDescr = pRequest->nodesToBrowse = UA_BrowseDescription_new();
        pRequest->nodesToBrowseSize = 1UL;
        pDescr->nodeId = m_uaNodeId;
        pDescr->resultMask = UA_BROWSERESULTMASK_ALL;
        pDescr->browseDirection = UA_BROWSEDIRECTION_BOTH;
        pDescr->referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASENCODING);

        auto response = UA_Client_Service_browse(m_pClient, *pRequest);
        for (auto k = 0UL; k < response.resultsSize; ++k)
        {
            auto browseRes = response.results[k];
            for (auto n = 0UL; n < browseRes.referencesSize; ++n)
            {
                auto browseRef = browseRes.references[n];
                if (ToQString(browseRef.browseName.name).contains("Binary"))
                {
                    m_nodeBinaryEnc = browseRef.nodeId.nodeId;
                    break;
                }
            }
        }

获得该 NodeId 后,将其传递给 UA_ExtensionObject::content::encoded::typeId:

        auto pExtObj = UA_ExtensionObject_new();
        pExtObj->encoding = UA_EXTENSIONOBJECT_ENCODED_BYTESTRING;
        auto nSizeAll = std::accumulate(vecByteStr.cbegin(), vecByteStr.cend(), 0ULL, [](size_t nSize, const UAptr<UA_ByteString>& pByteStr) {
            return nSize + pByteStr->length;
        });
        auto&& uaEncoded = pExtObj->content.encoded;
        uaEncoded.typeId = dt.encoding();
        uaEncoded.body.length = nSizeAll;
        auto pData = uaEncoded.body.data = new UA_Byte[nSizeAll];
        nIdx = 0UL;
        for (auto&& pByteStr : vecByteStr)
        {
            memcpy_s(pData + nIdx, nSizeAll - nIdx, pByteStr->data, pByteStr->length);
            nIdx += pByteStr->length;
        }

关于c++ - 使用自定义数据类型输入参数调用方法时 open62541 客户端失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73976312/

相关文章:

c++ - Boost 的 TCP basic_resolver_query 构造函数的参数

c++ - 在 ubuntu 上启动 ITK/VTK 项目时出错

c - C 混淆中的结构

client - 为什么要使用 CSS3 按钮?

android - 无法链接 libSTLport_shared,即使它存在...?

c++ - 在 VSCode 中编译 C++ 给我一个 undefined reference

c++ - 有什么方法不需要在 Xcode 中使用文件的完整路径? [C/C++]

c - 下面的场景如何同步?

python - 尝试使用 RTM API 连接到 Slack 时出现 "Failed RTM connect"错误

ios - Telegram(最简单的)内联机器人在 iOS 上发送照片 2 次