| 1 | // Copyright (C) 2019 The Qt Company Ltd. | 
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only | 
| 3 |  | 
| 4 | #include <private/opcuamethodnode_p.h> | 
| 5 | #include <private/opcuanodeidtype_p.h> | 
| 6 |  | 
| 7 | #include <QOpcUaNode> | 
| 8 | #include <QLoggingCategory> | 
| 9 |  | 
| 10 | QT_BEGIN_NAMESPACE | 
| 11 |  | 
| 12 | /*! | 
| 13 |     \qmltype MethodNode | 
| 14 |     \inqmlmodule QtOpcUa | 
| 15 |     \brief Calls a method on the server. | 
| 16 |     \since QtOpcUa 5.12 | 
| 17 |     \inherits Node | 
| 18 |  | 
| 19 |     This QML element supports calling method nodes on a server. | 
| 20 |     The target object node ID has to be specified by the \l objectNodeId property. | 
| 21 |  | 
| 22 |     \code | 
| 23 |     import QtOpcUa as QtOpcUa | 
| 24 |  | 
| 25 |     QtOpcUa.MethodNode { | 
| 26 |         nodeId : QtOpcUa.NodeId { | 
| 27 |             identifier: "s=Example.Method" | 
| 28 |             ns: "Example Namespace" | 
| 29 |         } | 
| 30 |         objectNodeId : QtOpcUa.NodeId { | 
| 31 |             identifier: "s=Example.Object" | 
| 32 |             ns: "Example Namespace" | 
| 33 |         } | 
| 34 |         connection: myConnection | 
| 35 |      } | 
| 36 |      \endcode | 
| 37 |  | 
| 38 |      The actual function call can be triggered by a signal. | 
| 39 |  | 
| 40 |      \code | 
| 41 |      Button { | 
| 42 |            text: "Start" | 
| 43 |            onClicked: myNode.callMethod | 
| 44 |      } | 
| 45 |      \endcode | 
| 46 |  | 
| 47 |      or by JavaScript | 
| 48 |  | 
| 49 |      \code | 
| 50 |           myNode.callMethod() | 
| 51 |      \endcode | 
| 52 | */ | 
| 53 |  | 
| 54 | /*! | 
| 55 |     \qmlmethod MethodNode::callMethod | 
| 56 |  | 
| 57 |     Calls the method on the connected server. | 
| 58 | */ | 
| 59 |  | 
| 60 | /*! | 
| 61 |     \qmlproperty OpcUaNode MethodNode::objectNodeId | 
| 62 |  | 
| 63 |     Determines the actual node on which the method is called. | 
| 64 |     It can be a relative or absolute node Id. | 
| 65 | */ | 
| 66 |  | 
| 67 | /*! | 
| 68 |     \qmlproperty Status MethodNode::resultStatus | 
| 69 |     \readonly | 
| 70 |  | 
| 71 |     Status of the last method call. This property has to be checked | 
| 72 |     to determine if the method call was successful. | 
| 73 |  | 
| 74 |     \sa Status | 
| 75 | */ | 
| 76 |  | 
| 77 | /*! | 
| 78 |     \qmlproperty list<MethodArgument> MethodNode::inputArguments | 
| 79 |  | 
| 80 |     Arguments to be used when calling the method on the server. | 
| 81 |  | 
| 82 |     This example shows how to call a method with two double arguments. | 
| 83 |     \code | 
| 84 |     QtOpcUa.MethodNode { | 
| 85 |         ... | 
| 86 |         inputArguments: [ | 
| 87 |             QtOpcUa.MethodArgument { | 
| 88 |                 value: 3 | 
| 89 |                 type: QtOpcUa.Constants.Double | 
| 90 |             }, | 
| 91 |             QtOpcUa.MethodArgument { | 
| 92 |                 value: 4 | 
| 93 |                 type: QtOpcUa.Constants.Double | 
| 94 |             } | 
| 95 |         ] | 
| 96 |     } | 
| 97 |     \endcode | 
| 98 |  | 
| 99 |     \sa callMethod | 
| 100 | */ | 
| 101 |  | 
| 102 | /*! | 
| 103 |     \qmlproperty list<var> MethodNode::outputArguments | 
| 104 |     \readonly | 
| 105 |  | 
| 106 |     Returns values from the method call. Depending on the output arguments, | 
| 107 |     this list may contain zero or more values. The \l resultStatus has to be checked | 
| 108 |     separately. In case the method call failed, the list will be empty. | 
| 109 |  | 
| 110 |     \code | 
| 111 |     if (node.status.isGood) { | 
| 112 |         // print two arguments | 
| 113 |         console.log("Number of return values:", node.outputArguments.length) | 
| 114 |         console.log("Return value #1:", node.outputArguments[0]) | 
| 115 |         console.log("Return value #2:", node.outputArguments[1]) | 
| 116 |     } | 
| 117 |     \endcode | 
| 118 |  | 
| 119 |     \sa callMethod, resultStatus | 
| 120 | */ | 
| 121 |  | 
| 122 | Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_QML) | 
| 123 |  | 
| 124 | OpcUaMethodNode::OpcUaMethodNode(QObject *parent): | 
| 125 |     OpcUaNode(parent) | 
| 126 | { | 
| 127 | } | 
| 128 |  | 
| 129 | OpcUaNodeIdType *OpcUaMethodNode::objectNodeId() const | 
| 130 | { | 
| 131 |     return m_objectNodeId; | 
| 132 | } | 
| 133 |  | 
| 134 | QQmlListProperty<OpcUaMethodArgument> OpcUaMethodNode::inputArguments() | 
| 135 | { | 
| 136 |     return QQmlListProperty<OpcUaMethodArgument>(this, | 
| 137 |         &m_inputArguments, | 
| 138 |         &OpcUaMethodNode::appendArgument, | 
| 139 |         &OpcUaMethodNode::argumentCount, | 
| 140 |         &OpcUaMethodNode::argument, | 
| 141 |         &OpcUaMethodNode::clearArguments); | 
| 142 | } | 
| 143 |  | 
| 144 | QVariantList OpcUaMethodNode::outputArguments() | 
| 145 | { | 
| 146 |     return m_outputArguments; | 
| 147 | } | 
| 148 |  | 
| 149 | void OpcUaMethodNode::setObjectNodeId(OpcUaNodeIdType *node) | 
| 150 | { | 
| 151 |     if (m_objectNodeId) | 
| 152 |         disconnect(receiver: m_objectNodeId); | 
| 153 |  | 
| 154 |     m_objectNodeId = node; | 
| 155 |     connect(sender: m_objectNodeId, signal: &OpcUaNodeIdType::nodeChanged, context: this, slot: &OpcUaMethodNode::handleObjectNodeIdChanged); | 
| 156 |     handleObjectNodeIdChanged(); | 
| 157 | } | 
| 158 |  | 
| 159 | void OpcUaMethodNode::callMethod() | 
| 160 | { | 
| 161 |     if (!m_objectNode) { | 
| 162 |         qCWarning(QT_OPCUA_PLUGINS_QML) << "No object node" ; | 
| 163 |         setStatus(status: Status::InvalidObjectNode); | 
| 164 |         return; | 
| 165 |     } | 
| 166 |     if (!m_objectNode->node()) { | 
| 167 |         qCWarning(QT_OPCUA_PLUGINS_QML) << "Invalid object node" ; | 
| 168 |         setStatus(status: Status::InvalidObjectNode); | 
| 169 |         return; | 
| 170 |     } | 
| 171 |     if (!m_node) { | 
| 172 |         qCWarning(QT_OPCUA_PLUGINS_QML) << "Invalid node Id" ; | 
| 173 |         setStatus(status: Status::InvalidNodeId); | 
| 174 |         return; | 
| 175 |     } | 
| 176 |  | 
| 177 |     QList<QOpcUa::TypedVariant> arguments; | 
| 178 |     for (const auto item : std::as_const(t&: m_inputArguments)) | 
| 179 |         arguments.push_back(t: QOpcUa::TypedVariant(item->value(), item->type())); | 
| 180 |     m_objectNode->node()->callMethod(methodNodeId: m_node->nodeId(), args: arguments); | 
| 181 | } | 
| 182 |  | 
| 183 | void OpcUaMethodNode::handleObjectNodeIdChanged() | 
| 184 | { | 
| 185 |     if (m_objectNode) | 
| 186 |         m_objectNode->deleteLater(); | 
| 187 |     m_objectNode = new OpcUaNode(this); | 
| 188 |     m_objectNode->setNodeId(m_objectNodeId); | 
| 189 |     connect(sender: m_objectNode, signal: &OpcUaNode::readyToUseChanged, context: this, slot: [this](){ | 
| 190 |         connect(sender: m_objectNode->node(), signal: &QOpcUaNode::methodCallFinished, context: this, slot: &OpcUaMethodNode::handleMethodCallFinished, type: Qt::UniqueConnection); | 
| 191 |         checkValidity(); | 
| 192 |     }); | 
| 193 |  | 
| 194 |     emit objectNodeIdChanged(); | 
| 195 | } | 
| 196 |  | 
| 197 | void OpcUaMethodNode::handleMethodCallFinished(QString methodNodeId, QVariant result, QOpcUa::UaStatusCode statusCode) | 
| 198 | { | 
| 199 |     Q_UNUSED(methodNodeId); | 
| 200 |     m_resultStatus = OpcUaStatus(statusCode); | 
| 201 |  | 
| 202 |     m_outputArguments.clear(); | 
| 203 |     if (result.canConvert<QVariantList>()) | 
| 204 |         m_outputArguments = result.value<QVariantList>(); | 
| 205 |     else | 
| 206 |         m_outputArguments.append(t: result); | 
| 207 |     emit resultStatusChanged(status: m_resultStatus); | 
| 208 |     emit outputArgumentsChanged(); | 
| 209 | } | 
| 210 |  | 
| 211 | void OpcUaMethodNode::setupNode(const QString &absolutePath) | 
| 212 | { | 
| 213 |     OpcUaNode::setupNode(absolutePath); | 
| 214 | } | 
| 215 |  | 
| 216 | bool OpcUaMethodNode::checkValidity() | 
| 217 | { | 
| 218 |     if (m_node->attribute(attribute: QOpcUa::NodeAttribute::NodeClass).value<QOpcUa::NodeClass>() != QOpcUa::NodeClass::Method) { | 
| 219 |         setStatus(status: Status::InvalidNodeType); | 
| 220 |         return false; | 
| 221 |     } | 
| 222 |     if (!m_objectNode || !m_objectNode->node()) { | 
| 223 |         setStatus(status: Status::InvalidObjectNode); | 
| 224 |         return false; | 
| 225 |     } | 
| 226 |  | 
| 227 |     const auto objectNodeClass = m_objectNode->node()->attribute(attribute: QOpcUa::NodeAttribute::NodeClass).value<QOpcUa::NodeClass>(); | 
| 228 |     if (objectNodeClass != QOpcUa::NodeClass::Object && objectNodeClass != QOpcUa::NodeClass::ObjectType) { | 
| 229 |         setStatus(status: Status::InvalidObjectNode, message: tr(s: "Object node is not of type `Object' or `ObjectType'" )); | 
| 230 |         return false; | 
| 231 |     } | 
| 232 |  | 
| 233 |     setStatus(status: Status::Valid); | 
| 234 |  | 
| 235 |     return true; | 
| 236 | } | 
| 237 |  | 
| 238 | OpcUaStatus OpcUaMethodNode::resultStatus() const | 
| 239 | { | 
| 240 |     return m_resultStatus; | 
| 241 | } | 
| 242 |  | 
| 243 | void OpcUaMethodNode::appendArgument(QQmlListProperty<OpcUaMethodArgument>* list, OpcUaMethodArgument* p) { | 
| 244 |     reinterpret_cast< QList<OpcUaMethodArgument*>* >(list->data)->append(t: p); | 
| 245 | } | 
| 246 |  | 
| 247 | void OpcUaMethodNode::clearArguments(QQmlListProperty<OpcUaMethodArgument>* list) { | 
| 248 |     reinterpret_cast< QList<OpcUaMethodArgument*>* >(list->data)->clear(); | 
| 249 | } | 
| 250 |  | 
| 251 | OpcUaMethodArgument* OpcUaMethodNode::argument(QQmlListProperty<OpcUaMethodArgument>* list, qsizetype i) { | 
| 252 |     return reinterpret_cast< QList<OpcUaMethodArgument*>* >(list->data)->at(i); | 
| 253 | } | 
| 254 |  | 
| 255 | qsizetype OpcUaMethodNode::argumentCount(QQmlListProperty<OpcUaMethodArgument>* list) { | 
| 256 |     return reinterpret_cast< QList<OpcUaMethodArgument*>* >(list->data)->size(); | 
| 257 | } | 
| 258 |  | 
| 259 |  | 
| 260 | QT_END_NAMESPACE | 
| 261 |  |