1// Copyright (C) 2018 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/opcuavaluenode_p.h>
5#include <private/opcuaconnection_p.h>
6#include <private/opcuanodeid_p.h>
7#include <private/opcuaattributevalue_p.h>
8
9#include <QLoggingCategory>
10#include <QMetaEnum>
11
12QT_BEGIN_NAMESPACE
13
14/*!
15 \qmltype ValueNode
16 \inqmlmodule QtOpcUa
17 \brief Represents a value node from a server.
18 \inherits Node
19 \since QtOpcUa 5.12
20
21 \code
22 import QtOpcUa as QtOpcUa
23
24 QtOpcUa.ValueNode {
25 nodeId: QtOpcUa.NodeId {
26 ns: "Test Namespace"
27 identifier: "s=TestName"
28 }
29 connection: myConnection
30 }
31 \endcode
32
33 A subscription will be created on the server in order to track value changes on the server.
34
35 \sa NodeId, Connection, Node
36*/
37
38/*!
39 \qmlproperty variant ValueNode::value
40
41 Value of this node.
42 Reading and writing this property will access the node on the server.
43*/
44
45/*!
46 \qmlproperty variant ValueNode::valueType
47
48 Type type of this node.
49 The initial value will be \l {QOpcUa::Undefined}{QtOpcUa.Constants.Undefined} and be fetched from the server when the first connection is established.
50 Any value will be written to the server as the specified type.
51*/
52
53/*!
54 \qmlproperty Date ValueNode::sourceTimestamp
55 \readonly
56
57 Source timestamp of the value attribute.
58*/
59
60/*!
61 \qmlproperty Date ValueNode::serverTimestamp
62 \readonly
63
64 Server timestamp of the value attribute.
65*/
66
67Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_QML)
68
69OpcUaValueNode::OpcUaValueNode(QObject *parent):
70 OpcUaNode(parent)
71{
72 connect(sender: m_attributeCache.attribute(attribute: QOpcUa::NodeAttribute::Value), signal: &OpcUaAttributeValue::changed, context: this, slot: &OpcUaValueNode::valueChanged);
73 connect(sender: this, signal: &OpcUaValueNode::filterChanged, context: this, slot: &OpcUaValueNode::updateFilters);
74}
75
76OpcUaValueNode::~OpcUaValueNode()
77{
78}
79
80void OpcUaValueNode::setValue(const QVariant &value)
81{
82 if (!m_connection || !m_node)
83 return;
84 m_node->writeAttribute(attribute: QOpcUa::NodeAttribute::Value, value, type: m_valueType);
85}
86
87template<typename T> QString enumToString(T value) {
88 const auto metaEnum = QMetaEnum::fromType<T>();
89 const auto key = metaEnum.valueToKey(static_cast<int>(value));
90 if (key)
91 return QString::fromLatin1(key);
92 else
93 return QString();
94}
95
96void OpcUaValueNode::setupNode(const QString &absolutePath)
97{
98 // Additionally read the value attribute
99 setAttributesToRead(attributesToRead()
100 | QOpcUa::NodeAttribute::Value
101 | QOpcUa::NodeAttribute::DataType);
102
103 OpcUaNode::setupNode(absolutePath);
104 if (!m_node)
105 return;
106
107 connect(sender: m_node, signal: &QOpcUaNode::attributeWritten, context: this, slot: [this](QOpcUa::NodeAttribute attribute, QOpcUa::UaStatusCode statusCode) {
108 if (statusCode != QOpcUa::Good) {
109 QString msg = QStringLiteral("Failed to write attribute ")
110 + enumToString(value: attribute)
111 + QStringLiteral(": ")
112 + enumToString(value: statusCode);
113 setStatus(status: Status::FailedToWriteAttribute, message: msg);
114 qCWarning(QT_OPCUA_PLUGINS_QML) << msg;
115 }
116 });
117
118
119 connect(sender: m_node, signal: &QOpcUaNode::attributeUpdated, context: this, slot: [this](QOpcUa::NodeAttribute attr, QVariant value) {
120 if (attr == QOpcUa::NodeAttribute::DataType && m_valueType == QOpcUa::Types::Undefined) {
121 const auto valueType = QOpcUa::opcUaDataTypeToQOpcUaType(type: value.toString());
122 m_valueType = valueType;
123 }
124 });
125
126 connect(sender: m_node, signal: &QOpcUaNode::enableMonitoringFinished, context: this, slot: [this](QOpcUa::NodeAttribute attr, QOpcUa::UaStatusCode statusCode){
127 if (attr != QOpcUa::NodeAttribute::Value)
128 return;
129 if (statusCode == QOpcUa::Good) {
130 m_monitoredState = true;
131 emit monitoredChanged(monitored: m_monitoredState);
132 qCDebug(QT_OPCUA_PLUGINS_QML) << "Monitoring was enabled for node" << resolvedNode().fullNodeId();
133 updateFilters();
134 } else {
135 qCWarning(QT_OPCUA_PLUGINS_QML) << "Failed to enable monitoring for node" << resolvedNode().fullNodeId();
136 setStatus(status: Status::FailedToSetupMonitoring);
137 }
138 });
139 connect(sender: m_node, signal: &QOpcUaNode::disableMonitoringFinished, context: this, slot: [this](QOpcUa::NodeAttribute attr, QOpcUa::UaStatusCode statusCode){
140 if (attr != QOpcUa::NodeAttribute::Value)
141 return;
142 // The monitoring is gone in this case, regardless of the status code
143 if (statusCode == QOpcUa::Good ||
144 node()->monitoringStatus(attr: QOpcUa::NodeAttribute::Value).statusCode()== QOpcUa::UaStatusCode::BadNoEntryExists) {
145 m_monitoredState = false;
146 emit monitoredChanged(monitored: m_monitoredState);
147 qCDebug(QT_OPCUA_PLUGINS_QML) << "Monitoring was disabled for node "<< resolvedNode().fullNodeId();
148 } else {
149 qCWarning(QT_OPCUA_PLUGINS_QML) << "Failed to disable monitoring for node "<< resolvedNode().fullNodeId();
150 setStatus(status: Status::FailedToDisableMonitoring);
151 }
152 });
153 connect(sender: m_node, signal: &QOpcUaNode::monitoringStatusChanged, context: this, slot: [this](QOpcUa::NodeAttribute attr, QOpcUaMonitoringParameters::Parameters items,
154 QOpcUa::UaStatusCode statusCode) {
155 if (attr != QOpcUa::NodeAttribute::Value && attr != QOpcUa::NodeAttribute::EventNotifier)
156 return;
157 if (statusCode != QOpcUa::Good) {
158 setStatus(status: Status::FailedToModifyMonitoring);
159 qCWarning(QT_OPCUA_PLUGINS_QML) << "Failed to modify monitoring";
160 } else {
161 if (items & QOpcUaMonitoringParameters::Parameter::PublishingInterval) {
162 if (m_publishingInterval != m_node->monitoringStatus(attr: QOpcUa::NodeAttribute::Value).publishingInterval()) {
163 m_publishingInterval = m_node->monitoringStatus(attr: QOpcUa::NodeAttribute::Value).publishingInterval();
164 emit publishingIntervalChanged(publishingInterval: m_publishingInterval);
165 }
166 }
167 }
168 });
169
170 updateSubscription();
171}
172
173void OpcUaValueNode::updateFilters() const
174{
175 if (!m_connection || !m_node || !m_filter || !m_monitoredState)
176 return;
177
178 m_node->modifyDataChangeFilter(attr: QOpcUa::NodeAttribute::Value, filter: m_filter->filter());
179}
180
181bool OpcUaValueNode::checkValidity()
182{
183 if (!m_connection || !m_node)
184 return false;
185
186 if (m_node->attribute(attribute: QOpcUa::NodeAttribute::NodeClass).value<QOpcUa::NodeClass>() != QOpcUa::NodeClass::Variable) {
187 setStatus(status: Status::InvalidNodeType);
188 return false;
189 }
190
191 setStatus(status: Status::Valid);
192 return true;
193}
194
195QVariant OpcUaValueNode::value() const
196{
197 if (!m_connection || !m_node)
198 return QVariant();
199 return m_node->attribute(attribute: QOpcUa::NodeAttribute::Value);
200}
201
202QDateTime OpcUaValueNode::serverTimestamp() const
203{
204 return getServerTimestamp(QOpcUa::NodeAttribute::Value);
205}
206
207QDateTime OpcUaValueNode::sourceTimestamp() const
208{
209 return getSourceTimestamp(QOpcUa::NodeAttribute::Value);
210}
211
212bool OpcUaValueNode::monitored() const
213{
214 if (!m_connection || !m_node)
215 return m_monitored;
216
217 return m_monitoredState;
218}
219
220void OpcUaValueNode::updateSubscription()
221{
222 if (!m_connection || !m_node)
223 return;
224
225 QOpcUaMonitoringParameters parameters;
226 parameters.setPublishingInterval(m_publishingInterval);
227
228 if (m_filter)
229 parameters.setFilter(m_filter->filter());
230
231 if (m_monitoredState != m_monitored) {
232 if (m_monitored) {
233 m_node->enableMonitoring(attr: QOpcUa::NodeAttribute::Value, settings: parameters);
234 } else {
235 m_node->disableMonitoring(attr: QOpcUa::NodeAttribute::Value);
236 }
237 }
238}
239void OpcUaValueNode::setMonitored(bool monitored)
240{
241 m_monitored = monitored;
242 updateSubscription();
243}
244
245double OpcUaValueNode::publishingInterval() const
246{
247 if (!m_connection || !m_node)
248 return 0.0;
249
250 auto monitoringStatus = node()->monitoringStatus(attr: QOpcUa::NodeAttribute::Value);
251 if (monitoringStatus.statusCode() == QOpcUa::BadNoEntryExists)
252 return 0.0;
253 return monitoringStatus.publishingInterval();
254}
255
256OpcUaDataChangeFilter *OpcUaValueNode::filter() const
257{
258 return m_filter;
259}
260
261void OpcUaValueNode::setFilter(OpcUaDataChangeFilter *filter)
262{
263 bool changed = false;
264
265 if (m_filter) {
266 disconnect(sender: m_filter, signal: &OpcUaDataChangeFilter::filterChanged, receiver: this, slot: &OpcUaValueNode::updateFilters);
267 changed = !(*m_filter == *filter);
268 } else {
269 changed = true;
270 }
271
272 m_filter = filter;
273 connect(sender: m_filter, signal: &OpcUaDataChangeFilter::filterChanged, context: this, slot: &OpcUaValueNode::updateFilters);
274
275 if (changed)
276 emit filterChanged();
277}
278
279void OpcUaValueNode::setPublishingInterval(double publishingInterval)
280{
281 if (!m_connection || !m_node)
282 return;
283 if (qFuzzyCompare(p1: m_publishingInterval, p2: publishingInterval))
284 return;
285
286 m_node->modifyMonitoring(attr: QOpcUa::NodeAttribute::Value, item: QOpcUaMonitoringParameters::Parameter::PublishingInterval, value: publishingInterval);
287}
288
289QOpcUa::Types OpcUaValueNode::valueType() const
290{
291 return m_valueType;
292}
293
294void OpcUaValueNode::setValueType(QOpcUa::Types valueType)
295{
296 m_valueType = valueType;
297}
298
299QT_END_NAMESPACE
300

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