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 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
197void 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
211void OpcUaMethodNode::setupNode(const QString &absolutePath)
212{
213 OpcUaNode::setupNode(absolutePath);
214}
215
216bool 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
238OpcUaStatus OpcUaMethodNode::resultStatus() const
239{
240 return m_resultStatus;
241}
242
243void OpcUaMethodNode::appendArgument(QQmlListProperty<OpcUaMethodArgument>* list, OpcUaMethodArgument* p) {
244 reinterpret_cast< QList<OpcUaMethodArgument*>* >(list->data)->append(t: p);
245}
246
247void OpcUaMethodNode::clearArguments(QQmlListProperty<OpcUaMethodArgument>* list) {
248 reinterpret_cast< QList<OpcUaMethodArgument*>* >(list->data)->clear();
249}
250
251OpcUaMethodArgument* OpcUaMethodNode::argument(QQmlListProperty<OpcUaMethodArgument>* list, qsizetype i) {
252 return reinterpret_cast< QList<OpcUaMethodArgument*>* >(list->data)->at(i);
253}
254
255qsizetype OpcUaMethodNode::argumentCount(QQmlListProperty<OpcUaMethodArgument>* list) {
256 return reinterpret_cast< QList<OpcUaMethodArgument*>* >(list->data)->size();
257}
258
259
260QT_END_NAMESPACE
261

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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