summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitattributes6
-rw-r--r--.qmake.conf2
-rw-r--r--src/serialbus/qmodbusdeviceidentification.cpp264
-rw-r--r--src/serialbus/qmodbusdeviceidentification.h122
-rw-r--r--src/serialbus/qmodbuspdu.cpp55
-rw-r--r--src/serialbus/qmodbuspdu.h13
-rw-r--r--src/serialbus/qmodbusrtuserialmaster.cpp32
-rw-r--r--src/serialbus/qmodbusrtuserialmaster.h3
-rw-r--r--src/serialbus/qmodbusrtuserialmaster_p.h47
-rw-r--r--src/serialbus/qmodbusserver.cpp133
-rw-r--r--src/serialbus/qmodbusserver.h1
-rw-r--r--src/serialbus/qmodbusserver_p.h1
-rw-r--r--src/serialbus/serialbus.pro8
-rw-r--r--tests/auto/auto.pro4
-rw-r--r--tests/auto/qmodbusdeviceidentification/qmodbusdeviceidentification.pro7
-rw-r--r--tests/auto/qmodbusdeviceidentification/tst_qmodbusdeviceidentification.cpp172
-rw-r--r--tests/auto/qmodbuspdu/tst_qmodbuspdu.cpp41
-rw-r--r--tests/auto/qmodbusrtuserialmaster/qmodbusrtuserialmaster.pro8
-rw-r--r--tests/auto/qmodbusrtuserialmaster/tst_qmodbusrtuserialmaster.cpp61
-rw-r--r--tests/auto/qmodbusserver/tst_qmodbusserver.cpp46
20 files changed, 993 insertions, 33 deletions
diff --git a/.gitattributes b/.gitattributes
index a0932db..018cf2c 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,3 @@
-.tag export-subst
-.gitignore export-ignore
-.gitattributes export-ignore
+.tag export-subst
+.gitignore export-ignore
+.gitattributes export-ignore
diff --git a/.qmake.conf b/.qmake.conf
index 5ec08b6..40e5787 100644
--- a/.qmake.conf
+++ b/.qmake.conf
@@ -1,4 +1,4 @@
load(qt_build_config)
CONFIG += warning_clean
-MODULE_VERSION = 5.6.1
+MODULE_VERSION = 5.7.0
diff --git a/src/serialbus/qmodbusdeviceidentification.cpp b/src/serialbus/qmodbusdeviceidentification.cpp
new file mode 100644
index 0000000..5ae3f54
--- /dev/null
+++ b/src/serialbus/qmodbusdeviceidentification.cpp
@@ -0,0 +1,264 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtSerialBus module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qmodbusdeviceidentification.h"
+#include "qmodbus_symbols_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QModbusDeviceIdentification
+ \inmodule QtSerialBus
+ \since 5.7
+
+ \brief The QModbusDeviceIdentification is a container class representing
+ the physical and functional description of a Modbus server.
+
+ The Device Identification interface is modeled as an address space composed
+ of a set of addressable data elements. The data elements are called objects
+ and an \l ObjectId identifies them.
+*/
+
+/*!
+ \enum QModbusDeviceIdentification::ObjectId
+
+ This enum describes the possible server objects. The interface consists of
+ three categories of objects:
+
+ Basic Device Identification. All objects of this category are mandatory.
+
+ \value VendorNameObjectId The vendor name of the device.
+ \value ProductCodeObjectId The product code of the device.
+ \value MajorMinorRevisionObjectId The product version numbering.
+
+ Regular Device Identification. All objects of this category are standard
+ defined and optional.
+
+ \value VendorUrlObjectId The vendor URL of the device.
+ \value ProductNameObjectId The product name of the device.
+ \value ModelNameObjectId The model name of the device.
+ \value UserApplicationNameObjectId The user application name of the device.
+
+ Reserved range (i.e., ReservedObjectId >= ObjectId < ProductDependentObjectId).
+ Do not use.
+
+ \value ReservedObjectId First value of reserved object Ids.
+
+ Extended Device Identification. All of these data are device dependent and
+ optional.
+
+ \value ProductDependentObjectId First possible value of product dependent
+ identifiers.
+ \value UndefinedObjectId Do not use.
+*/
+
+/*!
+ \enum QModbusDeviceIdentification::ReadDeviceIdCode
+
+ Defines the access type of the read identification request.
+
+ Stream access:
+
+ \value BasicReadDeviceIdCode Request to get the basic device
+ identification.
+ \value RegularReadDeviceIdCode Request to get the regular device
+ identification.
+ \value ExtendedReadDeviceIdCode Request to get the extended device
+ identification.
+
+ Individual access:
+
+ \value IndividualReadDeviceIdCode Request to get one specific identification
+ object.
+*/
+
+/*!
+ \enum QModbusDeviceIdentification::ConformityLevel
+
+ Defines the identification conformity level of the device and type of
+ supported access.
+
+ \value BasicConformityLevel Basic identification (stream access).
+ \value RegularConformityLevel Regular identification (stream access).
+ \value ExtendedConformityLevel Extended identification (stream access).
+ \value BasicIndividualConformityLevel Basic identification (stream access and
+ individual access).
+ \value RegularIndividualConformityLevel Regular identification (stream access
+ and individual access).
+ \value ExtendedIndividualConformityLevel Extended identification (stream access
+ and individual access).
+
+ \sa ReadDeviceIdCode
+*/
+
+/*!
+ \fn QModbusDeviceIdentification::QModbusDeviceIdentification()
+
+ Constructs an invalid QModbusDeviceIdentification object.
+*/
+
+/*!
+ \fn bool QModbusDeviceIdentification::isValid() const
+
+ Returns \c true if the device identification object is valid; otherwise
+ \c false.
+
+ A device identification object is considered valid if \l ProductNameObjectId,
+ \l ProductCodeObjectId and \l MajorMinorRevisionObjectId are set to a
+ non-empty value. Still the object can contain valid object id's and
+ associated data.
+
+ \note A default constructed device identification object is invalid.
+*/
+
+/*!
+ \fn QList<int> QModbusDeviceIdentification::objectIds() const
+
+ Returns a list containing all the object id's in the
+ \c QModbusDeviceIdentification object in ascending order.
+
+ \sa ObjectId
+*/
+
+/*!
+ \fn void QModbusDeviceIdentification::remove(uint objectId)
+
+ Removes the item for the given \a objectId.
+
+ \sa ObjectId
+*/
+
+/*!
+ \fn bool QModbusDeviceIdentification::contains(uint objectId) const
+
+ Returns \c true if there is an item for the given \a objectId; otherwise \c
+ false.
+
+ \sa ObjectId
+*/
+
+/*!
+ \fn QByteArray QModbusDeviceIdentification::value(uint objectId) const
+
+ Returns the value associated with the \a objectId. If there is no item with
+ the \a objectId, the function returns a \l{default-constructed value}.
+
+ \sa ObjectId
+*/
+
+/*!
+ \fn bool QModbusDeviceIdentification::insert(uint objectId, const QByteArray &value)
+
+ Inserts a new item with the \a objectId and a value of \a value. If there
+ is already an item with the \a objectId, that item's value is replaced with
+ \a value.
+
+ Returns \c true if the size of \a value is less than 245 bytes and the
+ \a objectId is less then \l QModbusDeviceIdentification::UndefinedObjectId.
+
+ \sa ObjectId
+*/
+
+/*!
+ \fn ConformityLevel QModbusDeviceIdentification::conformityLevel() const
+
+ Returns the identification conformity level of the device and type of
+ supported access.
+*/
+
+/*!
+ \fn void QModbusDeviceIdentification::setConformityLevel(ConformityLevel level)
+
+ Sets the identification conformity level of the device and type of
+ supported access to \a level.
+*/
+
+/*!
+ Converts the byte array \a ba to a QModbusDeviceIdentification object.
+
+ \note: The returned object might be empty or even invalid if some error
+ occurs while processing the byte array.
+
+ \sa isValid()
+*/
+QModbusDeviceIdentification QModbusDeviceIdentification::fromByteArray(const QByteArray &ba)
+{
+ QModbusDeviceIdentification qmdi;
+ // header 6 bytes: mei type + read device id + conformity level + more follows
+ // + next object id + number of object
+ // data 2 bytes: + object id + object size of the first object -> 8
+ if (ba.size() >= 8) {
+ if (ba[0] != EncapsulatedInterfaceTransport::ReadDeviceIdentification)
+ return qmdi;
+ if (ba.size() < (8 + quint8(ba[7])))
+ return qmdi;
+ } else {
+ return qmdi;
+ }
+
+ ConformityLevel level = ConformityLevel(quint8(ba[2]));
+ switch (level) {
+ case BasicConformityLevel:
+ case RegularConformityLevel:
+ case ExtendedConformityLevel:
+ case BasicIndividualConformityLevel:
+ case RegularIndividualConformityLevel:
+ case ExtendedIndividualConformityLevel:
+ qmdi.setConformityLevel(level);
+ break;
+ default:
+ return qmdi;
+ }
+
+ quint8 numOfObjects = ba[5];
+ quint8 objectSize = quint8(ba[7]);
+ qmdi.insert(quint8(ba[6]), ba.mid(8, objectSize));
+
+ // header + object id + object size + second object id (9 bytes) + first object size
+ int nextSizeField = 9 + objectSize;
+ for (int i = 1; i < numOfObjects; ++i) {
+ if (ba.size() <= nextSizeField)
+ break;
+ objectSize = ba[nextSizeField];
+ if (ba.size() < (nextSizeField + objectSize))
+ break;
+ qmdi.insert(quint8(ba[nextSizeField - 1]), ba.mid(nextSizeField + 1, objectSize));
+ nextSizeField += objectSize + 2; // object size + object id field + object size field
+ }
+ return qmdi;
+}
+
+QT_END_NAMESPACE
diff --git a/src/serialbus/qmodbusdeviceidentification.h b/src/serialbus/qmodbusdeviceidentification.h
new file mode 100644
index 0000000..e7217e5
--- /dev/null
+++ b/src/serialbus/qmodbusdeviceidentification.h
@@ -0,0 +1,122 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtSerialBus module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QMODBUSDEVICEIDENTIFICATION_P_H
+#define QMODBUSDEVICEIDENTIFICATION_P_H
+
+#include <QtCore/qmap.h>
+#include <QtCore/qmetatype.h>
+#include <QtSerialBus/qserialbusglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+class QModbusDeviceIdentification
+{
+public:
+ enum ObjectId {
+ /* Basic mandatory */
+ VendorNameObjectId = 0x00,
+ ProductCodeObjectId = 0x01,
+ MajorMinorRevisionObjectId = 0x02,
+
+ /* Regular optional */
+ VendorUrlObjectId = 0x03,
+ ProductNameObjectId = 0x04,
+ ModelNameObjectId = 0x05,
+ UserApplicationNameObjectId = 0x06,
+ ReservedObjectId = 0x07,
+
+ /* Extended optional */
+ ProductDependentObjectId = 0x80,
+
+ UndefinedObjectId = 0x100
+ };
+
+ enum ReadDeviceIdCode {
+ BasicReadDeviceIdCode = 0x01,
+ RegularReadDeviceIdCode = 0x02,
+ ExtendedReadDeviceIdCode = 0x03,
+ IndividualReadDeviceIdCode = 0x04
+ };
+
+ enum ConformityLevel {
+ BasicConformityLevel = 0x01,
+ RegularConformityLevel = 0x02,
+ ExtendedConformityLevel = 0x03,
+ BasicIndividualConformityLevel = 0x81,
+ RegularIndividualConformityLevel = 0x82,
+ ExtendedIndividualConformityLevel = 0x83
+ };
+
+ QModbusDeviceIdentification() = default;
+
+ bool isValid() const {
+ return !m_objects.value(VendorNameObjectId).isEmpty()
+ && !m_objects.value(ProductCodeObjectId).isEmpty()
+ && !m_objects.value(MajorMinorRevisionObjectId).isEmpty();
+ }
+
+ QList<int> objectIds() const { return m_objects.keys(); }
+ void remove(uint objectId) { m_objects.remove(objectId); }
+ bool contains(uint objectId) const { return m_objects.contains(objectId); }
+
+ bool insert(uint objectId, const QByteArray &data) {
+ if ((data.size() > 245) || (objectId >= ObjectId::UndefinedObjectId))
+ return false;
+ m_objects[objectId] = data;
+ return true;
+ }
+ QByteArray value(uint objectId) const { return m_objects.value(objectId); }
+
+ ConformityLevel conformityLevel() const { return m_conformityLevel; }
+ void setConformityLevel(ConformityLevel level) { m_conformityLevel = level; }
+
+ static Q_SERIALBUS_EXPORT QModbusDeviceIdentification fromByteArray(const QByteArray &ba);
+
+private:
+ QMap<int, QByteArray> m_objects;
+ ConformityLevel m_conformityLevel = BasicConformityLevel;
+};
+Q_DECLARE_TYPEINFO(QModbusDeviceIdentification, Q_MOVABLE_TYPE);
+Q_DECLARE_TYPEINFO(QModbusDeviceIdentification::ObjectId, Q_PRIMITIVE_TYPE);
+Q_DECLARE_TYPEINFO(QModbusDeviceIdentification::ReadDeviceIdCode, Q_PRIMITIVE_TYPE);
+Q_DECLARE_TYPEINFO(QModbusDeviceIdentification::ConformityLevel, Q_PRIMITIVE_TYPE);
+
+QT_END_NAMESPACE
+
+Q_DECLARE_METATYPE(QModbusDeviceIdentification)
+
+#endif // QMODBUSDEVICEIDENTIFICATION_P_H
diff --git a/src/serialbus/qmodbuspdu.cpp b/src/serialbus/qmodbuspdu.cpp
index 2f20747..ef8d93c 100644
--- a/src/serialbus/qmodbuspdu.cpp
+++ b/src/serialbus/qmodbuspdu.cpp
@@ -38,9 +38,16 @@
#include "qmodbus_symbols_p.h"
#include <QtCore/qdebug.h>
+#include <QtCore/qhash.h>
QT_BEGIN_NAMESPACE
+using ReqSizeCalc = QHash<quint8, QModbusRequest::CalcFuncPtr>;
+Q_GLOBAL_STATIC(ReqSizeCalc, requestSizeCalculators);
+
+using ResSizeCalc = QHash<quint8, QModbusResponse::CalcFuncPtr>;
+Q_GLOBAL_STATIC(ResSizeCalc, responseSizeCalculators);
+
namespace Private {
enum struct Type {
@@ -525,9 +532,17 @@ int QModbusRequest::minimumDataSize(const QModbusRequest &request)
Calculates the expected data size for \a request based on the request's
function code and data. Returns the full size of the request's data part;
\c {-1} if the size could not be properly calculated.
+
+ \sa minimumDataSize
+ \sa registerDataSizeCalculator
*/
int QModbusRequest::calculateDataSize(const QModbusRequest &request)
{
+ if (requestSizeCalculators.exists()) {
+ if (auto ptr = requestSizeCalculators()->value(request.functionCode(), nullptr))
+ return ptr(request);
+ }
+
if (request.isException())
return 1;
@@ -569,6 +584,22 @@ int QModbusRequest::calculateDataSize(const QModbusRequest &request)
}
/*!
+ This function registers a user-defined implementation to calculate the
+ request data size for function code \a fc. It can be used to extend or
+ override the implementation inside \l QModbusRequest::calculateDataSize().
+
+ The \c CalcFuncPtr is a typedef for a pointer to a custom \a calculator
+ function with the following signature:
+ \code
+ int myCalculateDataSize(const QModbusRequest &pdu);
+ \endcode
+*/
+void QModbusRequest::registerDataSizeCalculator(FunctionCode fc, CalcFuncPtr calculator)
+{
+ requestSizeCalculators()->insert(fc, calculator);
+}
+
+/*!
\relates QModbusRequest
Reads a \a pdu from the \a stream and returns a reference to the stream.
@@ -649,9 +680,17 @@ int QModbusResponse::minimumDataSize(const QModbusResponse &response)
Calculates the expected data size for \a response, based on the response's
function code and data. Returns the full size of the response's data part;
\c {-1} if the size could not be properly calculated.
+
+ \sa minimumDataSize
+ \sa registerDataSizeCalculator
*/
int QModbusResponse::calculateDataSize(const QModbusResponse &response)
{
+ if (responseSizeCalculators.exists()) {
+ if (auto ptr = responseSizeCalculators()->value(response.functionCode(), nullptr))
+ return ptr(response);
+ }
+
if (response.isException())
return 1;
@@ -723,6 +762,22 @@ int QModbusResponse::calculateDataSize(const QModbusResponse &response)
}
/*!
+ This function registers a user-defined implementation to calculate the
+ response data size for function code \a fc. It can be used to extend or
+ override the implementation inside \l QModbusResponse::calculateDataSize().
+
+ The \c CalcFuncPtr is a typedef for a pointer to a custom \a calculator
+ function with the following signature:
+ \code
+ int myCalculateDataSize(const QModbusResponse &pdu);
+ \endcode
+*/
+void QModbusResponse::registerDataSizeCalculator(FunctionCode fc, CalcFuncPtr calculator)
+{
+ responseSizeCalculators()->insert(fc, calculator);
+}
+
+/*!
\relates QModbusResponse
Reads a \a pdu from the \a stream and returns a reference to the stream.
diff --git a/src/serialbus/qmodbuspdu.h b/src/serialbus/qmodbuspdu.h
index 12fd33f..19a2d45 100644
--- a/src/serialbus/qmodbuspdu.h
+++ b/src/serialbus/qmodbuspdu.h
@@ -160,9 +160,10 @@ private:
template<typename ... Args> void encode(Args ... newData) {
m_data.clear();
- if (sizeof...(Args)) {
+ Q_CONSTEXPR quint32 argCount = sizeof...(Args);
+ if (argCount > 0) {
QDataStream stream(&m_data, QIODevice::WriteOnly);
- char tmp[1024] = { (encode(&stream, newData), void(), '0')... };
+ char tmp[argCount] = { (encode(&stream, newData), void(), '0')... };
Q_UNUSED(tmp)
}
}
@@ -170,7 +171,7 @@ private:
Q_CONSTEXPR quint32 argCount = sizeof...(Args);
if (argCount > 0 && !m_data.isEmpty()) {
QDataStream stream(m_data);
- char tmp[1024] = { (decode(&stream, newData), void(), '0')... };
+ char tmp[argCount] = { (decode(&stream, newData), void(), '0')... };
Q_UNUSED(tmp)
}
}
@@ -198,6 +199,9 @@ public:
Q_SERIALBUS_EXPORT static int minimumDataSize(const QModbusRequest &pdu);
Q_SERIALBUS_EXPORT static int calculateDataSize(const QModbusRequest &pdu);
+ using CalcFuncPtr = decltype(&calculateDataSize);
+ Q_SERIALBUS_EXPORT static void registerDataSizeCalculator(FunctionCode fc, CalcFuncPtr func);
+
template <typename ... Args>
QModbusRequest(FunctionCode code, Args ... newData)
: QModbusPdu(code, newData...)
@@ -220,6 +224,9 @@ public:
Q_SERIALBUS_EXPORT static int minimumDataSize(const QModbusResponse &pdu);
Q_SERIALBUS_EXPORT static int calculateDataSize(const QModbusResponse &pdu);
+ using CalcFuncPtr = decltype(&calculateDataSize);
+ Q_SERIALBUS_EXPORT static void registerDataSizeCalculator(FunctionCode fc, CalcFuncPtr func);
+
template <typename ... Args>
QModbusResponse(FunctionCode code, Args ... newData)
: QModbusPdu(code, newData...)
diff --git a/src/serialbus/qmodbusrtuserialmaster.cpp b/src/serialbus/qmodbusrtuserialmaster.cpp
index c646ee7..331e0ee 100644
--- a/src/serialbus/qmodbusrtuserialmaster.cpp
+++ b/src/serialbus/qmodbusrtuserialmaster.cpp
@@ -76,6 +76,38 @@ QModbusRtuSerialMaster::~QModbusRtuSerialMaster()
}
/*!
+ \since 5.7
+
+ Returns the amount of microseconds for the silent interval between two
+ consecutive Modbus messages.
+
+ \sa setInterFrameDelay()
+*/
+int QModbusRtuSerialMaster::interFrameDelay() const
+{
+ Q_D(const QModbusRtuSerialMaster);
+ return d->m_interFrameDelayMilliseconds * 1000;
+}
+
+/*!
+ \since 5.7
+
+ Sets the amount of \a microseconds for the silent interval between two
+ consecutive Modbus messages. By default, the class implementation will use
+ a pre-calculated value according to the Modbus specification. A active or
+ running connection is not affected by such delay changes.
+
+ \note If \a microseconds is set to -1 or \a microseconds is less than the
+ pre-calculated delay then this pre-calculated value is used as frame delay.
+*/
+void QModbusRtuSerialMaster::setInterFrameDelay(int microseconds)
+{
+ Q_D(QModbusRtuSerialMaster);
+ d->m_interFrameDelayMilliseconds = qCeil(qreal(microseconds) / 1000.);
+ d->calculateInterFrameDelay();
+}
+
+/*!
\internal
*/
QModbusRtuSerialMaster::QModbusRtuSerialMaster(QModbusRtuSerialMasterPrivate &dd, QObject *parent)
diff --git a/src/serialbus/qmodbusrtuserialmaster.h b/src/serialbus/qmodbusrtuserialmaster.h
index 7f879f5..ebdf21f 100644
--- a/src/serialbus/qmodbusrtuserialmaster.h
+++ b/src/serialbus/qmodbusrtuserialmaster.h
@@ -52,6 +52,9 @@ public:
explicit QModbusRtuSerialMaster(QObject *parent = Q_NULLPTR);
~QModbusRtuSerialMaster();
+ int interFrameDelay() const;
+ void setInterFrameDelay(int microseconds);
+
protected:
QModbusRtuSerialMaster(QModbusRtuSerialMasterPrivate &dd, QObject *parent = Q_NULLPTR);
diff --git a/src/serialbus/qmodbusrtuserialmaster_p.h b/src/serialbus/qmodbusrtuserialmaster_p.h
index 022c902..d3ddf1d 100644
--- a/src/serialbus/qmodbusrtuserialmaster_p.h
+++ b/src/serialbus/qmodbusrtuserialmaster_p.h
@@ -157,7 +157,7 @@ public:
m_state = Schedule; // reschedule, even if empty
m_serialPort->clear(QSerialPort::AllDirections);
- QTimer::singleShot(m_timeoutThreeDotFiveMs, [this]() { processQueue(); });
+ QTimer::singleShot(m_interFrameDelayMilliseconds, [this]() { processQueue(); });
});
using TypeId = void (QSerialPort::*)(QSerialPort::SerialPortError);
@@ -223,6 +223,28 @@ public:
});
}
+ /*!
+ According to the Modbus specification, in RTU mode message frames
+ are separated by a silent interval of at least 3.5 character times.
+ Calculate the timeout if we are less than 19200 baud, use a fixed
+ timeout for everything equal or greater than 19200 baud.
+ If the user set the timeout to be longer than the calculated one,
+ we'll keep the user defined.
+ */
+ void calculateInterFrameDelay() {
+ // The spec recommends a timeout value of 1.750 msec. Without such
+ // precise single-shot timers use a approximated value of 1.750 msec.
+ int delayMilliSeconds = 2;
+ if (m_baudRate < 19200) {
+ // Example: 9600 baud, 11 bit per packet -> 872 char/sec
+ // so: 1000 ms / 872 char = 1.147 ms/char * 3.5 character
+ // Always round up because the spec requests at least 3.5 char.
+ delayMilliSeconds = qCeil(3500. / (qreal(m_baudRate) / 11.));
+ }
+ if (m_interFrameDelayMilliseconds < delayMilliSeconds)
+ m_interFrameDelayMilliseconds = delayMilliSeconds;
+ }
+
void setupEnvironment() {
if (m_serialPort) {
m_serialPort->setPortName(m_comPort);
@@ -232,20 +254,7 @@ public:
m_serialPort->setStopBits(m_stopBits);
}
- // According to the Modbus specification, in RTU mode message frames
- // are separated by a silent interval of at least 3.5 character times.
- // Calculate the timeout if we are less than 19200 baud, use a fixed
- // timeout for everything equal or greater than 19200 baud.
- if (m_baudRate < 19200) {
- // Example: 9600 baud, 11 bit per packet -> 872 char/sec
- // so: 1000 ms / 872 char = 1.147 ms/char * 3.5 character
- // Always round up because the spec requests at least 3.5 char.
- m_timeoutThreeDotFiveMs = qCeil(3500. / (qreal(m_baudRate) / 11.));
- } else {
- // The spec recommends a timeout value of 1.750 msec. Without such
- // precise single-shot timers use a approximated value of 1.750 msec.
- m_timeoutThreeDotFiveMs = 2;
- }
+ calculateInterFrameDelay();
responseBuffer.clear();
m_state = QModbusRtuSerialMasterPrivate::Idle;
@@ -254,7 +263,7 @@ public:
void scheduleNextRequest() {
m_state = Schedule;
m_serialPort->clear(QSerialPort::AllDirections);
- QTimer::singleShot(m_timeoutThreeDotFiveMs, [this]() { processQueue(); });
+ QTimer::singleShot(m_interFrameDelayMilliseconds, [this]() { processQueue(); });
}
QModbusReply *enqueueRequest(const QModbusRequest &request, int serverAddress,
@@ -322,7 +331,7 @@ public:
scheduleNextRequest();
} else {
m_serialPort->clear(QSerialPort::AllDirections);
- QTimer::singleShot(m_timeoutThreeDotFiveMs, [writeAdu]() { writeAdu(); });
+ QTimer::singleShot(m_interFrameDelayMilliseconds, [writeAdu]() { writeAdu(); });
}
} else {
qCDebug(QT_MODBUS) << "(RTU client) Send successful:" << m_current.requestPdu;
@@ -345,7 +354,7 @@ public:
} else {
m_state = Send;
m_serialPort->clear(QSerialPort::AllDirections);
- QTimer::singleShot(m_timeoutThreeDotFiveMs, [this, writeAdu]() { writeAdu(); });
+ QTimer::singleShot(m_interFrameDelayMilliseconds, [this, writeAdu]() { writeAdu(); });
}
break;
@@ -383,7 +392,7 @@ public:
QQueue<QueueElement> m_queue;
QSerialPort *m_serialPort = Q_NULLPTR;
- int m_timeoutThreeDotFiveMs = 2; // A approximated value of 1.750 msec.
+ int m_interFrameDelayMilliseconds = 2; // A approximated value of 1.750 msec.
};
QT_END_NAMESPACE
diff --git a/src/serialbus/qmodbusserver.cpp b/src/serialbus/qmodbusserver.cpp
index b049518..ffb673a 100644
--- a/src/serialbus/qmodbusserver.cpp
+++ b/src/serialbus/qmodbusserver.cpp
@@ -34,6 +34,7 @@
**
****************************************************************************/
+#include "qmodbusdeviceidentification.h"
#include "qmodbusserver.h"
#include "qmodbusserver_p.h"
#include "qmodbus_symbols_p.h"
@@ -79,6 +80,7 @@ Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)
\value ServerIdentifier The identifier of the server, \b not the server address. \c quint8
\value RunIndicatorStatus The run indicator of the server. \c quint8
\value AdditionalData The additional data of the server. \c QByteArray
+ \value DeviceIdentification The physical and functional description of the server. \c QModbusDeviceIdentification
User options:
@@ -189,6 +191,9 @@ int QModbusServer::serverAddress() const
\li Returns the server's additional data. This data is used as
addendum by the \l QModbusPdu::ReportServerId function code.
\row
+ \li \l QModbusServer::DeviceIdentification
+ \li Returns the server's physical and functional description.
+ \row
\li \l QModbusServer::UserOption
\li Returns the value of a user option.
@@ -218,6 +223,8 @@ QVariant QModbusServer::value(int option) const
return d->m_serverOptions.value(option, quint8(0xff));
case AdditionalData:
return d->m_serverOptions.value(option, QByteArray("Qt Modbus Server"));
+ case DeviceIdentification:
+ return d->m_serverOptions.value(option, QVariant());
};
if (option < UserOption)
@@ -288,6 +295,10 @@ QVariant QModbusServer::value(int option) const
response message size restrictions.
The default value preset is \c {Qt Modbus Server}.
\row
+ \li \l QModbusServer::DeviceIdentification
+ \li Sets the server's physical and functional description. By default
+ there is no additional device identification data set.
+ \row
\li \l QModbusServer::UserOption
\li Sets the value of a user option to \a newValue.
@@ -362,6 +373,11 @@ bool QModbusServer::setValue(int option, const QVariant &newValue)
d->m_serverOptions.insert(option, additionalData);
return true;
}
+ case DeviceIdentification:
+ if (!newValue.canConvert<QModbusDeviceIdentification>())
+ return false;
+ d->m_serverOptions.insert(option, newValue);
+ return true;
default:
break;
};
@@ -653,9 +669,8 @@ QModbusResponse QModbusServerPrivate::processRequest(const QModbusPdu &request)
return processReadWriteMultipleRegistersRequest(request);
case QModbusRequest::ReadFifoQueue:
return processReadFifoQueueRequest(request);
- case QModbusRequest::EncapsulatedInterfaceTransport: // TODO: Implement.
- return QModbusExceptionResponse(request.functionCode(),
- QModbusExceptionResponse::IllegalFunction);
+ case QModbusRequest::EncapsulatedInterfaceTransport:
+ return processEncapsulatedInterfaceTransportRequest(request);
default:
break;
}
@@ -1174,6 +1189,118 @@ QModbusResponse QModbusServerPrivate::processReadFifoQueueRequest(const QModbusR
fifoRegisters.values());
}
+QModbusResponse QModbusServerPrivate::processEncapsulatedInterfaceTransportRequest(
+ const QModbusRequest &request)
+{
+ CHECK_SIZE_LESS_THAN(request);
+ quint8 MEIType;
+ request.decodeData(&MEIType);
+
+ switch (MEIType) {
+ case EncapsulatedInterfaceTransport::CanOpenGeneralReference:
+ break;
+ case EncapsulatedInterfaceTransport::ReadDeviceIdentification: {
+ if (request.dataSize() != 3u) {
+ return QModbusExceptionResponse(request.functionCode(),
+ QModbusExceptionResponse::IllegalDataValue);
+ }
+
+ const QVariant tmp = q_func()->value(QModbusServer::DeviceIdentification);
+ if (tmp.isNull() || (!tmp.isValid())) {
+ // TODO: Is this correct?
+ return QModbusExceptionResponse(request.functionCode(),
+ QModbusExceptionResponse::ServerDeviceFailure);
+ }
+
+ QModbusDeviceIdentification objectPool = tmp.value<QModbusDeviceIdentification>();
+ if (!objectPool.isValid()) {
+ // TODO: Is this correct?
+ return QModbusExceptionResponse(request.functionCode(),
+ QModbusExceptionResponse::ServerDeviceFailure);
+ }
+
+ quint8 readDeviceIdCode, objectId;
+ request.decodeData(&MEIType, &readDeviceIdCode, &objectId);
+ if (!objectPool.contains(objectId)) {
+ // Individual access requires the object Id to be present, so we will always fail.
+ // For all other cases we will reevaluate object Id after we reset it as per spec.
+ objectId = QModbusDeviceIdentification::VendorNameObjectId;
+ if (readDeviceIdCode == QModbusDeviceIdentification::IndividualReadDeviceIdCode
+ || !objectPool.contains(objectId)) {
+ return QModbusExceptionResponse(request.functionCode(),
+ QModbusExceptionResponse::IllegalDataAddress);
+ }
+ }
+
+ auto payload = [MEIType, readDeviceIdCode, objectId, objectPool](int lastObjectId) {
+ // TODO: Take conformity level into account.
+ QByteArray payload(6, Qt::Uninitialized);
+ payload[0] = MEIType;
+ payload[1] = readDeviceIdCode;
+ payload[2] = quint8(objectPool.conformityLevel());
+ payload[3] = quint8(0x00); // no more follows
+ payload[4] = quint8(0x00); // next object id
+ payload[5] = quint8(0x00); // number of objects
+
+ const QList<int> objectIds = objectPool.objectIds();
+ foreach (int id, objectIds) {
+ if (id < objectId)
+ continue;
+ if (id > lastObjectId)
+ break;
+ const QByteArray object = objectPool.value(id);
+ QByteArray objectData(2, Qt::Uninitialized);
+ objectData[0] = id;
+ objectData[1] = quint8(object.size());
+ objectData += object;
+ if (payload.size() + objectData.size() > 253) {
+ payload[3] = quint8(0xff); // more follows
+ payload[4] = id; // next object id
+ break;
+ }
+ payload.append(objectData);
+ payload[5] = payload[5] + 1u; // number of objects
+ }
+ return payload;
+ };
+
+ switch (readDeviceIdCode) {
+ case QModbusDeviceIdentification::BasicReadDeviceIdCode:
+ // TODO: How to handle a valid Id <> VendorName ... MajorMinorRevision
+ return QModbusResponse(request.functionCode(),
+ payload(QModbusDeviceIdentification::MajorMinorRevisionObjectId));
+ case QModbusDeviceIdentification::RegularReadDeviceIdCode:
+ // TODO: How to handle a valid Id <> VendorUrl ... UserApplicationName
+ return QModbusResponse(request.functionCode(),
+ payload(QModbusDeviceIdentification::UserApplicationNameObjectId));
+ case QModbusDeviceIdentification::ExtendedReadDeviceIdCode:
+ // TODO: How to handle a valid Id < ProductDependent
+ return QModbusResponse(request.functionCode(),
+ payload(QModbusDeviceIdentification::UndefinedObjectId));
+ case QModbusDeviceIdentification::IndividualReadDeviceIdCode: {
+ // TODO: Take conformity level into account.
+ const QByteArray object = objectPool.value(objectId);
+ QByteArray header(8, Qt::Uninitialized);
+ header[0] = MEIType;
+ header[1] = readDeviceIdCode;
+ header[2] = quint8(objectPool.conformityLevel());
+ header[3] = quint8(0x00); // no more follows
+ header[4] = quint8(0x00); // next object id
+ header[5] = quint8(0x01); // number of objects
+ header[6] = objectId;
+ header[7] = quint8(object.length());
+ return QModbusResponse(request.functionCode(), QByteArray(header + object));
+ }
+ default:
+ return QModbusExceptionResponse(request.functionCode(),
+ QModbusExceptionResponse::IllegalDataValue);
+ }
+ } break;
+ }
+ return QModbusExceptionResponse(request.functionCode(),
+ QModbusExceptionResponse::IllegalFunction);
+}
+
void QModbusServerPrivate::storeModbusCommEvent(const QModbusCommEvent &eventByte)
{
// Inserts an event byte at the start of the event log. If the event log
diff --git a/src/serialbus/qmodbusserver.h b/src/serialbus/qmodbusserver.h
index b2a501e..40dd0e1 100644
--- a/src/serialbus/qmodbusserver.h
+++ b/src/serialbus/qmodbusserver.h
@@ -62,6 +62,7 @@ public:
ServerIdentifier,
RunIndicatorStatus,
AdditionalData,
+ DeviceIdentification,
// Reserved
UserOption = 0x100
};
diff --git a/src/serialbus/qmodbusserver_p.h b/src/serialbus/qmodbusserver_p.h
index a490af3..2618ccf 100644
--- a/src/serialbus/qmodbusserver_p.h
+++ b/src/serialbus/qmodbusserver_p.h
@@ -111,6 +111,7 @@ public:
QModbusResponse processMaskWriteRegisterRequest(const QModbusRequest &request);
QModbusResponse processReadWriteMultipleRegistersRequest(const QModbusRequest &request);
QModbusResponse processReadFifoQueueRequest(const QModbusRequest &request);
+ QModbusResponse processEncapsulatedInterfaceTransportRequest(const QModbusRequest &request);
void storeModbusCommEvent(const QModbusCommEvent &eventByte);
diff --git a/src/serialbus/serialbus.pro b/src/serialbus/serialbus.pro
index eadc2a1..02348da 100644
--- a/src/serialbus/serialbus.pro
+++ b/src/serialbus/serialbus.pro
@@ -21,7 +21,8 @@ PUBLIC_HEADERS += \
qmodbustcpclient.h \
qmodbustcpserver.h \
qmodbusrtuserialslave.h \
- qmodbuspdu.h
+ qmodbuspdu.h \
+ qmodbusdeviceidentification.h
PRIVATE_HEADERS += \
qcanbusdevice_p.h \
@@ -34,7 +35,7 @@ PRIVATE_HEADERS += \
qmodbusrtuserialslave_p.h \
qmodbus_symbols_p.h \
qmodbuscommevent_p.h \
- qmodbusadu_p.h
+ qmodbusadu_p.h \
SOURCES += \
qcanbusdevice.cpp \
@@ -50,7 +51,8 @@ SOURCES += \
qmodbustcpclient.cpp \
qmodbustcpserver.cpp \
qmodbusrtuserialslave.cpp \
- qmodbuspdu.cpp
+ qmodbuspdu.cpp \
+ qmodbusdeviceidentification.cpp
HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index 7f11ff3..2542d65 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -10,7 +10,9 @@ SUBDIRS += cmake \
qmodbusclient \
qmodbusserver \
qmodbuscommevent \
- qmodbusadu
+ qmodbusadu \
+ qmodbusdeviceidentification \
+ qmodbusrtuserialmaster
qcanbus.depends += plugins
qcanbusdevice.depends += plugins
diff --git a/tests/auto/qmodbusdeviceidentification/qmodbusdeviceidentification.pro b/tests/auto/qmodbusdeviceidentification/qmodbusdeviceidentification.pro
new file mode 100644
index 0000000..721b521
--- /dev/null
+++ b/tests/auto/qmodbusdeviceidentification/qmodbusdeviceidentification.pro
@@ -0,0 +1,7 @@
+QT = core testlib serialbus
+TARGET = tst_qmodbusdeviceidentification
+CONFIG += testcase c++11
+
+CONFIG -= app_bundle
+
+SOURCES += tst_qmodbusdeviceidentification.cpp
diff --git a/tests/auto/qmodbusdeviceidentification/tst_qmodbusdeviceidentification.cpp b/tests/auto/qmodbusdeviceidentification/tst_qmodbusdeviceidentification.cpp
new file mode 100644
index 0000000..582b46d
--- /dev/null
+++ b/tests/auto/qmodbusdeviceidentification/tst_qmodbusdeviceidentification.cpp
@@ -0,0 +1,172 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtSerialBus module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtSerialBus/qmodbusdeviceidentification.h>
+#include <QtSerialBus/qmodbuspdu.h>
+
+#include <QtTest/QtTest>
+
+class tst_QModbusDeviceIdentification : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void testConstructor()
+ {
+ QModbusDeviceIdentification qmdi;
+ QCOMPARE(qmdi.isValid(), false);
+ QCOMPARE(qmdi.objectIds().isEmpty(), true);
+ for (int i = QModbusDeviceIdentification::VendorNameObjectId;
+ i < QModbusDeviceIdentification::UndefinedObjectId; ++i) {
+ QCOMPARE(qmdi.contains(i), false);
+ qmdi.remove(i); // force crash if the behavior changes
+ QCOMPARE(qmdi.value(i), QByteArray());
+ }
+ QCOMPARE(qmdi.conformityLevel(), QModbusDeviceIdentification::BasicConformityLevel);
+ }
+
+ void testIsValid()
+ {
+ QModbusDeviceIdentification qmdi;
+ QCOMPARE(qmdi.isValid(), false);
+ QCOMPARE(qmdi.insert(QModbusDeviceIdentification::ReservedObjectId, "Reserved"), true);
+ QCOMPARE(qmdi.isValid(), false);
+ QCOMPARE(qmdi.insert(QModbusDeviceIdentification::VendorNameObjectId,
+ "Company identification"), true);
+ QCOMPARE(qmdi.isValid(), false);
+ QCOMPARE(qmdi.insert(QModbusDeviceIdentification::ProductCodeObjectId, "Product code"),
+ true);
+ QCOMPARE(qmdi.isValid(), false);
+ QCOMPARE(qmdi.insert(QModbusDeviceIdentification::MajorMinorRevisionObjectId, "V2.11"),
+ true);
+ QCOMPARE(qmdi.isValid(), true);
+ QCOMPARE(qmdi.insert(QModbusDeviceIdentification::MajorMinorRevisionObjectId, ""), true);
+ QCOMPARE(qmdi.isValid(), false);
+ QCOMPARE(qmdi.insert(QModbusDeviceIdentification::MajorMinorRevisionObjectId, "V2.11"),
+ true);
+ QCOMPARE(qmdi.isValid(), true);
+ }
+
+ void testRemoveAndContains()
+ {
+ QModbusDeviceIdentification qmdi;
+ QCOMPARE(qmdi.contains(QModbusDeviceIdentification::ReservedObjectId), false);
+ QCOMPARE(qmdi.insert(QModbusDeviceIdentification::ReservedObjectId, "Reserved"), true);
+ QCOMPARE(qmdi.contains(QModbusDeviceIdentification::ReservedObjectId), true);
+ qmdi.remove(QModbusDeviceIdentification::ReservedObjectId);
+ QCOMPARE(qmdi.contains(QModbusDeviceIdentification::ReservedObjectId), false);
+ }
+
+ void testInsertAndValue()
+ {
+ QModbusDeviceIdentification qmdi;
+ QCOMPARE(qmdi.insert(QModbusDeviceIdentification::ProductDependentObjectId, "Test"), true);
+ QCOMPARE(qmdi.value(QModbusDeviceIdentification::ProductDependentObjectId),
+ QByteArray("Test"));
+ QCOMPARE(qmdi.insert(QModbusDeviceIdentification::ProductDependentObjectId,
+ QByteArray(246, '@')), false);
+ QCOMPARE(qmdi.value(QModbusDeviceIdentification::ProductDependentObjectId),
+ QByteArray("Test"));
+ QCOMPARE(qmdi.insert(QModbusDeviceIdentification::ProductDependentObjectId,
+ QByteArray(245, '@')), true);
+ QCOMPARE(qmdi.value(QModbusDeviceIdentification::ProductDependentObjectId),
+ QByteArray(245, '@'));
+ QCOMPARE(qmdi.insert(QModbusDeviceIdentification::UndefinedObjectId, "Test"), false);
+ QCOMPARE(qmdi.value(QModbusDeviceIdentification::UndefinedObjectId), QByteArray());
+ }
+
+ void testConformityLevel()
+ {
+ QModbusDeviceIdentification qmdi;
+ QCOMPARE(qmdi.conformityLevel(), QModbusDeviceIdentification::BasicConformityLevel);
+ qmdi.setConformityLevel(QModbusDeviceIdentification::BasicIndividualConformityLevel);
+ QCOMPARE(qmdi.conformityLevel(), QModbusDeviceIdentification::BasicIndividualConformityLevel);
+ }
+
+ void testConstructFromByteArray()
+ {
+ const QByteArray vendorNameObject = "Company identification";
+ QModbusResponse r(QModbusResponse::EncapsulatedInterfaceTransport,
+ QByteArray::fromHex("0e01010000010016") + vendorNameObject);
+
+ {
+ QModbusDeviceIdentification qmdi = QModbusDeviceIdentification::fromByteArray(r.data());
+ QCOMPARE(qmdi.isValid(), false);
+ QCOMPARE(qmdi.objectIds(), QList<int>() << QModbusDeviceIdentification::VendorNameObjectId);
+ QCOMPARE(qmdi.contains(QModbusDeviceIdentification::VendorNameObjectId), true);
+ QCOMPARE(qmdi.value(QModbusDeviceIdentification::VendorNameObjectId), vendorNameObject);
+ QCOMPARE(qmdi.conformityLevel(), QModbusDeviceIdentification::BasicConformityLevel);
+ }
+
+ const QByteArray productCodeObject = "Product code";
+ r.setData(QByteArray::fromHex("0e01010000020016") + vendorNameObject
+ + QByteArray::fromHex("010c") + productCodeObject);
+ {
+ QModbusDeviceIdentification qmdi = QModbusDeviceIdentification::fromByteArray(r.data());
+ QCOMPARE(qmdi.isValid(), false);
+ QCOMPARE(qmdi.objectIds(), QList<int>() << QModbusDeviceIdentification::VendorNameObjectId
+ << QModbusDeviceIdentification::ProductCodeObjectId);
+ QCOMPARE(qmdi.contains(QModbusDeviceIdentification::VendorNameObjectId), true);
+ QCOMPARE(qmdi.contains(QModbusDeviceIdentification::ProductCodeObjectId), true);
+ QCOMPARE(qmdi.value(QModbusDeviceIdentification::VendorNameObjectId), vendorNameObject);
+ QCOMPARE(qmdi.value(QModbusDeviceIdentification::ProductCodeObjectId), productCodeObject);
+ QCOMPARE(qmdi.conformityLevel(), QModbusDeviceIdentification::BasicConformityLevel);
+ }
+
+ const QByteArray majorMinorRevision = "V2.11";
+ r.setData(QByteArray::fromHex("0e01010000030016") + vendorNameObject
+ + QByteArray::fromHex("010c") + productCodeObject + QByteArray::fromHex("0205")
+ + majorMinorRevision);
+ {
+ QModbusDeviceIdentification qmdi = QModbusDeviceIdentification::fromByteArray(r.data());
+ QCOMPARE(qmdi.isValid(), true);
+ QCOMPARE(qmdi.objectIds(), QList<int>() << QModbusDeviceIdentification::VendorNameObjectId
+ << QModbusDeviceIdentification::ProductCodeObjectId
+ << QModbusDeviceIdentification::MajorMinorRevisionObjectId);
+ QCOMPARE(qmdi.contains(QModbusDeviceIdentification::VendorNameObjectId), true);
+ QCOMPARE(qmdi.contains(QModbusDeviceIdentification::ProductCodeObjectId), true);
+ QCOMPARE(qmdi.contains(QModbusDeviceIdentification::MajorMinorRevisionObjectId), true);
+ QCOMPARE(qmdi.value(QModbusDeviceIdentification::VendorNameObjectId), vendorNameObject);
+ QCOMPARE(qmdi.value(QModbusDeviceIdentification::ProductCodeObjectId), productCodeObject);
+ QCOMPARE(qmdi.value(QModbusDeviceIdentification::MajorMinorRevisionObjectId), majorMinorRevision);
+ QCOMPARE(qmdi.conformityLevel(), QModbusDeviceIdentification::BasicConformityLevel);
+ }
+ }
+};
+
+QTEST_MAIN(tst_QModbusDeviceIdentification)
+
+#include "tst_qmodbusdeviceidentification.moc"
diff --git a/tests/auto/qmodbuspdu/tst_qmodbuspdu.cpp b/tests/auto/qmodbuspdu/tst_qmodbuspdu.cpp
index 76bfea3..592a14e 100644
--- a/tests/auto/qmodbuspdu/tst_qmodbuspdu.cpp
+++ b/tests/auto/qmodbuspdu/tst_qmodbuspdu.cpp
@@ -803,6 +803,47 @@ private slots:
quint8(0xff)); // dummy additional data
QCOMPARE(QModbusResponse::calculateDataSize(rdi), 22);
}
+
+ void testCustomCalculator()
+ {
+ // request
+ QModbusRequest request(QModbusRequest::WriteSingleCoil);
+ request.encodeData(quint8(0x00), quint8(0xac), quint8(0xff), quint8(0x00));
+ QCOMPARE(QModbusRequest::calculateDataSize(request), 4);
+
+ QModbusRequest::registerDataSizeCalculator(QModbusRequest::WriteSingleCoil,
+ [](const QModbusRequest &)->int {
+ return 15;
+ });
+ QCOMPARE(QModbusRequest::calculateDataSize(request), 15);
+
+ QModbusRequest::registerDataSizeCalculator(QModbusRequest::WriteSingleCoil, nullptr);
+ QCOMPARE(QModbusRequest::calculateDataSize(request), 4);
+
+ // response
+ QModbusResponse response(QModbusRequest::WriteSingleCoil);
+ response.encodeData(quint8(0x00), quint8(0xac), quint8(0xff), quint8(0x00));
+ QCOMPARE(QModbusResponse::calculateDataSize(request), 4);
+
+ QModbusResponse::registerDataSizeCalculator(QModbusResponse::WriteSingleCoil,
+ [](const QModbusResponse &)->int {
+ return 15;
+ });
+ QCOMPARE(QModbusResponse::calculateDataSize(response), 15);
+
+ QModbusResponse::registerDataSizeCalculator(QModbusResponse::WriteSingleCoil, nullptr);
+ QCOMPARE(QModbusResponse::calculateDataSize(response), 4);
+
+ // custom
+ QModbusPdu::FunctionCode custom = QModbusPdu::FunctionCode(0x30);
+ request.setFunctionCode(custom);
+ QCOMPARE(QModbusRequest::calculateDataSize(request), -1);
+
+ QModbusRequest::registerDataSizeCalculator(custom, [](const QModbusRequest &)->int {
+ return 27;
+ });
+ QCOMPARE(QModbusRequest::calculateDataSize(request), 27);
+ }
};
QTEST_MAIN(tst_QModbusPdu)
diff --git a/tests/auto/qmodbusrtuserialmaster/qmodbusrtuserialmaster.pro b/tests/auto/qmodbusrtuserialmaster/qmodbusrtuserialmaster.pro
new file mode 100644
index 0000000..3125a27
--- /dev/null
+++ b/tests/auto/qmodbusrtuserialmaster/qmodbusrtuserialmaster.pro
@@ -0,0 +1,8 @@
+CONFIG -= app_bundle
+CONFIG += testcase c++11
+
+QT = core testlib serialbus
+
+TARGET = tst_qmodbusrtuserialmaster
+SOURCES += tst_qmodbusrtuserialmaster.cpp
+
diff --git a/tests/auto/qmodbusrtuserialmaster/tst_qmodbusrtuserialmaster.cpp b/tests/auto/qmodbusrtuserialmaster/tst_qmodbusrtuserialmaster.cpp
new file mode 100644
index 0000000..f341ce3
--- /dev/null
+++ b/tests/auto/qmodbusrtuserialmaster/tst_qmodbusrtuserialmaster.cpp
@@ -0,0 +1,61 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the QtSerialBus module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtSerialBus/qmodbusrtuserialmaster.h>
+
+#include <QtTest/QtTest>
+
+class tst_QModbusRtuSerialMaster : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void testInterFrameDelay()
+ {
+ QModbusRtuSerialMaster qmrsm;
+ QCOMPARE(qmrsm.interFrameDelay(), 2000);
+ qmrsm.setInterFrameDelay(1000);
+ QCOMPARE(qmrsm.interFrameDelay(), 2000);
+ qmrsm.setInterFrameDelay(3000);
+ QCOMPARE(qmrsm.interFrameDelay(), 3000);
+ qmrsm.setInterFrameDelay(-1);
+ QCOMPARE(qmrsm.interFrameDelay(), 2000);
+ }
+};
+
+QTEST_MAIN(tst_QModbusRtuSerialMaster)
+
+#include "tst_qmodbusrtuserialmaster.moc"
diff --git a/tests/auto/qmodbusserver/tst_qmodbusserver.cpp b/tests/auto/qmodbusserver/tst_qmodbusserver.cpp
index 114063a..2f5c231 100644
--- a/tests/auto/qmodbusserver/tst_qmodbusserver.cpp
+++ b/tests/auto/qmodbusserver/tst_qmodbusserver.cpp
@@ -37,6 +37,7 @@
#include <QtSerialBus/qmodbusserver.h>
#include <QtSerialBus/qmodbusrtuserialslave.h>
#include <QtSerialBus/qmodbustcpserver.h>
+#include <QtSerialBus/qmodbusdeviceidentification.h>
#include <QtCore/qdebug.h>
#include <QtTest/QtTest>
@@ -1198,6 +1199,51 @@ private slots:
server.processRequest(QModbusRequest(QModbusRequest::Diagnostics, quint16(0x0014), quint16(0)));
QCOMPARE(server.value(QModbusServer::DiagnosticRegister).value<quint16>(), quint16(0xfffe));
}
+
+ void testProcessEncapsulatedInterfaceTransportRequest()
+ {
+ QModbusDeviceIdentification objectPool;
+ QCOMPARE(objectPool.insert(QModbusDeviceIdentification::VendorNameObjectId,
+ "Company identification"), true);
+ QCOMPARE(objectPool.isValid(), false);
+ QCOMPARE(objectPool.insert(QModbusDeviceIdentification::ProductCodeObjectId,
+ "Product code"), true);
+ QCOMPARE(objectPool.isValid(), false);
+ QCOMPARE(objectPool.insert(QModbusDeviceIdentification::MajorMinorRevisionObjectId,
+ "V2.11"), true);
+ QCOMPARE(objectPool.isValid(), true);
+
+ QCOMPARE(server.setValue(QModbusServer::DeviceIdentification,
+ QVariant::fromValue<QModbusDeviceIdentification>(objectPool)), true);
+
+ auto response = server
+ .processRequest(QModbusRequest(QModbusRequest::EncapsulatedInterfaceTransport,
+ QByteArray::fromHex("0e0100")));
+ QCOMPARE(response.data(), QByteArray::fromHex("0e01010000030016")
+ + "Company identification" + QByteArray::fromHex("010c") + "Product code"
+ + QByteArray::fromHex("0205") + "V2.11");
+
+ QCOMPARE(objectPool.insert(QModbusDeviceIdentification::ProductCodeObjectId, QByteArray(
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")), true);
+ QCOMPARE(objectPool.isValid(), true);
+
+ QCOMPARE(server.setValue(QModbusServer::DeviceIdentification,
+ QVariant::fromValue<QModbusDeviceIdentification>(objectPool)), true);
+
+ response = server.processRequest(QModbusRequest(QModbusPdu::EncapsulatedInterfaceTransport,
+ QByteArray::fromHex("0e0100")));
+ QCOMPARE(response.data(), QByteArray::fromHex("0e0101ff02020016")
+ + "Company identification" + QByteArray::fromHex("01dc") + QByteArray(
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"));
+
+ response = server.processRequest(QModbusRequest(QModbusPdu::EncapsulatedInterfaceTransport,
+ QByteArray::fromHex("0e0102")));
+ QCOMPARE(response.data(), QByteArray::fromHex("0e01010000010205") + "V2.11");
+ }
};
QTEST_MAIN(tst_QModbusServer)