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
10QT_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
122Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_QML)
123
124OpcUaMethodNode::OpcUaMethodNode(QObject *parent):
125 OpcUaNode(parent)
126{
127}
128
129OpcUaNodeIdType *OpcUaMethodNode::objectNodeId() const
130{
131 return m_objectNodeId;
132}
133
134QQmlListProperty<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
144QVariantList OpcUaMethodNode::outputArguments()
145{
146 return m_outputArguments;
147}
148
149void 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
159void 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
183void OpcUaMethodNode::handleObjectNodeIdChanged()
184{
185 m_objectNode->deleteLater();
186 m_objectNode = new OpcUaNode(this);
187 m_objectNode->setNodeId(m_objectNodeId);
188 connect(sender: m_objectNode, signal: &OpcUaNode::readyToUseChanged, context: this, slot: [this](){
189 connect(sender: m_objectNode->node(), signal: &QOpcUaNode::methodCallFinished, context: this, slot: &OpcUaMethodNode::handleMethodCallFinished, type: Qt::UniqueConnection);
190 checkValidity();
191 });
192
193 emit objectNodeIdChanged();
194}
195
196void OpcUaMethodNode::handleMethodCallFinished(QString methodNodeId, QVariant result, QOpcUa::UaStatusCode statusCode)
197{
198 Q_UNUSED(methodNodeId);
199 m_resultStatus = OpcUaStatus(statusCode);
200
201 m_outputArguments.clear();
202 if (result.canConvert<QVariantList>())
203 m_outputArguments = result.value<QVariantList>();
204 else
205 m_outputArguments.append(t: result);
206 emit resultStatusChanged(status: m_resultStatus);
207 emit outputArgumentsChanged();
208}
209
210void OpcUaMethodNode::setupNode(const QString &absolutePath)
211{
212 OpcUaNode::setupNode(absolutePath);
213}
214
215bool OpcUaMethodNode::checkValidity()
216{
217 if (m_node->attribute(attribute: QOpcUa::NodeAttribute::NodeClass).value<QOpcUa::NodeClass>() != QOpcUa::NodeClass::Method) {
218 setStatus(status: Status::InvalidNodeType);
219 return false;
220 }
221 if (!m_objectNode || !m_objectNode->node()) {
222 setStatus(status: Status::InvalidObjectNode);
223 return false;
224 }
225
226 const auto objectNodeClass = m_objectNode->node()->attribute(attribute: QOpcUa::NodeAttribute::NodeClass).value<QOpcUa::NodeClass>();
227 if (objectNodeClass != QOpcUa::NodeClass::Object && objectNodeClass != QOpcUa::NodeClass::ObjectType) {
228 setStatus(status: Status::InvalidObjectNode, message: tr(s: "Object node is not of type `Object' or `ObjectType'"));
229 return false;
230 }
231
232 setStatus(status: Status::Valid);
233
234 return true;
235}
236
237OpcUaStatus OpcUaMethodNode::resultStatus() const
238{
239 return m_resultStatus;
240}
241
242void OpcUaMethodNode::appendArgument(QQmlListProperty<OpcUaMethodArgument>* list, OpcUaMethodArgument* p) {
243 reinterpret_cast< QList<OpcUaMethodArgument*>* >(list->data)->append(t: p);
244}
245
246void OpcUaMethodNode::clearArguments(QQmlListProperty<OpcUaMethodArgument>* list) {
247 reinterpret_cast< QList<OpcUaMethodArgument*>* >(list->data)->clear();
248}
249
250OpcUaMethodArgument* OpcUaMethodNode::argument(QQmlListProperty<OpcUaMethodArgument>* list, qsizetype i) {
251 return reinterpret_cast< QList<OpcUaMethodArgument*>* >(list->data)->at(i);
252}
253
254qsizetype OpcUaMethodNode::argumentCount(QQmlListProperty<OpcUaMethodArgument>* list) {
255 return reinterpret_cast< QList<OpcUaMethodArgument*>* >(list->data)->size();
256}
257
258
259QT_END_NAMESPACE
260

source code of qtopcua/src/declarative_opcua/opcuamethodnode.cpp