1 | // Copyright (C) 2017 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 "qopen62541.h" |
5 | #include "qopen62541utils.h" |
6 | #include "qopen62541valueconverter.h" |
7 | |
8 | #include "qopcuadatavalue.h" |
9 | #include "qopcuamultidimensionalarray.h" |
10 | |
11 | #include <QtCore/qdatetime.h> |
12 | #include <QtCore/qloggingcategory.h> |
13 | #include <QtCore/qtimezone.h> |
14 | #include <QtCore/quuid.h> |
15 | |
16 | #include <cstring> |
17 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_OPEN62541) |
21 | |
22 | using namespace QOpcUa::NodeIds; |
23 | |
24 | namespace QOpen62541ValueConverter { |
25 | |
26 | UA_Variant toOpen62541Variant(const QVariant &value, QOpcUa::Types type) |
27 | { |
28 | UA_Variant open62541value; |
29 | UA_Variant_init(p: &open62541value); |
30 | |
31 | if (value.canConvert<QOpcUaMultiDimensionalArray>()) { |
32 | QOpcUaMultiDimensionalArray data = value.value<QOpcUaMultiDimensionalArray>(); |
33 | UA_Variant result = toOpen62541Variant(value: data.valueArray(), type); |
34 | |
35 | const auto &arrayDimensions = data.arrayDimensions(); |
36 | |
37 | if (!arrayDimensions.isEmpty()) { |
38 | // Ensure that the array dimensions size is < UINT32_MAX |
39 | if (static_cast<quint64>(arrayDimensions.size()) > (std::numeric_limits<quint32>::max)()) |
40 | return open62541value; |
41 | result.arrayDimensionsSize = arrayDimensions.size(); |
42 | result.arrayDimensions = static_cast<UA_UInt32 *>(UA_Array_new(size: result.arrayDimensionsSize, type: &UA_TYPES[UA_TYPES_UINT32])); |
43 | std::copy(first: arrayDimensions.constBegin(), last: arrayDimensions.constEnd(), result: result.arrayDimensions); |
44 | } |
45 | return result; |
46 | } |
47 | |
48 | if (value.metaType().id() == QMetaType::QVariantList && value.toList().size() == 0) |
49 | return open62541value; |
50 | |
51 | QVariant temp = (value.metaType().id() == QMetaType::QVariantList) ? value.toList().at(i: 0) : value; |
52 | QOpcUa::Types valueType = type == QOpcUa::Undefined ? |
53 | QOpcUa::metaTypeToQOpcUaType(type: static_cast<QMetaType::Type>(temp.metaType().id())) : type; |
54 | |
55 | const UA_DataType *dt = toDataType(valueType); |
56 | |
57 | switch (valueType) { |
58 | case QOpcUa::Boolean: |
59 | return arrayFromQVariant<UA_Boolean, bool>(var: value, type: dt); |
60 | case QOpcUa::SByte: |
61 | return arrayFromQVariant<UA_SByte, char>(var: value, type: dt); |
62 | case QOpcUa::Byte: |
63 | return arrayFromQVariant<UA_Byte, uchar>(var: value, type: dt); |
64 | case QOpcUa::Int16: |
65 | return arrayFromQVariant<UA_Int16, qint16>(var: value, type: dt); |
66 | case QOpcUa::UInt16: |
67 | return arrayFromQVariant<UA_UInt16, quint16>(var: value, type: dt); |
68 | case QOpcUa::Int32: |
69 | return arrayFromQVariant<UA_Int32, qint32>(var: value, type: dt); |
70 | case QOpcUa::UInt32: |
71 | return arrayFromQVariant<UA_UInt32, quint32>(var: value, type: dt); |
72 | case QOpcUa::Int64: |
73 | return arrayFromQVariant<UA_Int64, int64_t>(var: value, type: dt); |
74 | case QOpcUa::UInt64: |
75 | return arrayFromQVariant<UA_UInt64, uint64_t>(var: value, type: dt); |
76 | case QOpcUa::Float: |
77 | return arrayFromQVariant<UA_Float, float>(var: value, type: dt); |
78 | case QOpcUa::Double: |
79 | return arrayFromQVariant<UA_Double, double>(var: value, type: dt); |
80 | case QOpcUa::DateTime: |
81 | return arrayFromQVariant<UA_DateTime, QDateTime>(var: value, type: dt); |
82 | case QOpcUa::String: |
83 | return arrayFromQVariant<UA_String, QString>(var: value, type: dt); |
84 | case QOpcUa::LocalizedText: |
85 | return arrayFromQVariant<UA_LocalizedText, QOpcUaLocalizedText>(var: value, type: dt); |
86 | case QOpcUa::ByteString: |
87 | return arrayFromQVariant<UA_ByteString, QByteArray>(var: value, type: dt); |
88 | case QOpcUa::NodeId: |
89 | return arrayFromQVariant<UA_NodeId, QString>(var: value, type: dt); |
90 | case QOpcUa::Guid: |
91 | return arrayFromQVariant<UA_Guid, QUuid>(var: value, type: dt); |
92 | case QOpcUa::XmlElement: |
93 | return arrayFromQVariant<UA_XmlElement, QString>(var: value, type: dt); |
94 | case QOpcUa::QualifiedName: |
95 | return arrayFromQVariant<UA_QualifiedName, QOpcUaQualifiedName>(var: value, type: dt); |
96 | case QOpcUa::StatusCode: |
97 | return arrayFromQVariant<UA_StatusCode, QOpcUa::UaStatusCode>(var: value, type: dt); |
98 | case QOpcUa::Range: |
99 | return arrayFromQVariant<UA_Range, QOpcUaRange>(var: value, type: dt); |
100 | case QOpcUa::EUInformation: |
101 | return arrayFromQVariant<UA_EUInformation, QOpcUaEUInformation>(var: value, type: dt); |
102 | case QOpcUa::ComplexNumber: |
103 | return arrayFromQVariant<UA_ComplexNumberType, QOpcUaComplexNumber>(var: value, type: dt); |
104 | case QOpcUa::DoubleComplexNumber: |
105 | return arrayFromQVariant<UA_DoubleComplexNumberType, QOpcUaDoubleComplexNumber>(var: value, type: dt); |
106 | case QOpcUa::AxisInformation: |
107 | return arrayFromQVariant<UA_AxisInformation, QOpcUaAxisInformation>(var: value, type: dt); |
108 | case QOpcUa::XV: |
109 | return arrayFromQVariant<UA_XVType, QOpcUaXValue>(var: value, type: dt); |
110 | case QOpcUa::ExpandedNodeId: |
111 | return arrayFromQVariant<UA_ExpandedNodeId, QOpcUaExpandedNodeId>(var: value, type: dt); |
112 | case QOpcUa::Argument: |
113 | return arrayFromQVariant<UA_Argument, QOpcUaArgument>(var: value, type: dt); |
114 | case QOpcUa::ExtensionObject: |
115 | return arrayFromQVariant<UA_ExtensionObject, QOpcUaExtensionObject>(var: value, type: dt); |
116 | default: |
117 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Variant conversion to Open62541 for typeIndex" << type << " not implemented" ; |
118 | } |
119 | |
120 | return open62541value; |
121 | } |
122 | |
123 | QVariant toQVariant(const UA_Variant &value) |
124 | { |
125 | if (value.type == nullptr) { |
126 | return QVariant(); |
127 | } |
128 | |
129 | if (value.type == &UA_TYPES[UA_TYPES_BOOLEAN]) |
130 | return arrayToQVariant<bool, UA_Boolean>(var: value, type: QMetaType::Bool); |
131 | else if (value.type == &UA_TYPES[UA_TYPES_SBYTE]) |
132 | return arrayToQVariant<signed char, UA_SByte>(var: value, type: QMetaType::SChar); |
133 | else if (value.type == &UA_TYPES[UA_TYPES_BYTE]) |
134 | return arrayToQVariant<uchar, UA_Byte>(var: value, type: QMetaType::UChar); |
135 | else if (value.type == &UA_TYPES[UA_TYPES_INT16]) |
136 | return arrayToQVariant<qint16, UA_Int16>(var: value, type: QMetaType::Short); |
137 | else if (value.type == &UA_TYPES[UA_TYPES_UINT16]) |
138 | return arrayToQVariant<quint16, UA_UInt16>(var: value, type: QMetaType::UShort); |
139 | else if (value.type == &UA_TYPES[UA_TYPES_INT32]) |
140 | return arrayToQVariant<qint32, UA_Int32>(var: value, type: QMetaType::Int); |
141 | else if (value.type == &UA_TYPES[UA_TYPES_UINT32]) |
142 | return arrayToQVariant<quint32, UA_UInt32>(var: value, type: QMetaType::UInt); |
143 | else if (value.type == &UA_TYPES[UA_TYPES_INT64]) |
144 | return arrayToQVariant<int64_t, UA_Int64>(var: value, type: QMetaType::LongLong); |
145 | else if (value.type == &UA_TYPES[UA_TYPES_UINT64]) |
146 | return arrayToQVariant<uint64_t, UA_UInt64>(var: value, type: QMetaType::ULongLong); |
147 | else if (value.type == &UA_TYPES[UA_TYPES_FLOAT]) |
148 | return arrayToQVariant<float, UA_Float>(var: value, type: QMetaType::Float); |
149 | else if (value.type == &UA_TYPES[UA_TYPES_DOUBLE]) |
150 | return arrayToQVariant<double, UA_Double>(var: value, type: QMetaType::Double); |
151 | else if (value.type == &UA_TYPES[UA_TYPES_STRING]) |
152 | return arrayToQVariant<QString, UA_String>(var: value, type: QMetaType::QString); |
153 | else if (value.type == &UA_TYPES[UA_TYPES_BYTESTRING]) |
154 | return arrayToQVariant<QByteArray, UA_ByteString>(var: value, type: QMetaType::QByteArray); |
155 | else if (value.type == &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]) |
156 | return arrayToQVariant<QOpcUaLocalizedText, UA_LocalizedText>(var: value); |
157 | else if (value.type == &UA_TYPES[UA_TYPES_NODEID]) |
158 | return arrayToQVariant<QString, UA_NodeId>(var: value, type: QMetaType::QString); |
159 | else if (value.type == &UA_TYPES[UA_TYPES_DATETIME]) |
160 | return arrayToQVariant<QDateTime, UA_DateTime>(var: value, type: QMetaType::QDateTime); |
161 | else if (value.type == &UA_TYPES[UA_TYPES_GUID]) |
162 | return arrayToQVariant<QUuid, UA_Guid>(var: value, type: QMetaType::QUuid); |
163 | else if (value.type == &UA_TYPES[UA_TYPES_XMLELEMENT]) |
164 | return arrayToQVariant<QString, UA_XmlElement>(var: value, type: QMetaType::QString); |
165 | else if (value.type == &UA_TYPES[UA_TYPES_QUALIFIEDNAME]) |
166 | return arrayToQVariant<QOpcUaQualifiedName, UA_QualifiedName>(var: value); |
167 | else if (value.type == &UA_TYPES[UA_TYPES_STATUSCODE]) |
168 | return arrayToQVariant<QOpcUa::UaStatusCode, UA_StatusCode>(var: value, type: QMetaType::UInt); |
169 | else if (value.type == &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]) |
170 | return arrayToQVariant<QVariant, UA_ExtensionObject>(var: value); |
171 | else if (value.type == &UA_TYPES[UA_TYPES_EXPANDEDNODEID]) |
172 | return arrayToQVariant<QOpcUaExpandedNodeId, UA_ExpandedNodeId>(var: value); |
173 | else if (value.type == &UA_TYPES[UA_TYPES_ARGUMENT]) |
174 | return arrayToQVariant<QOpcUaArgument, UA_Argument>(var: value); |
175 | else if (value.type == &UA_TYPES[UA_TYPES_RANGE]) |
176 | return arrayToQVariant<QOpcUaRange, UA_Range>(var: value); |
177 | else if (value.type == &UA_TYPES[UA_TYPES_EUINFORMATION]) |
178 | return arrayToQVariant<QOpcUaEUInformation, UA_EUInformation>(var: value); |
179 | else if (value.type == &UA_TYPES[UA_TYPES_AXISINFORMATION]) |
180 | return arrayToQVariant<QOpcUaAxisInformation, UA_AxisInformation>(var: value); |
181 | else if (value.type == &UA_TYPES[UA_TYPES_COMPLEXNUMBERTYPE]) |
182 | return arrayToQVariant<QOpcUaComplexNumber, UA_ComplexNumberType>(var: value); |
183 | else if (value.type == &UA_TYPES[UA_TYPES_DOUBLECOMPLEXNUMBERTYPE]) |
184 | return arrayToQVariant<QOpcUaDoubleComplexNumber, UA_DoubleComplexNumberType>(var: value); |
185 | else if (value.type == &UA_TYPES[UA_TYPES_XVTYPE]) |
186 | return arrayToQVariant<QOpcUaXValue, UA_XVType>(var: value); |
187 | |
188 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Variant conversion from Open62541 for typeName" << value.type->typeName << " not implemented" ; |
189 | return QVariant(); |
190 | } |
191 | |
192 | const UA_DataType *toDataType(QOpcUa::Types valueType) |
193 | { |
194 | switch (valueType) { |
195 | case QOpcUa::Boolean: |
196 | return &UA_TYPES[UA_TYPES_BOOLEAN]; |
197 | case QOpcUa::Int32: |
198 | return &UA_TYPES[UA_TYPES_INT32]; |
199 | case QOpcUa::UInt32: |
200 | return &UA_TYPES[UA_TYPES_UINT32]; |
201 | case QOpcUa::Double: |
202 | return &UA_TYPES[UA_TYPES_DOUBLE]; |
203 | case QOpcUa::Float: |
204 | return &UA_TYPES[UA_TYPES_FLOAT]; |
205 | case QOpcUa::String: |
206 | return &UA_TYPES[UA_TYPES_STRING]; |
207 | case QOpcUa::LocalizedText: |
208 | return &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]; |
209 | case QOpcUa::DateTime: |
210 | return &UA_TYPES[UA_TYPES_DATETIME]; |
211 | case QOpcUa::UInt16: |
212 | return &UA_TYPES[UA_TYPES_UINT16]; |
213 | case QOpcUa::Int16: |
214 | return &UA_TYPES[UA_TYPES_INT16]; |
215 | case QOpcUa::UInt64: |
216 | return &UA_TYPES[UA_TYPES_UINT64]; |
217 | case QOpcUa::Int64: |
218 | return &UA_TYPES[UA_TYPES_INT64]; |
219 | case QOpcUa::Byte: |
220 | return &UA_TYPES[UA_TYPES_BYTE]; |
221 | case QOpcUa::SByte: |
222 | return &UA_TYPES[UA_TYPES_SBYTE]; |
223 | case QOpcUa::ByteString: |
224 | return &UA_TYPES[UA_TYPES_BYTESTRING]; |
225 | case QOpcUa::XmlElement: |
226 | return &UA_TYPES[UA_TYPES_XMLELEMENT]; |
227 | case QOpcUa::NodeId: |
228 | return &UA_TYPES[UA_TYPES_NODEID]; |
229 | case QOpcUa::Guid: |
230 | return &UA_TYPES[UA_TYPES_GUID]; |
231 | case QOpcUa::QualifiedName: |
232 | return &UA_TYPES[UA_TYPES_QUALIFIEDNAME]; |
233 | case QOpcUa::StatusCode: |
234 | return &UA_TYPES[UA_TYPES_STATUSCODE]; |
235 | case QOpcUa::Range: |
236 | return &UA_TYPES[UA_TYPES_RANGE]; |
237 | case QOpcUa::EUInformation: |
238 | return &UA_TYPES[UA_TYPES_EUINFORMATION]; |
239 | case QOpcUa::ComplexNumber: |
240 | return &UA_TYPES[UA_TYPES_COMPLEXNUMBERTYPE]; |
241 | case QOpcUa::DoubleComplexNumber: |
242 | return &UA_TYPES[UA_TYPES_DOUBLECOMPLEXNUMBERTYPE]; |
243 | case QOpcUa::AxisInformation: |
244 | return &UA_TYPES[UA_TYPES_AXISINFORMATION]; |
245 | case QOpcUa::XV: |
246 | return &UA_TYPES[UA_TYPES_XVTYPE]; |
247 | case QOpcUa::ExtensionObject: |
248 | return &UA_TYPES[UA_TYPES_EXTENSIONOBJECT]; |
249 | case QOpcUa::ExpandedNodeId: |
250 | return &UA_TYPES[UA_TYPES_EXPANDEDNODEID]; |
251 | case QOpcUa::Argument: |
252 | return &UA_TYPES[UA_TYPES_ARGUMENT]; |
253 | default: |
254 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Trying to convert undefined type:" << valueType; |
255 | return nullptr; |
256 | } |
257 | } |
258 | |
259 | template<typename TARGETTYPE, typename UATYPE> |
260 | TARGETTYPE scalarToQt(const UATYPE *data) |
261 | { |
262 | return *reinterpret_cast<const TARGETTYPE *>(data); |
263 | } |
264 | |
265 | template<> |
266 | QString scalarToQt<QString, UA_String>(const UA_String *data) |
267 | { |
268 | return QString::fromUtf8(utf8: reinterpret_cast<const char *>(data->data), size: data->length); |
269 | } |
270 | |
271 | template<> |
272 | QByteArray scalarToQt<QByteArray, UA_ByteString>(const UA_ByteString *data) |
273 | { |
274 | return QByteArray(reinterpret_cast<const char *>(data->data), data->length); |
275 | } |
276 | |
277 | template<> |
278 | QOpcUaLocalizedText scalarToQt<QOpcUaLocalizedText, UA_LocalizedText>(const UA_LocalizedText *data) |
279 | { |
280 | QOpcUaLocalizedText lt; |
281 | lt.setLocale(scalarToQt<QString, UA_String>(data: &(data->locale))); |
282 | lt.setText(scalarToQt<QString, UA_String>(data: &(data->text))); |
283 | return lt; |
284 | } |
285 | |
286 | template<> |
287 | QString scalarToQt<QString, UA_NodeId>(const UA_NodeId *data) |
288 | { |
289 | return Open62541Utils::nodeIdToQString(id: *data); |
290 | } |
291 | |
292 | template<> |
293 | QDateTime scalarToQt<QDateTime, UA_DateTime>(const UA_DateTime *data) |
294 | { |
295 | // OPC-UA part 3, Table C.9 |
296 | if (*data == (std::numeric_limits<qint64>::min)() || *data == (std::numeric_limits<qint64>::max)()) |
297 | return QDateTime(); |
298 | |
299 | const QDateTime epochStart(QDate(1601, 1, 1), QTime(0, 0), QTimeZone::UTC); |
300 | return epochStart.addMSecs(msecs: *data / UA_DATETIME_MSEC).toLocalTime(); |
301 | } |
302 | |
303 | template<> |
304 | QOpcUaDataValue scalarToQt<QOpcUaDataValue, UA_DataValue>(const UA_DataValue *data) |
305 | { |
306 | QOpcUaDataValue result; |
307 | if (data->hasSourceTimestamp) |
308 | result.setSourceTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime, UA_DateTime>(data: &data->sourceTimestamp)); |
309 | if (data->hasServerTimestamp) |
310 | result.setServerTimestamp(QOpen62541ValueConverter::scalarToQt<QDateTime, UA_DateTime>(data: &data->serverTimestamp)); |
311 | if (data->hasValue) |
312 | result.setValue(QOpen62541ValueConverter::toQVariant(value: data->value)); |
313 | if (data->hasStatus) { |
314 | result.setStatusCode(QOpen62541ValueConverter::scalarToQt<QOpcUa::UaStatusCode, UA_StatusCode>(data: &data->status)); |
315 | } else { |
316 | result.setStatusCode(QOpcUa::UaStatusCode::Good); |
317 | } |
318 | return result; |
319 | } |
320 | |
321 | template<> |
322 | QUuid scalarToQt<QUuid, UA_Guid>(const UA_Guid *data) |
323 | { |
324 | return QUuid(data->data1, data->data2, data->data3, data->data4[0], data->data4[1], data->data4[2], |
325 | data->data4[3], data->data4[4], data->data4[5], data->data4[6], data->data4[7]); |
326 | } |
327 | |
328 | template<> |
329 | QOpcUaQualifiedName scalarToQt<QOpcUaQualifiedName, UA_QualifiedName>(const UA_QualifiedName *data) |
330 | { |
331 | QOpcUaQualifiedName temp; |
332 | temp.setNamespaceIndex(data->namespaceIndex); |
333 | temp.setName(scalarToQt<QString, UA_String>(data: &(data->name))); |
334 | return temp; |
335 | } |
336 | |
337 | template<> |
338 | QOpcUaArgument scalarToQt<QOpcUaArgument, UA_Argument>(const UA_Argument *data) |
339 | { |
340 | QOpcUaArgument temp; |
341 | temp.setValueRank(data->valueRank); |
342 | temp.setDataTypeId(Open62541Utils::nodeIdToQString(id: data->dataType)); |
343 | temp.setName(scalarToQt<QString, UA_String>(data: &data->name)); |
344 | temp.setDescription(scalarToQt<QOpcUaLocalizedText, UA_LocalizedText>(data: &data->description)); |
345 | for (size_t i = 0; i < data->arrayDimensionsSize; ++i) |
346 | temp.arrayDimensionsRef().append(t: data->arrayDimensions[i]); |
347 | return temp; |
348 | } |
349 | |
350 | template<> |
351 | QOpcUaRange scalarToQt<QOpcUaRange, UA_Range>(const UA_Range *data) |
352 | { |
353 | return QOpcUaRange(data->low, data->high); |
354 | } |
355 | |
356 | template<> |
357 | QOpcUaEUInformation scalarToQt<QOpcUaEUInformation, UA_EUInformation>(const UA_EUInformation *data) |
358 | { |
359 | return QOpcUaEUInformation(scalarToQt<QString, UA_String>(data: &data->namespaceUri), |
360 | data->unitId, |
361 | scalarToQt<QOpcUaLocalizedText, UA_LocalizedText>(data: &data->displayName), |
362 | scalarToQt<QOpcUaLocalizedText, UA_LocalizedText>(data: &data->description)); |
363 | } |
364 | |
365 | template<> |
366 | QOpcUaComplexNumber scalarToQt<QOpcUaComplexNumber, UA_ComplexNumberType>(const UA_ComplexNumberType *data) |
367 | { |
368 | return QOpcUaComplexNumber(data->real, data->imaginary); |
369 | } |
370 | |
371 | template<> |
372 | QOpcUaDoubleComplexNumber scalarToQt<QOpcUaDoubleComplexNumber, UA_DoubleComplexNumberType>( |
373 | const UA_DoubleComplexNumberType *data) |
374 | { |
375 | return QOpcUaDoubleComplexNumber(data->real, data->imaginary); |
376 | } |
377 | |
378 | template<> |
379 | QOpcUaAxisInformation scalarToQt<QOpcUaAxisInformation, UA_AxisInformation>(const UA_AxisInformation *data) |
380 | { |
381 | QList<double> axisSteps; |
382 | |
383 | if (data->axisStepsSize) { |
384 | axisSteps.reserve(asize: data->axisStepsSize); |
385 | std::copy(first: data->axisSteps, last: data->axisSteps + data->axisStepsSize, result: std::back_inserter(x&: axisSteps)); |
386 | } |
387 | |
388 | return QOpcUaAxisInformation(scalarToQt<QOpcUaEUInformation, UA_EUInformation>(data: &data->engineeringUnits), |
389 | scalarToQt<QOpcUaRange, UA_Range>(data: &data->eURange), |
390 | scalarToQt<QOpcUaLocalizedText, UA_LocalizedText>(data: &data->title), |
391 | static_cast<QOpcUa::AxisScale>(data->axisScaleType), |
392 | axisSteps); |
393 | } |
394 | |
395 | template<> |
396 | QOpcUaXValue scalarToQt<QOpcUaXValue, UA_XVType>(const UA_XVType *data) |
397 | { |
398 | return QOpcUaXValue(data->x, data->value); |
399 | } |
400 | |
401 | template <> |
402 | QVariant scalarToQt<QVariant, UA_ExtensionObject>(const UA_ExtensionObject *data) |
403 | { |
404 | // OPC-UA part 6, Table 13 states that an extension object can have no body, a ByteString encoded body |
405 | // or an XML encoded body. |
406 | |
407 | // Handle extension object without body |
408 | if (data->encoding == UA_EXTENSIONOBJECT_ENCODED_NOBODY) { |
409 | QOpcUaExtensionObject obj; |
410 | obj.setEncoding(QOpcUaExtensionObject::Encoding::NoBody); |
411 | return QVariant::fromValue(value: obj); |
412 | } |
413 | |
414 | // Some types are automatically decoded by open62541. In this case, the encoding is UA_EXTENSIONOBJECT_DECODED |
415 | if (data->encoding != UA_EXTENSIONOBJECT_ENCODED_XML && data->encoding != UA_EXTENSIONOBJECT_ENCODED_BYTESTRING) { |
416 | |
417 | if (data->content.decoded.type == &UA_TYPES[UA_TYPES_ARGUMENT] && data->content.decoded.data != nullptr) { |
418 | return scalarToQt<QOpcUaArgument, UA_Argument>(data: reinterpret_cast<UA_Argument *>(data->content.decoded.data)); |
419 | } |
420 | |
421 | if (data->content.decoded.type == &UA_TYPES[UA_TYPES_RANGE] && data->content.decoded.data != nullptr) { |
422 | return scalarToQt<QOpcUaRange, UA_Range>(data: reinterpret_cast<UA_Range *>(data->content.decoded.data)); |
423 | } |
424 | |
425 | if (data->content.decoded.type == &UA_TYPES[UA_TYPES_EUINFORMATION] && data->content.decoded.data) { |
426 | return scalarToQt<QOpcUaEUInformation, UA_EUInformation> |
427 | (data: reinterpret_cast<UA_EUInformation *>(data->content.decoded.data)); |
428 | } |
429 | |
430 | if (data->content.decoded.type == &UA_TYPES[UA_TYPES_COMPLEXNUMBERTYPE] && data->content.decoded.data) { |
431 | return scalarToQt<QOpcUaComplexNumber, UA_ComplexNumberType> |
432 | (data: reinterpret_cast<UA_ComplexNumberType *>(data->content.decoded.data)); |
433 | } |
434 | |
435 | if (data->content.decoded.type == &UA_TYPES[UA_TYPES_DOUBLECOMPLEXNUMBERTYPE] && data->content.decoded.data) { |
436 | return scalarToQt<QOpcUaDoubleComplexNumber, UA_DoubleComplexNumberType> |
437 | (data: reinterpret_cast<UA_DoubleComplexNumberType *>(data->content.decoded.data)); |
438 | } |
439 | |
440 | if (data->content.decoded.type == &UA_TYPES[UA_TYPES_AXISINFORMATION] && data->content.decoded.data) { |
441 | return scalarToQt<QOpcUaAxisInformation, UA_AxisInformation> |
442 | (data: reinterpret_cast<UA_AxisInformation *>(data->content.decoded.data)); |
443 | } |
444 | |
445 | if (data->content.decoded.type == &UA_TYPES[UA_TYPES_XVTYPE] && data->content.decoded.data) { |
446 | return scalarToQt<QOpcUaXValue, UA_XVType> |
447 | (data: reinterpret_cast<UA_XVType *>(data->content.decoded.data)); |
448 | } |
449 | |
450 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Unsupported decoded extension object type, unable to convert" << data->content.decoded.type->typeName; |
451 | return QVariant(); |
452 | } |
453 | |
454 | QByteArray buffer = QByteArray::fromRawData(data: reinterpret_cast<const char *>(data->content.encoded.body.data), |
455 | size: data->content.encoded.body.length); |
456 | |
457 | // Decode recognized types, as required by OPC-UA part 4, 5.2.2.15 |
458 | if (data->content.encoded.typeId.identifierType == UA_NODEIDTYPE_NUMERIC && |
459 | data->content.encoded.typeId.namespaceIndex == 0 && |
460 | data->encoding == UA_EXTENSIONOBJECT_ENCODED_BYTESTRING) { |
461 | |
462 | QOpcUaBinaryDataEncoding decoder(&buffer); |
463 | |
464 | bool success = false; |
465 | QVariant result; |
466 | Namespace0 objType = Namespace0(data->content.encoded.typeId.identifier.numeric); |
467 | switch (objType) { |
468 | case Namespace0::EUInformation_Encoding_DefaultBinary: |
469 | result = decoder.decode<QOpcUaEUInformation>(success); |
470 | break; |
471 | case Namespace0::ComplexNumberType_Encoding_DefaultBinary: |
472 | result = decoder.decode<QOpcUaComplexNumber>(success); |
473 | break; |
474 | case Namespace0::DoubleComplexNumberType_Encoding_DefaultBinary: |
475 | result = decoder.decode<QOpcUaDoubleComplexNumber>(success); |
476 | break; |
477 | case Namespace0::AxisInformation_Encoding_DefaultBinary: |
478 | result = decoder.decode<QOpcUaAxisInformation>(success); |
479 | break; |
480 | case Namespace0::XVType_Encoding_DefaultBinary: |
481 | result = decoder.decode<QOpcUaXValue>(success); |
482 | break; |
483 | default: |
484 | break; |
485 | } |
486 | if (success) |
487 | return result; |
488 | } |
489 | |
490 | // Return extension objects with binary or XML body as QOpcUaExtensionObject |
491 | QOpcUaExtensionObject obj; |
492 | obj.setEncoding(static_cast<QOpcUaExtensionObject::Encoding>(data->encoding)); |
493 | obj.setEncodingTypeId(Open62541Utils::nodeIdToQString(id: data->content.encoded.typeId)); |
494 | obj.setEncodedBody(QByteArray(buffer.constData(), buffer.size())); |
495 | return obj; |
496 | } |
497 | |
498 | template<> |
499 | QOpcUaExpandedNodeId scalarToQt<QOpcUaExpandedNodeId, UA_ExpandedNodeId>(const UA_ExpandedNodeId *data) |
500 | { |
501 | QOpcUaExpandedNodeId temp; |
502 | temp.setServerIndex(data->serverIndex); |
503 | temp.setNodeId(Open62541Utils::nodeIdToQString(id: data->nodeId)); |
504 | temp.setNamespaceUri(scalarToQt<QString, UA_String>(data: &data->namespaceUri)); |
505 | return temp; |
506 | } |
507 | |
508 | template<typename TARGETTYPE, typename UATYPE> |
509 | QVariant arrayToQVariant(const UA_Variant &var, QMetaType::Type type) |
510 | { |
511 | UATYPE *temp = static_cast<UATYPE *>(var.data); |
512 | |
513 | if (var.arrayLength > 0) { |
514 | QVariantList list; |
515 | for (size_t i = 0; i < var.arrayLength; ++i) { |
516 | QVariant tempVar = QVariant::fromValue(scalarToQt<TARGETTYPE, UATYPE>(&temp[i])); |
517 | if (type != QMetaType::UnknownType && type != static_cast<QMetaType::Type>(tempVar.metaType().id())) |
518 | tempVar.convert(type: QMetaType(type)); |
519 | list.append(t: tempVar); |
520 | } |
521 | |
522 | if (var.arrayDimensionsSize > 0) { |
523 | // Ensure that the array dimensions fit in a QList |
524 | if (var.arrayDimensionsSize > static_cast<quint64>((std::numeric_limits<int>::max)())) |
525 | return QOpcUaMultiDimensionalArray(); |
526 | QList<quint32> arrayDimensions; |
527 | std::copy(first: var.arrayDimensions, last: var.arrayDimensions+var.arrayDimensionsSize, result: std::back_inserter(x&: arrayDimensions)); |
528 | return QOpcUaMultiDimensionalArray(list, arrayDimensions); |
529 | } |
530 | |
531 | if (list.size() == 1) |
532 | return list.at(i: 0); |
533 | else |
534 | return list; |
535 | } else if (UA_Variant_isScalar(v: &var)) { |
536 | QVariant tempVar = QVariant::fromValue(scalarToQt<TARGETTYPE, UATYPE>(temp)); |
537 | if (type != QMetaType::UnknownType && type != static_cast<QMetaType::Type>(tempVar.metaType().id())) |
538 | tempVar.convert(type: QMetaType(type)); |
539 | return tempVar; |
540 | } else if (var.arrayLength == 0 && var.data == UA_EMPTY_ARRAY_SENTINEL) { |
541 | return QVariantList(); // Return empty QVariantList for empty array |
542 | } |
543 | |
544 | return QVariant(); // Return empty QVariant for empty scalar variant |
545 | } |
546 | |
547 | template<typename TARGETTYPE, typename QTTYPE> |
548 | void scalarFromQt(const QTTYPE &value, TARGETTYPE *ptr) |
549 | { |
550 | *ptr = static_cast<TARGETTYPE>(value); |
551 | } |
552 | |
553 | template<> |
554 | void scalarFromQt<UA_DateTime, QDateTime>(const QDateTime &value, UA_DateTime *ptr) |
555 | { |
556 | if (!value.isValid()) { |
557 | *ptr = (std::numeric_limits<qint64>::min)(); |
558 | return; |
559 | } |
560 | |
561 | // OPC-UA part 3, Table C.9 |
562 | const QDateTime uaEpochStart(QDate(1601, 1, 1), QTime(0, 0), QTimeZone::UTC); |
563 | |
564 | *ptr = UA_DATETIME_MSEC * (value.toMSecsSinceEpoch() - uaEpochStart.toMSecsSinceEpoch()); |
565 | } |
566 | |
567 | template<> |
568 | void scalarFromQt<UA_String, QString>(const QString &value, UA_String *ptr) |
569 | { |
570 | *ptr = UA_STRING_ALLOC(value.toUtf8().constData()); |
571 | } |
572 | |
573 | template<> |
574 | void scalarFromQt<UA_LocalizedText, QOpcUaLocalizedText>(const QOpcUaLocalizedText &value, UA_LocalizedText *ptr) |
575 | { |
576 | scalarFromQt<UA_String, QString>(value: value.locale(), ptr: &(ptr->locale)); |
577 | scalarFromQt<UA_String, QString>(value: value.text(), ptr: &(ptr->text)); |
578 | } |
579 | |
580 | template<> |
581 | void scalarFromQt<UA_ByteString, QByteArray>(const QByteArray &value, UA_ByteString *ptr) |
582 | { |
583 | ptr->length = value.size(); |
584 | UA_StatusCode success = UA_Array_copy(src: reinterpret_cast<const UA_Byte *>(value.constData()), |
585 | size: value.size(), dst: reinterpret_cast<void **>(&ptr->data), type: &UA_TYPES[UA_TYPES_BYTE]); |
586 | if (success != UA_STATUSCODE_GOOD) { |
587 | ptr->length = 0; |
588 | ptr->data = nullptr; |
589 | } |
590 | } |
591 | |
592 | template<> |
593 | void scalarFromQt<UA_NodeId, QString>(const QString &value, UA_NodeId *ptr) |
594 | { |
595 | *ptr = Open62541Utils::nodeIdFromQString(name: value); |
596 | } |
597 | |
598 | template<> |
599 | void scalarFromQt<UA_QualifiedName, QOpcUaQualifiedName>(const QOpcUaQualifiedName &value, UA_QualifiedName *ptr) |
600 | { |
601 | ptr->namespaceIndex = value.namespaceIndex(); |
602 | scalarFromQt<UA_String, QString>(value: value.name(), ptr: &(ptr->name)); |
603 | } |
604 | |
605 | template<> |
606 | void scalarFromQt<UA_Guid, QUuid>(const QUuid &value, UA_Guid *ptr) |
607 | { |
608 | ptr->data1 = value.data1; |
609 | ptr->data2 = value.data2; |
610 | ptr->data3 = value.data3; |
611 | std::memcpy(dest: ptr->data4, src: value.data4, n: sizeof(value.data4)); |
612 | } |
613 | |
614 | template<> |
615 | void scalarFromQt<UA_Range, QOpcUaRange>(const QOpcUaRange &value, UA_Range *ptr) |
616 | { |
617 | ptr->low = value.low(); |
618 | ptr->high = value.high(); |
619 | } |
620 | |
621 | template<> |
622 | void scalarFromQt<UA_EUInformation, QOpcUaEUInformation>(const QOpcUaEUInformation &value, UA_EUInformation *ptr) |
623 | { |
624 | scalarFromQt<UA_String, QString>(value: value.namespaceUri(), ptr: &ptr->namespaceUri); |
625 | scalarFromQt<UA_LocalizedText, QOpcUaLocalizedText>(value: value.description(), ptr: &ptr->description); |
626 | scalarFromQt<UA_LocalizedText, QOpcUaLocalizedText>(value: value.displayName(), ptr: &ptr->displayName); |
627 | ptr->unitId = value.unitId(); |
628 | } |
629 | |
630 | template<> |
631 | void scalarFromQt<UA_ComplexNumberType, QOpcUaComplexNumber>(const QOpcUaComplexNumber &value, UA_ComplexNumberType *ptr) |
632 | { |
633 | ptr->real = value.real(); |
634 | ptr->imaginary = value.imaginary(); |
635 | } |
636 | |
637 | template<> |
638 | void scalarFromQt<UA_DoubleComplexNumberType, QOpcUaDoubleComplexNumber>(const QOpcUaDoubleComplexNumber &value, UA_DoubleComplexNumberType *ptr) |
639 | { |
640 | ptr->real = value.real(); |
641 | ptr->imaginary = value.imaginary(); |
642 | } |
643 | |
644 | template<> |
645 | void scalarFromQt<UA_AxisInformation, QOpcUaAxisInformation>(const QOpcUaAxisInformation &value, UA_AxisInformation *ptr) |
646 | { |
647 | scalarFromQt<UA_LocalizedText, QOpcUaLocalizedText>(value: value.title(), ptr: &ptr->title); |
648 | scalarFromQt<UA_EUInformation, QOpcUaEUInformation>(value: value.engineeringUnits(), ptr: &ptr->engineeringUnits); |
649 | scalarFromQt<UA_Range, QOpcUaRange>(value: value.eURange(), ptr: &ptr->eURange); |
650 | ptr->axisScaleType = static_cast<UA_AxisScaleEnumeration>(value.axisScaleType()); |
651 | ptr->axisStepsSize = value.axisSteps().size(); |
652 | if (ptr->axisStepsSize) { |
653 | auto res = UA_Array_copy(src: value.axisSteps().constData(), size: ptr->axisStepsSize, dst: reinterpret_cast<void **>(&ptr->axisSteps), |
654 | type: &UA_TYPES[UA_TYPES_DOUBLE]); |
655 | |
656 | if (res != UA_STATUSCODE_GOOD) |
657 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Failed to copy axis steps" ; |
658 | } else { |
659 | ptr->axisSteps = nullptr; |
660 | } |
661 | } |
662 | |
663 | template<> |
664 | void scalarFromQt<UA_XVType, QOpcUaXValue>(const QOpcUaXValue &value, UA_XVType *ptr) |
665 | { |
666 | ptr->x = value.x(); |
667 | ptr->value = value.value(); |
668 | } |
669 | |
670 | template<> |
671 | void scalarFromQt<UA_ExtensionObject, QOpcUaExtensionObject>(const QOpcUaExtensionObject &obj, UA_ExtensionObject *ptr) |
672 | { |
673 | QByteArray temp = obj.encodedBody(); |
674 | UA_NodeId encodingId = Open62541Utils::nodeIdFromQString(name: obj.encodingTypeId()); |
675 | UaDeleter<UA_NodeId> nodeIdDeleter(&encodingId, UA_NodeId_clear); |
676 | createExtensionObject(data&: temp, typeEncodingId: encodingId, ptr, encoding: obj.encoding()); |
677 | } |
678 | |
679 | template<> |
680 | void scalarFromQt<UA_ExpandedNodeId, QOpcUaExpandedNodeId>(const QOpcUaExpandedNodeId &value, UA_ExpandedNodeId *ptr) |
681 | { |
682 | ptr->serverIndex = value.serverIndex(); |
683 | scalarFromQt<UA_String, QString>(value: value.namespaceUri(), ptr: &ptr->namespaceUri); |
684 | ptr->nodeId = Open62541Utils::nodeIdFromQString(name: value.nodeId()); |
685 | } |
686 | |
687 | template<> |
688 | void scalarFromQt<UA_Argument, QOpcUaArgument>(const QOpcUaArgument &value, UA_Argument *ptr) |
689 | { |
690 | ptr->valueRank = value.valueRank(); |
691 | scalarFromQt<UA_LocalizedText, QOpcUaLocalizedText>(value: value.description(), ptr: &ptr->description); |
692 | scalarFromQt<UA_String, QString>(value: value.name(), ptr: &ptr->name); |
693 | ptr->dataType = Open62541Utils::nodeIdFromQString(name: value.dataTypeId()); |
694 | ptr->arrayDimensionsSize = value.arrayDimensions().size(); |
695 | UA_StatusCode res = UA_Array_copy(src: value.arrayDimensions().constData(), size: ptr->arrayDimensionsSize, |
696 | dst: reinterpret_cast<void **>(&ptr->arrayDimensions), type: &UA_TYPES[UA_TYPES_UINT32]); |
697 | if (res != UA_STATUSCODE_GOOD) |
698 | ptr->arrayDimensionsSize = 0; |
699 | } |
700 | |
701 | template<typename TARGETTYPE, typename QTTYPE> |
702 | UA_Variant arrayFromQVariant(const QVariant &var, const UA_DataType *type) |
703 | { |
704 | UA_Variant open62541value; |
705 | UA_Variant_init(p: &open62541value); |
706 | |
707 | if (type == nullptr) { |
708 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Unable to convert QVariant to UA_Variant, unknown type" ; |
709 | return open62541value; |
710 | } |
711 | |
712 | if (var.metaType().id() == QMetaType::QVariantList) { |
713 | const QVariantList list = var.toList(); |
714 | if (list.isEmpty()) |
715 | return open62541value; |
716 | |
717 | for (const auto &it : std::as_const(t: list)) { |
718 | if (!it.canConvert<QTTYPE>()) { |
719 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Value type" << var.typeName() << |
720 | "in the QVariant does not match type parameter" << type->typeName; |
721 | return open62541value; |
722 | } |
723 | } |
724 | |
725 | TARGETTYPE *arr = static_cast<TARGETTYPE *>(UA_Array_new(size: list.size(), type)); |
726 | |
727 | for (int i = 0; i < list.size(); ++i) |
728 | scalarFromQt<TARGETTYPE, QTTYPE>(list[i].value<QTTYPE>(), &arr[i]); |
729 | |
730 | UA_Variant_setArray(&open62541value, arr, list.size(), type); |
731 | return open62541value; |
732 | } |
733 | |
734 | if (!var.canConvert<QTTYPE>()) { |
735 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Value type" << var.typeName() << |
736 | "in the QVariant does not match type parameter" << type->typeName; |
737 | return open62541value; |
738 | } |
739 | |
740 | TARGETTYPE *temp = static_cast<TARGETTYPE *>(UA_new(type)); |
741 | scalarFromQt<TARGETTYPE, QTTYPE>(var.value<QTTYPE>(), temp); |
742 | UA_Variant_setScalar(&open62541value, temp, type); |
743 | return open62541value; |
744 | } |
745 | |
746 | void createExtensionObject(QByteArray &data, const UA_NodeId &typeEncodingId, UA_ExtensionObject *ptr, QOpcUaExtensionObject::Encoding encoding) |
747 | { |
748 | UA_ExtensionObject obj; |
749 | UA_ExtensionObject_init(p: &obj); |
750 | |
751 | if (!data.isEmpty() && encoding == QOpcUaExtensionObject::Encoding::NoBody) |
752 | qCWarning(QT_OPCUA_PLUGINS_OPEN62541) << "Data for extension object provided but will not be encoded because encoding format is set to skip the body" ; |
753 | |
754 | if (encoding != QOpcUaExtensionObject::Encoding::NoBody) { |
755 | obj.encoding = static_cast<UA_ExtensionObjectEncoding>(encoding); |
756 | obj.content.encoded.body.data = reinterpret_cast<UA_Byte *>(data.data()); |
757 | obj.content.encoded.body.length = data.size(); |
758 | } |
759 | obj.content.encoded.typeId = typeEncodingId; |
760 | UA_ExtensionObject_copy(src: &obj, dst: ptr); |
761 | } |
762 | |
763 | } |
764 | |
765 | QT_END_NAMESPACE |
766 | |