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