diff options
author | Jannis Voelker <jannis.voelker@basyskom.com> | 2018-04-30 14:24:26 +0200 |
---|---|---|
committer | Jannis Völker <jannis.voelker@basyskom.com> | 2018-07-27 12:13:41 +0000 |
commit | c20a8242974bc2c580ab84c998e6346b03ccd33c (patch) | |
tree | 55ec5143cbdfcf1d2adf08b57d08755e25b7a2ca /src | |
parent | 735fd6aed2fe3d853d863113c42f60f2227e377e (diff) |
Enable reading and writing of multi-dimensional arrays
This patch proposes a way to handle multi-dimensional arrays in Qt OPC UA.
For now, support has only been implemented in the open62541 backend.
A test case showing how to handle multi-dimensional arrays using the
approach implemented in this patch has been added to tst_client.cpp.
Change-Id: Iabe58fee405989898469c13a1c0ed51b5d057098
Reviewed-by: Rainer Keller <Rainer.Keller@qt.io>
Diffstat (limited to 'src')
-rw-r--r-- | src/opcua/client/qopcuatype.cpp | 220 | ||||
-rw-r--r-- | src/opcua/client/qopcuatype.h | 34 | ||||
-rw-r--r-- | src/opcua/doc/src/qtopcua.qdoc | 5 | ||||
-rw-r--r-- | src/plugins/opcua/open62541/qopen62541valueconverter.cpp | 24 |
4 files changed, 282 insertions, 1 deletions
diff --git a/src/opcua/client/qopcuatype.cpp b/src/opcua/client/qopcuatype.cpp index 21424cc..367a24b 100644 --- a/src/opcua/client/qopcuatype.cpp +++ b/src/opcua/client/qopcuatype.cpp @@ -3699,4 +3699,224 @@ void QOpcUa::QExtensionObject::setEncodingTypeId(const QString &value) data->encodingTypeId = value; } +/* + This class has been modelled in the style of the Variant encoding + defined in OPC-UA part 6, 5.2.2.16. + + This solution has been preferred to returning nested QVariantLists + due to the following reasons: + - A QVariantList inside a QVariantList is stored as a QVariant which must be converted + to QVariantList before the elements can be accessed. This makes it impossible to update the + values in place. + - The length of the array is encoded as a 32 bit unsigned integer. + Array dimensions are encoded in an array, so an array can have UINT32_MAX dimensions. + Depending on the number of dimensions, there could be lots of nested QVariantLists + which would require a huge effort when calculating the array dimensions for conversions + between QVariantList and the sdk specific variant type. +*/ + +/*! + \class QOpcUa::QMultiDimensionalArray + \inmodule QtOpcUa + \inheaderfile QtOpcUa/qopcuatype.h + \brief A container class for multidimensional arrays. + + This class manages arrays of Qt OPC UA types with associated array dimensions information. + It is returned as value when a multidimensional array is received from the server. It can also + be used as a write value or as parameter for filters and method calls. +*/ + +class QOpcUa::QMultiDimensionalArrayData : public QSharedData +{ +public: + QVariantList value; + QVector<quint32> arrayDimensions; + quint32 expectedArrayLength{0}; +}; + +QOpcUa::QMultiDimensionalArray::QMultiDimensionalArray() + : data(new QOpcUa::QMultiDimensionalArrayData) +{ +} + +/*! + Constructs a multidimensional array from \a other. +*/ +QOpcUa::QMultiDimensionalArray::QMultiDimensionalArray(const QOpcUa::QMultiDimensionalArray &other) + : data(other.data) +{ +} + +/*! + Sets the values from \a rhs in the multidimensional array. +*/ +QOpcUa::QMultiDimensionalArray &QOpcUa::QMultiDimensionalArray::operator=(const QOpcUa::QMultiDimensionalArray &rhs) +{ + if (this != &rhs) + data.operator=(rhs.data); + return *this; +} + +/*! + Constructs a multidimensional array with value \a value and array dimensions \a arrayDimensions. +*/ +QOpcUa::QMultiDimensionalArray::QMultiDimensionalArray(const QVariantList &value, const QVector<quint32> &arrayDimensions) + : data(new QOpcUa::QMultiDimensionalArrayData) +{ + setValueArray(value); + setArrayDimensions(arrayDimensions); +} + +/*! + Creates a multidimensional array with preallocated data fitting \a arrayDimensions. +*/ +QOpcUa::QMultiDimensionalArray::QMultiDimensionalArray(const QVector<quint32> &arrayDimensions) + : data(new QOpcUa::QMultiDimensionalArrayData) +{ + setArrayDimensions(arrayDimensions); + if (data->expectedArrayLength) { + data->value.reserve(data->expectedArrayLength); + for (size_t i = 0; i < data->expectedArrayLength; ++i) + data->value.append(QVariant()); + } +} + +QOpcUa::QMultiDimensionalArray::~QMultiDimensionalArray() +{ +} + +/*! + Returns the dimensions of the multidimensional array. + The element at position n contains the length of the n-th dimension. +*/ +QVector<quint32> QOpcUa::QMultiDimensionalArray::arrayDimensions() const +{ + return data->arrayDimensions; +} + +/*! + Sets the dimensions of the multidimensional array to \a arrayDimensions. +*/ +void QOpcUa::QMultiDimensionalArray::setArrayDimensions(const QVector<quint32> &arrayDimensions) +{ + data->arrayDimensions = arrayDimensions; + data->expectedArrayLength = std::accumulate(data->arrayDimensions.begin(), data->arrayDimensions.end(), + 1, std::multiplies<quint32>()); +} + +/*! + Returns \c true if this multidimensional array has the same value as \a other. +*/ +bool QOpcUa::QMultiDimensionalArray::operator==(const QOpcUa::QMultiDimensionalArray &other) const +{ + return arrayDimensions() == other.arrayDimensions() && + valueArray() == other.valueArray(); +} + +/*! + Converts this multidimensional array to \l QVariant. +*/ +QOpcUa::QMultiDimensionalArray::operator QVariant() const +{ + return QVariant::fromValue(*this); +} + +/*! + Returns the value array of the multidimensional array. +*/ +QVariantList QOpcUa::QMultiDimensionalArray::valueArray() const +{ + return data->value; +} + +/*! + Returns a reference to the value array of the multidimensional array. +*/ +QVariantList &QOpcUa::QMultiDimensionalArray::valueArrayRef() +{ + return data->value; +} + +/*! + Sets the value array of the multidimensional array to \a value. +*/ +void QOpcUa::QMultiDimensionalArray::setValueArray(const QVariantList &value) +{ + data->value = value; +} + +/*! + Returns the array index in \l valueArray() of the element identified by \a indices. + If \a indices is invalid for the array or if the array's dimensions don't match + the size of \l valueArray(), the invalid index \c -1 is returned. +*/ +int QOpcUa::QMultiDimensionalArray::arrayIndex(const QVector<quint32> &indices) const +{ + // A QList can store INT_MAX values. Depending on the platform, this allows a size > UINT32_MAX + if (data->expectedArrayLength > static_cast<quint64>(std::numeric_limits<int>::max()) || + static_cast<quint64>(data->value.size()) > std::numeric_limits<quint32>::max()) + return -1; + + // Check number of dimensions and data size + if (indices.size() != data->arrayDimensions.size() || + data->expectedArrayLength != static_cast<quint32>(data->value.size())) + return -1; // Missing array dimensions or array dimensions don't fit the array + + quint32 index = 0; + quint32 stride = 1; + // Reverse iteration to avoid repetitions while calculating the stride + for (int i = data->arrayDimensions.size() - 1; i >= 0; --i) { + if (indices.at(i) >= data->arrayDimensions.at(i)) // Out of bounds + return -1; + + // Arrays are encoded in row-major order: [0,0,0], [0,0,1], [0,1,0], [0,1,1], [1,0,0], [1,0,1], [1,1,0], [1,1,1] + // The stride for dimension i in a n dimensional array is the product of all array dimensions from i+1 to n + if (i < data->arrayDimensions.size() - 1) + stride *= data->arrayDimensions.at(i + 1); + index += stride * indices.at(i); + } + + return (index <= static_cast<quint64>(std::numeric_limits<int>::max())) ? + static_cast<int>(index) : -1; +} + +/*! + Returns the value of the element identified by \a indices. + If the indices are invalid for the array, an empty \l QVariant is returned. +*/ +QVariant QOpcUa::QMultiDimensionalArray::value(const QVector<quint32> &indices) const +{ + int index = arrayIndex(indices); + + if (index < 0) + return QVariant(); + + return data->value.at(index); +} + +/*! + Sets the value at position \a indices to \a value. + Returns \c true if the value has been successfully set. +*/ +bool QOpcUa::QMultiDimensionalArray::setValue(const QVector<quint32> &indices, const QVariant &value) +{ + int index = arrayIndex(indices); + + if (index < 0) + return false; + + data->value[index] = value; + return true; +} + +/*! + Returns \c true if the multidimensional array is valid +*/ +bool QOpcUa::QMultiDimensionalArray::isValid() const +{ + return static_cast<quint64>(data->value.size()) == data->expectedArrayLength && + static_cast<quint64>(data->value.size()) <= std::numeric_limits<quint32>::max() && + static_cast<quint64>(data->arrayDimensions.size()) <= std::numeric_limits<quint32>::max(); +} + QT_END_NAMESPACE diff --git a/src/opcua/client/qopcuatype.h b/src/opcua/client/qopcuatype.h index 24d11fc..742bdc0 100644 --- a/src/opcua/client/qopcuatype.h +++ b/src/opcua/client/qopcuatype.h @@ -1092,6 +1092,39 @@ public: private: QSharedDataPointer<QOpcUa::QExtensionObjectData> data; }; + +class QMultiDimensionalArrayData; +class Q_OPCUA_EXPORT QMultiDimensionalArray +{ +public: + QMultiDimensionalArray(); + QMultiDimensionalArray(const QOpcUa::QMultiDimensionalArray &other); + QMultiDimensionalArray &operator=(const QOpcUa::QMultiDimensionalArray &rhs); + QMultiDimensionalArray(const QVariantList &valueArray, const QVector<quint32> &arrayDimensions); + QMultiDimensionalArray(const QVector<quint32> &arrayDimensions); + ~QMultiDimensionalArray(); + + QVariantList valueArray() const; + QVariantList &valueArrayRef(); + void setValueArray(const QVariantList &valueArray); + + int arrayIndex(const QVector<quint32> &indices) const; + QVariant value(const QVector<quint32> &indices) const; + bool setValue(const QVector<quint32> &indices, const QVariant &value); + + bool isValid() const; + + QVector<quint32> arrayDimensions() const; + void setArrayDimensions(const QVector<quint32> &arrayDimensions); + + bool operator==(const QOpcUa::QMultiDimensionalArray &other) const; + + operator QVariant() const; + +private: + QSharedDataPointer<QOpcUa::QMultiDimensionalArrayData> data; +}; + } Q_DECLARE_TYPEINFO(QOpcUa::Types, Q_PRIMITIVE_TYPE); @@ -1138,5 +1171,6 @@ Q_DECLARE_METATYPE(QOpcUa::QApplicationDescription) Q_DECLARE_METATYPE(QOpcUa::QEndpointDescription) Q_DECLARE_METATYPE(QOpcUa::QArgument) Q_DECLARE_METATYPE(QOpcUa::QExtensionObject) +Q_DECLARE_METATYPE(QOpcUa::QMultiDimensionalArray) #endif // QOPCUATYPE diff --git a/src/opcua/doc/src/qtopcua.qdoc b/src/opcua/doc/src/qtopcua.qdoc index be8e693..a0c9f08 100644 --- a/src/opcua/doc/src/qtopcua.qdoc +++ b/src/opcua/doc/src/qtopcua.qdoc @@ -109,6 +109,10 @@ \li X \li X \row + \li Multidimensional arrays + \li X + \li + \row \li Browse \li X \li X @@ -351,5 +355,4 @@ \code Unified Automation C++ SDK ............. yes \endcode - */ diff --git a/src/plugins/opcua/open62541/qopen62541valueconverter.cpp b/src/plugins/opcua/open62541/qopen62541valueconverter.cpp index 5662695..2ff527f 100644 --- a/src/plugins/opcua/open62541/qopen62541valueconverter.cpp +++ b/src/plugins/opcua/open62541/qopen62541valueconverter.cpp @@ -95,6 +95,20 @@ UA_Variant toOpen62541Variant(const QVariant &value, QOpcUa::Types type) UA_Variant open62541value; UA_Variant_init(&open62541value); + if (value.canConvert<QOpcUa::QMultiDimensionalArray>()) { + QOpcUa::QMultiDimensionalArray data = value.value<QOpcUa::QMultiDimensionalArray>(); + UA_Variant result = toOpen62541Variant(data.valueArray(), type); + + if (!data.arrayDimensions().isEmpty()) { + // Ensure that the array dimensions size is < UINT32_MAX + if (static_cast<quint64>(data.arrayDimensions().size()) > std::numeric_limits<quint32>::max()) + return open62541value; + result.arrayDimensionsSize = data.arrayDimensions().size(); + result.arrayDimensions = static_cast<UA_UInt32 *>(UA_Array_new(result.arrayDimensionsSize, &UA_TYPES[UA_TYPES_UINT32])); + std::copy(data.arrayDimensions().constBegin(), data.arrayDimensions().constEnd(), result.arrayDimensions); + } + return result; + } if (value.type() == QVariant::List && value.toList().size() == 0) return open62541value; @@ -455,6 +469,16 @@ QVariant arrayToQVariant(const UA_Variant &var, QMetaType::Type type) tempVar.convert(type); list.append(tempVar); } + + if (var.arrayDimensionsSize > 0) { + // Ensure that the array dimensions fit in a QVector + if (var.arrayDimensionsSize > static_cast<quint64>(std::numeric_limits<int>::max())) + return QOpcUa::QMultiDimensionalArray(); + QVector<quint32> arrayDimensions; + std::copy(var.arrayDimensions, var.arrayDimensions+var.arrayDimensionsSize, std::back_inserter(arrayDimensions)); + return QOpcUa::QMultiDimensionalArray(list, arrayDimensions); + } + if (list.size() == 1) return list.at(0); else |