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

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