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 | |
12 | QT_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 | |
67 | Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_QML) |
68 | |
69 | OpcUaValueNode::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 | |
76 | OpcUaValueNode::~OpcUaValueNode() |
77 | { |
78 | } |
79 | |
80 | void 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 | |
87 | template<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 | |
96 | void 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 | |
173 | void 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 | |
181 | bool 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 | |
195 | QVariant OpcUaValueNode::value() const |
196 | { |
197 | if (!m_connection || !m_node) |
198 | return QVariant(); |
199 | return m_node->attribute(attribute: QOpcUa::NodeAttribute::Value); |
200 | } |
201 | |
202 | QDateTime OpcUaValueNode::serverTimestamp() const |
203 | { |
204 | return getServerTimestamp(QOpcUa::NodeAttribute::Value); |
205 | } |
206 | |
207 | QDateTime OpcUaValueNode::sourceTimestamp() const |
208 | { |
209 | return getSourceTimestamp(QOpcUa::NodeAttribute::Value); |
210 | } |
211 | |
212 | bool OpcUaValueNode::monitored() const |
213 | { |
214 | if (!m_connection || !m_node) |
215 | return m_monitored; |
216 | |
217 | return m_monitoredState; |
218 | } |
219 | |
220 | void 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 | } |
239 | void OpcUaValueNode::setMonitored(bool monitored) |
240 | { |
241 | m_monitored = monitored; |
242 | updateSubscription(); |
243 | } |
244 | |
245 | double 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 | |
256 | OpcUaDataChangeFilter *OpcUaValueNode::filter() const |
257 | { |
258 | return m_filter; |
259 | } |
260 | |
261 | void 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 | |
279 | void 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 | |
289 | QOpcUa::Types OpcUaValueNode::valueType() const |
290 | { |
291 | return m_valueType; |
292 | } |
293 | |
294 | void OpcUaValueNode::setValueType(QOpcUa::Types valueType) |
295 | { |
296 | m_valueType = valueType; |
297 | } |
298 | |
299 | QT_END_NAMESPACE |
300 | |