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
18QT_BEGIN_NAMESPACE
19
20Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_OPEN62541)
21
22using namespace QOpcUa::NodeIds;
23
24namespace QOpen62541ValueConverter {
25
26UA_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
123QVariant 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
192const 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
259template<typename TARGETTYPE, typename UATYPE>
260TARGETTYPE scalarToQt(const UATYPE *data)
261{
262 return *reinterpret_cast<const TARGETTYPE *>(data);
263}
264
265template<>
266QString 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
271template<>
272QByteArray scalarToQt<QByteArray, UA_ByteString>(const UA_ByteString *data)
273{
274 return QByteArray(reinterpret_cast<const char *>(data->data), data->length);
275}
276
277template<>
278QOpcUaLocalizedText 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
286template<>
287QString scalarToQt<QString, UA_NodeId>(const UA_NodeId *data)
288{
289 return Open62541Utils::nodeIdToQString(id: *data);
290}
291
292template<>
293QDateTime 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
303template<>
304QOpcUaDataValue 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
321template<>
322QUuid 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
328template<>
329QOpcUaQualifiedName 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
337template<>
338QOpcUaArgument 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
350template<>
351QOpcUaRange scalarToQt<QOpcUaRange, UA_Range>(const UA_Range *data)
352{
353 return QOpcUaRange(data->low, data->high);
354}
355
356template<>
357QOpcUaEUInformation 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
365template<>
366QOpcUaComplexNumber scalarToQt<QOpcUaComplexNumber, UA_ComplexNumberType>(const UA_ComplexNumberType *data)
367{
368 return QOpcUaComplexNumber(data->real, data->imaginary);
369}
370
371template<>
372QOpcUaDoubleComplexNumber scalarToQt<QOpcUaDoubleComplexNumber, UA_DoubleComplexNumberType>(
373 const UA_DoubleComplexNumberType *data)
374{
375 return QOpcUaDoubleComplexNumber(data->real, data->imaginary);
376}
377
378template<>
379QOpcUaAxisInformation 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
395template<>
396QOpcUaXValue scalarToQt<QOpcUaXValue, UA_XVType>(const UA_XVType *data)
397{
398 return QOpcUaXValue(data->x, data->value);
399}
400
401template <>
402QVariant 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
498template<>
499QOpcUaExpandedNodeId 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
508template<typename TARGETTYPE, typename UATYPE>
509QVariant 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
547template<typename TARGETTYPE, typename QTTYPE>
548void scalarFromQt(const QTTYPE &value, TARGETTYPE *ptr)
549{
550 *ptr = static_cast<TARGETTYPE>(value);
551}
552
553template<>
554void 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
567template<>
568void scalarFromQt<UA_String, QString>(const QString &value, UA_String *ptr)
569{
570 *ptr = UA_STRING_ALLOC(value.toUtf8().constData());
571}
572
573template<>
574void 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
580template<>
581void 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
592template<>
593void scalarFromQt<UA_NodeId, QString>(const QString &value, UA_NodeId *ptr)
594{
595 *ptr = Open62541Utils::nodeIdFromQString(name: value);
596}
597
598template<>
599void 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
605template<>
606void 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
614template<>
615void scalarFromQt<UA_Range, QOpcUaRange>(const QOpcUaRange &value, UA_Range *ptr)
616{
617 ptr->low = value.low();
618 ptr->high = value.high();
619}
620
621template<>
622void 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
630template<>
631void scalarFromQt<UA_ComplexNumberType, QOpcUaComplexNumber>(const QOpcUaComplexNumber &value, UA_ComplexNumberType *ptr)
632{
633 ptr->real = value.real();
634 ptr->imaginary = value.imaginary();
635}
636
637template<>
638void scalarFromQt<UA_DoubleComplexNumberType, QOpcUaDoubleComplexNumber>(const QOpcUaDoubleComplexNumber &value, UA_DoubleComplexNumberType *ptr)
639{
640 ptr->real = value.real();
641 ptr->imaginary = value.imaginary();
642}
643
644template<>
645void 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
663template<>
664void scalarFromQt<UA_XVType, QOpcUaXValue>(const QOpcUaXValue &value, UA_XVType *ptr)
665{
666 ptr->x = value.x();
667 ptr->value = value.value();
668}
669
670template<>
671void 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
679template<>
680void 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
687template<>
688void 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
701template<typename TARGETTYPE, typename QTTYPE>
702UA_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
746void 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
765QT_END_NAMESPACE
766

source code of qtopcua/src/plugins/opcua/open62541/qopen62541valueconverter.cpp