summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Blasche <alexander.blasche@theqtcompany.com>2016-02-19 08:51:01 +0100
committerAlex Blasche <alexander.blasche@theqtcompany.com>2016-02-19 08:51:01 +0100
commit246c93df987e3ee49ba18a3d50cc9d96d35b9672 (patch)
treeef44b18c77bd81b5db0a8f875002face3e4594f9
parented6ec90cb81cdd66738627f264e928da3857d1fd (diff)
parent6956e682340221be286080ec9b40d0ecf6fc882d (diff)
Merge remote-tracking branch 'gerrit/5.6' into 5.7
Conflicts: .qmake.conf Change-Id: I663edd2b2accf1548acf5a156df7fef9d2024f51
-rw-r--r--examples/serialbus/modbus/adueditor/mainwindow.h3
-rw-r--r--examples/serialbus/modbus/adueditor/modbustcpclient_p.h2
-rw-r--r--examples/serialbus/modbus/master/settingsdialog.h2
-rw-r--r--examples/serialbus/modbus/master/settingsdialog.ui2
-rw-r--r--examples/serialbus/modbus/slave/mainwindow.cpp6
-rw-r--r--src/plugins/canbus/socketcan/main.cpp2
-rw-r--r--src/serialbus/doc/qtserialbus.qdocconf3
-rw-r--r--src/serialbus/doc/snippets/snippetmain.cpp (renamed from src/serialbus/doc/snippets/main.cpp)0
-rw-r--r--src/serialbus/doc/snippets/snippets.pro2
-rw-r--r--src/serialbus/doc/src/qtcanbus-backends.qdoc65
-rw-r--r--src/serialbus/doc/src/socketcan.qdoc2
-rw-r--r--src/serialbus/qcanbusdevice.cpp18
-rw-r--r--src/serialbus/qcanbusdevice.h8
-rw-r--r--src/serialbus/qmodbusclient_p.h7
-rw-r--r--src/serialbus/qmodbuscommevent_p.h1
-rw-r--r--src/serialbus/qmodbusdataunit.h2
-rw-r--r--src/serialbus/qmodbusdevice_p.h2
-rw-r--r--src/serialbus/qmodbuspdu.cpp229
-rw-r--r--src/serialbus/qmodbuspdu.h43
-rw-r--r--src/serialbus/qmodbusreply.cpp3
-rw-r--r--src/serialbus/qmodbusrtuserialmaster.cpp10
-rw-r--r--src/serialbus/qmodbusrtuserialmaster_p.h338
-rw-r--r--src/serialbus/qmodbusrtuserialslave.cpp10
-rw-r--r--src/serialbus/qmodbusrtuserialslave_p.h10
-rw-r--r--src/serialbus/qmodbustcpclient_p.h2
-rw-r--r--src/serialbus/qmodbustcpserver_p.h2
-rw-r--r--tests/auto/qmodbuspdu/tst_qmodbuspdu.cpp2
-rw-r--r--tests/auto/qmodbusserver/tst_qmodbusserver.cpp1
28 files changed, 441 insertions, 336 deletions
diff --git a/examples/serialbus/modbus/adueditor/mainwindow.h b/examples/serialbus/modbus/adueditor/mainwindow.h
index b37fbb1..4870a25 100644
--- a/examples/serialbus/modbus/adueditor/mainwindow.h
+++ b/examples/serialbus/modbus/adueditor/mainwindow.h
@@ -53,8 +53,7 @@ QT_END_NAMESPACE;
class DebugHandler
{
public:
- DebugHandler() Q_DECL_EQ_DEFAULT;
- DebugHandler(QtMessageHandler newMessageHandler)
+ explicit DebugHandler(QtMessageHandler newMessageHandler)
: oldMessageHandler(qInstallMessageHandler(newMessageHandler))
{}
~DebugHandler() { qInstallMessageHandler(oldMessageHandler); }
diff --git a/examples/serialbus/modbus/adueditor/modbustcpclient_p.h b/examples/serialbus/modbus/adueditor/modbustcpclient_p.h
index af20d66..050985f 100644
--- a/examples/serialbus/modbus/adueditor/modbustcpclient_p.h
+++ b/examples/serialbus/modbus/adueditor/modbustcpclient_p.h
@@ -55,8 +55,6 @@ class ModbusTcpClientPrivate : private QModbusTcpClientPrivate
Q_DECLARE_PUBLIC(ModbusTcpClient)
public:
- ModbusTcpClientPrivate() Q_DECL_EQ_DEFAULT;
-
QModbusReply *enqueueRequest(const QModbusRequest &request, int, const QModbusDataUnit &unit,
QModbusReply::ReplyType type) Q_DECL_OVERRIDE
{
diff --git a/examples/serialbus/modbus/master/settingsdialog.h b/examples/serialbus/modbus/master/settingsdialog.h
index e86e2bb..ecefbba 100644
--- a/examples/serialbus/modbus/master/settingsdialog.h
+++ b/examples/serialbus/modbus/master/settingsdialog.h
@@ -62,7 +62,7 @@ public:
int baud = QSerialPort::Baud19200;
int dataBits = QSerialPort::Data8;
int stopBits = QSerialPort::OneStop;
- int responseTime = 200;
+ int responseTime = 1000;
int numberOfRetries = 3;
};
diff --git a/examples/serialbus/modbus/master/settingsdialog.ui b/examples/serialbus/modbus/master/settingsdialog.ui
index c38d566..cbfe301 100644
--- a/examples/serialbus/modbus/master/settingsdialog.ui
+++ b/examples/serialbus/modbus/master/settingsdialog.ui
@@ -39,7 +39,7 @@
<number>-1</number>
</property>
<property name="maximum">
- <number>2000</number>
+ <number>5000</number>
</property>
<property name="singleStep">
<number>20</number>
diff --git a/examples/serialbus/modbus/slave/mainwindow.cpp b/examples/serialbus/modbus/slave/mainwindow.cpp
index 82976a0..f568291 100644
--- a/examples/serialbus/modbus/slave/mainwindow.cpp
+++ b/examples/serialbus/modbus/slave/mainwindow.cpp
@@ -219,7 +219,7 @@ void MainWindow::discreteInputChanged(int id)
void MainWindow::bitChanged(int id, QModbusDataUnit::RegisterType table, bool value)
{
- if (!modbusDevice || modbusDevice->state() != QModbusDevice::ConnectedState)
+ if (!modbusDevice)
return;
if (!modbusDevice->setData(table, id, value))
@@ -228,7 +228,7 @@ void MainWindow::bitChanged(int id, QModbusDataUnit::RegisterType table, bool va
void MainWindow::setRegister(const QString &value)
{
- if (!modbusDevice || modbusDevice->state() != QModbusDevice::ConnectedState)
+ if (!modbusDevice)
return;
const QString objectName = QObject::sender()->objectName();
@@ -271,7 +271,7 @@ void MainWindow::updateWidgets(QModbusDataUnit::RegisterType table, int address,
void MainWindow::setupDeviceData()
{
- if (!modbusDevice || modbusDevice->state() != QModbusDevice::ConnectedState)
+ if (!modbusDevice)
return;
for (int i = 0; i < coilButtons.buttons().count(); ++i)
diff --git a/src/plugins/canbus/socketcan/main.cpp b/src/plugins/canbus/socketcan/main.cpp
index f1c34f5..c938d96 100644
--- a/src/plugins/canbus/socketcan/main.cpp
+++ b/src/plugins/canbus/socketcan/main.cpp
@@ -44,6 +44,7 @@
QT_BEGIN_NAMESPACE
+//! [SocketCanFactory]
class CanBusPlugin : public QObject, public QCanBusFactory
{
Q_OBJECT
@@ -58,6 +59,7 @@ public:
return device;
}
};
+//! [SocketCanFactory]
QT_END_NAMESPACE
diff --git a/src/serialbus/doc/qtserialbus.qdocconf b/src/serialbus/doc/qtserialbus.qdocconf
index 4e25fdf..79b3de1 100644
--- a/src/serialbus/doc/qtserialbus.qdocconf
+++ b/src/serialbus/doc/qtserialbus.qdocconf
@@ -38,7 +38,8 @@ sourcedirs += ..
examplesinstallpath = serialbus
exampledirs += ../../../examples/serialbus \
- snippets/
+ snippets/ \
+ ../../plugins/canbus/socketcan
imagedirs += images
diff --git a/src/serialbus/doc/snippets/main.cpp b/src/serialbus/doc/snippets/snippetmain.cpp
index 614357d..614357d 100644
--- a/src/serialbus/doc/snippets/main.cpp
+++ b/src/serialbus/doc/snippets/snippetmain.cpp
diff --git a/src/serialbus/doc/snippets/snippets.pro b/src/serialbus/doc/snippets/snippets.pro
index d214932..4fdcd74 100644
--- a/src/serialbus/doc/snippets/snippets.pro
+++ b/src/serialbus/doc/snippets/snippets.pro
@@ -3,5 +3,5 @@ TARGET = serialbus_cppsnippet
QT = core serialbus
SOURCES += \
- main.cpp
+ snippetmain.cpp
diff --git a/src/serialbus/doc/src/qtcanbus-backends.qdoc b/src/serialbus/doc/src/qtcanbus-backends.qdoc
index e6bcde4..6f32c5e 100644
--- a/src/serialbus/doc/src/qtcanbus-backends.qdoc
+++ b/src/serialbus/doc/src/qtcanbus-backends.qdoc
@@ -33,6 +33,8 @@
A Controller Area Network (CAN) is a vehicle bus standard designed to allow microcontrollers
and devices to communicate with each other in applications without a host computer.
+ \section1 Overview
+
It is a message-based protocol, designed originally for multiplex electrical wiring within
automobiles, but is also used in many other contexts.
@@ -43,25 +45,76 @@
\li QCanBusFrame defines a CAN frame that can be written and read from QCanBusDevice.
\endlist
- Various vendors provide CAN devices with different API for access. The CAN bus plugin
- supports the following set of backends for various devices:
+ Multiple vendors provide CAN devices with varying APIs for access. The QtSerialBus module
+ supports the following sets of CAN bus backends:
\table
\header
\li Vendor
- \li Backend
+ \li Backend (key)
\li Brief description
\row
\li CAN over Linux sockets
- \li \l {Using SocketCAN Backend}{SocketCAN}
+ \li \l {Using SocketCAN Backend}{SocketCAN} (\c socketcan)
\li CAN bus backend using Linux sockets and open source drivers.
\row
\li PEAK-System
- \li \l {Using PeakCAN Backend}{PeakCAN}
+ \li \l {Using PeakCAN Backend}{PeakCAN} (\c peakcan)
\li CAN bus backend using the PCAN adapters.
\row
\li MHS Elektronik
- \li \l {Using TinyCAN Backend}{TinyCAN}
+ \li \l {Using TinyCAN Backend}{TinyCAN} (\c tinycan)
\li CAN bus backend using the Tiny-CAN adapters.
\endtable
+
+ \section1 Implementing a Custom CAN Plugin
+
+ If the plugins provided by Qt are not suitable for the required target platform,
+ a custom CAN bus plugin can be implemented. The implementation follows the standard
+ way of implementing Qt plug-ins. The custom plugin must be deployed
+ to \c {$QTDIR/plugins/canbus}.
+
+ Each plugin must define a key, which is used to load the plugin. This is done via a small json
+ file. For example, the socketcan plugin uses the following \c {plugin.json}:
+
+ \code
+ {
+ "Key": "socketcan"
+ }
+ \endcode
+
+ This key must be passed to \l {QCanBus::createDevice()} together with the interface name of
+ the CAN bus adapter. QCanBus loads and instantiates the plugin using the QCanBusFactory
+ interface which each plugin must implement as central entry point. The interface acts as
+ a factory and its sole purpose is to return a \l QCanBusDevice instance. The above mentioned
+ interface name is passed on via the factory's \l QCanBusFactory::createDevice() method.
+ The following is the factory implementation of the \e socketcan plugin:
+
+ \snippet main.cpp SocketCanFactory
+
+ The next step is to provide an implementation of QCanBusDevice. At the very least, the
+ following pure virtual functions must be implemented:
+
+ \list
+ \li \l QCanBusDevice::open()
+ \li \l QCanBusDevice::close()
+ \li \l QCanBusDevice::writeFrame()
+ \li \l QCanBusDevice::interpretErrorFrame()
+ \endlist
+
+ The \l {QCanBusDevice::open()}{open()} and \l {QCanBusDevice::close()}{close()}
+ methods are used in conjuntion with \l QCanBusDevice::connectDevice() and
+ \l QCanBusDevice::disconnectDevice() respectively. Check the
+ function documentation for implementation details.
+
+ \l QCanBusDevice::writeFrame() is responsible for sanity checks such as the validity
+ of the \l QCanBusFrame and that the device is still connected. Provided
+ that the checks passed, it writes the frame to the CAN bus. Upon success it emits
+ the \l QCanBusDevice::framesWritten() signal; otherwise \l QCanBusDevice::setError() is
+ called with an appropriate error message. This function may also be used to implement
+ an asynchronous write operation. It is the plugin implementors responsibility to emit
+ the appropriate signals at the appropriate time.
+
+ Last but not least, \l QCanBusDevice::interpretErrorFrame provides a convenient way
+ to translate the content of an CAN bus error frame to a human-readable error string.
*/
diff --git a/src/serialbus/doc/src/socketcan.qdoc b/src/serialbus/doc/src/socketcan.qdoc
index 70728e9..b0113e3 100644
--- a/src/serialbus/doc/src/socketcan.qdoc
+++ b/src/serialbus/doc/src/socketcan.qdoc
@@ -116,7 +116,7 @@
For example:
- \snippet main.cpp SocketCan Filter Example
+ \snippet snippetmain.cpp SocketCan Filter Example
Efficient frame format and flexible data-rate are supported in SocketCAN.
*/
diff --git a/src/serialbus/qcanbusdevice.cpp b/src/serialbus/qcanbusdevice.cpp
index 6cc4f52..7693fc2 100644
--- a/src/serialbus/qcanbusdevice.cpp
+++ b/src/serialbus/qcanbusdevice.cpp
@@ -121,7 +121,7 @@ QT_BEGIN_NAMESPACE
The example below demonstrates how to use the struct:
- \snippet main.cpp Filter Examples
+ \snippet snippetmain.cpp Filter Examples
*/
/*!
@@ -415,11 +415,18 @@ qint64 QCanBusDevice::framesToWrite() const
This function is called by connectDevice(). Subclasses must provide
an implementation which returns \c true if the CAN bus connection
- could be established; otherwise \c false.
+ could be established; otherwise \c false. The QCanBusDevice implementation
+ ensures upon entry of this function that the device's \l state() is set
+ to \l QCanBusDevice::ConnectingState already.
The implementation must ensure that upon success the instance's \l state()
is set to \l QCanBusDevice::ConnectedState; otherwise
- \l QCanBusDevice::UnconnectedState.
+ \l QCanBusDevice::UnconnectedState. \l setState() must be used to set the new
+ device state.
+
+ The custom implementation is responsible for opening the socket, instanciation
+ of a potentially required \l QSocketNotifier and the application of custom and default
+ \l QCanBusDevice::configurationParameter().
\sa connectDevice()
*/
@@ -431,6 +438,9 @@ qint64 QCanBusDevice::framesToWrite() const
The implementation must ensure that the instance's
\l state() is set to \l QCanBusDevice::UnconnectedState.
+ This function's most important task is to close the socket to the CAN device
+ and to call \l QCanBusDevice::setState().
+
\sa disconnectDevice()
*/
@@ -500,7 +510,7 @@ QCanBusFrame QCanBusDevice::readFrame()
Interprets \a frame as error frame and returns a human readable
description of the error.
- If \a frame is not an error frame, the return string is empty.
+ If \a frame is not an error frame, the returned string is empty.
*/
/*!
diff --git a/src/serialbus/qcanbusdevice.h b/src/serialbus/qcanbusdevice.h
index ace3d4b..8c9436c 100644
--- a/src/serialbus/qcanbusdevice.h
+++ b/src/serialbus/qcanbusdevice.h
@@ -89,18 +89,10 @@ public:
};
Q_DECLARE_FLAGS(FormatFilters, FormatFilter)
-#ifndef Q_QDOC
quint32 frameId = 0;
quint32 frameIdMask = 0;
QCanBusFrame::FrameType type = QCanBusFrame::InvalidFrame;
FormatFilter format = MatchBaseAndExtendedFormat;
-#else
- // qdoc cannot handle default initialized member
- quint32 frameId;
- quint32 frameIdMask;
- QCanBusFrame::FrameType type;
- FormatFilter format;
-#endif
};
explicit QCanBusDevice(QObject *parent = Q_NULLPTR);
diff --git a/src/serialbus/qmodbusclient_p.h b/src/serialbus/qmodbusclient_p.h
index cb71bc3..f7f6a36 100644
--- a/src/serialbus/qmodbusclient_p.h
+++ b/src/serialbus/qmodbusclient_p.h
@@ -61,9 +61,6 @@ class Q_AUTOTEST_EXPORT QModbusClientPrivate : public QModbusDevicePrivate
Q_DECLARE_PUBLIC(QModbusClient)
public:
- QModbusClientPrivate() Q_DECL_EQ_DEFAULT;
- virtual ~QModbusClientPrivate() Q_DECL_EQ_DEFAULT;
-
QModbusReply *sendRequest(const QModbusRequest &request, int serverAddress,
const QModbusDataUnit *const unit);
QModbusRequest createReadRequest(const QModbusDataUnit &data) const;
@@ -106,7 +103,7 @@ public:
int m_responseTimeoutDuration = 1000;
struct QueueElement {
- QueueElement() Q_DECL_EQ_DEFAULT;
+ QueueElement() = default;
QueueElement(QModbusReply *r, const QModbusRequest &req, const QModbusDataUnit &u, int num,
int timeout = -1)
: reply(r), requestPdu(req), unit(u), numberOfRetries(num)
@@ -127,6 +124,8 @@ public:
QModbusDataUnit unit;
int numberOfRetries;
QSharedPointer<QTimer> timer;
+ QByteArray adu;
+ qint64 bytesWritten = 0;
};
void processQueueElement(const QModbusResponse &pdu, const QueueElement &element);
};
diff --git a/src/serialbus/qmodbuscommevent_p.h b/src/serialbus/qmodbuscommevent_p.h
index 84d6f30..4cf730a 100644
--- a/src/serialbus/qmodbuscommevent_p.h
+++ b/src/serialbus/qmodbuscommevent_p.h
@@ -103,7 +103,6 @@ public:
}
private:
- QModbusCommEvent() Q_DECL_EQ_DEFAULT;
quint8 m_eventByte;
};
diff --git a/src/serialbus/qmodbusdataunit.h b/src/serialbus/qmodbusdataunit.h
index 97830ce..9b767c5 100644
--- a/src/serialbus/qmodbusdataunit.h
+++ b/src/serialbus/qmodbusdataunit.h
@@ -54,7 +54,7 @@ public:
HoldingRegisters
};
- QModbusDataUnit() Q_DECL_EQ_DEFAULT;
+ QModbusDataUnit() = default;
explicit QModbusDataUnit(RegisterType type)
: QModbusDataUnit(type, 0, 0)
diff --git a/src/serialbus/qmodbusdevice_p.h b/src/serialbus/qmodbusdevice_p.h
index 91b169e..f9d46e2 100644
--- a/src/serialbus/qmodbusdevice_p.h
+++ b/src/serialbus/qmodbusdevice_p.h
@@ -61,8 +61,6 @@ class QModbusDevicePrivate : public QObjectPrivate
Q_DECLARE_PUBLIC(QModbusDevice)
public:
- QModbusDevicePrivate() Q_DECL_EQ_DEFAULT;
-
QModbusDevice::State state = QModbusDevice::UnconnectedState;
QModbusDevice::Error error = QModbusDevice::NoError;
QString errorString;
diff --git a/src/serialbus/qmodbuspdu.cpp b/src/serialbus/qmodbuspdu.cpp
index e32607a..da17a03 100644
--- a/src/serialbus/qmodbuspdu.cpp
+++ b/src/serialbus/qmodbuspdu.cpp
@@ -35,6 +35,7 @@
****************************************************************************/
#include "qmodbuspdu.h"
+#include "qmodbus_symbols_p.h"
#include <QtCore/qdebug.h>
@@ -95,6 +96,55 @@ static int minimumDataSize(const QModbusPdu &pdu, Type type)
return -1;
}
+static QDataStream &pduFromStream(QDataStream &stream, QModbusPdu &pdu, Type type)
+{
+ QModbusPdu::FunctionCode code;
+ stream >> reinterpret_cast<quint8&> (code);
+ pdu.setFunctionCode(static_cast<QModbusPdu::FunctionCode> (code));
+
+ int size = (type == Type::Response) ? QModbusResponse::minimumDataSize(pdu)
+ : QModbusRequest::minimumDataSize(pdu);
+ if (size < 0)
+ pdu.setFunctionCode(QModbusResponse::Invalid);
+ if (size <= 0)
+ return stream;
+
+ QByteArray data(size, Qt::Uninitialized);
+ if (stream.device()->peek(data.data(), size) != size)
+ return stream;
+
+ size = type == Type::Response ? QModbusResponse::calculateDataSize(QModbusResponse(code, data))
+ : QModbusRequest::calculateDataSize(QModbusResponse(code, data));
+ if (size < 0) {
+ pdu.setFunctionCode(QModbusResponse::Invalid);
+ return stream;
+ }
+
+ auto readRaw = [&]() -> QDataStream& {
+ stream.readRawData(data.data(), data.size());
+ pdu.setData(data);
+ return stream;
+ };
+
+ quint16 subCode;
+ data.resize(size);
+ switch (pdu.functionCode()) {
+ default: return readRaw();
+ case QModbusPdu::Diagnostics:
+ pdu.decodeData(&subCode);
+ if (subCode != Diagnostics::ReturnQueryData)
+ return readRaw();
+ // deliberately fall trough in case of ReturnQueryData
+ case QModbusPdu::EncapsulatedInterfaceTransport:
+ data.resize(stream.device()->size() - 1); // One byte for the function code.
+ if (data.size() <= 252) // The maximum PDU data size is 252 bytes.
+ return readRaw();
+ break;
+ }
+ pdu.setFunctionCode(QModbusResponse::Invalid);
+ return stream;
+}
+
} // namespace Private
/*!
@@ -233,8 +283,9 @@ static int minimumDataSize(const QModbusPdu &pdu, Type type)
Constructs a QModbusPdu with function code set to \a code and payload set to \a data.
The data is converted and stored in big-endian byte order.
- \note Usage should be limited the POD types only. This is because \c QDataStream stream
- operators will not only append raw data, but also e.g. size, count etc. for complex types.
+ \note Usage is limited \c quint8 and \c quint16 only. This is because
+ \c QDataStream stream operators will not only append raw data, but also
+ e.g. size, count etc. for complex types.
*/
/*!
@@ -312,7 +363,7 @@ static int minimumDataSize(const QModbusPdu &pdu, Type type)
*/
/*!
- \fn void QModbusPdu::decodeData(Args data) const
+ \fn void QModbusPdu::decodeData(Args && ... data) const
Converts the payload into host endianness and reads it into \a data. Data can be a variable
length argument list.
@@ -324,12 +375,13 @@ static int minimumDataSize(const QModbusPdu &pdu, Type type)
response.decodeData(&count, &id, &run);
\endcode
- \note Usage should be limited the POD types only. This is because \c QDataStream stream
- operators will not only read raw data, but also e.g. size, count etc. for complex types.
+ \note Usage is limited \c quint8 and \c quint16 only. This is because
+ \c QDataStream stream operators will not only append raw data, but also
+ e.g. size, count etc. for complex types.
*/
/*!
- \fn void QModbusPdu::encodeData(Args data)
+ \fn void QModbusPdu::encodeData(Args ... data)
Sets the payload to \a data. The data is converted and stored in big-endian byte order.
@@ -339,8 +391,9 @@ static int minimumDataSize(const QModbusPdu &pdu, Type type)
request.encodeData(quint16(0x0c), quint16(0x0a));
\endcode
- \note Usage should be limited the POD types only. This is because \c QDataStream stream
- operators will not only append raw data, but also e.g. size, count etc. for complex types.
+ \note Usage is limited \c quint8 and \c quint16 only. This is because
+ \c QDataStream stream operators will not only append raw data, but also
+ e.g. size, count etc. for complex types.
*/
/*!
@@ -351,8 +404,9 @@ static int minimumDataSize(const QModbusPdu &pdu, Type type)
QDebug operator<<(QDebug debug, const QModbusPdu &pdu)
{
QDebugStateSaver _(debug);
- debug.nospace().noquote() << "0x" << QByteArray(1, pdu.functionCode()).toHex() << pdu.data()
- .toHex();
+ debug.nospace().noquote() << "0x" << hex << qSetFieldWidth(2) << qSetPadChar('0')
+ << (pdu.isException() ? pdu.functionCode() | QModbusPdu::ExceptionByte : pdu.functionCode())
+ << qSetFieldWidth(0) << pdu.data().toHex();
return debug;
}
@@ -419,8 +473,9 @@ QDataStream &operator<<(QDataStream &stream, const QModbusPdu &pdu)
Constructs a QModbusRequest with function code set to \a code and payload set to \a data.
The data is converted and stored in big-endian byte order.
- \note Usage should be limited the POD types only. This is because \c QDataStream stream
- operators will not only append raw data, but also e.g. size, count etc. for complex types.
+ \note Usage is limited \c quint8 and \c quint16 only. This is because
+ \c QDataStream stream operators will not only append raw data, but also
+ e.g. size, count etc. for complex types.
*/
/*!
@@ -431,52 +486,45 @@ QDataStream &operator<<(QDataStream &stream, const QModbusPdu &pdu)
*/
/*!
- Returns the minimum data size for a request, based on the \a pdu function code.
- \note The function returns \c {-1} if the size could not be properly calculated.
+ Returns the expected minimum data size for \a request based on the
+ request's function code; \c {-1} if the function code is not known.
*/
-int QModbusRequest::minimumDataSize(const QModbusPdu &pdu)
+int QModbusRequest::minimumDataSize(const QModbusRequest &request)
{
- return Private::minimumDataSize(pdu, Private::Type::Request);
+ return Private::minimumDataSize(request, Private::Type::Request);
}
/*!
- Calculates the expected data size for a request, based on the \a pdu function code.
- The \a data byte array is expected to be the part directly following the function
- code. Returns the full size of the PDU's data part; \c {-1} if the size could not be
- properly calculated.
+ 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.
*/
-int QModbusRequest::calculateDataSize(const QModbusPdu &pdu, const QByteArray &data)
+int QModbusRequest::calculateDataSize(const QModbusRequest &request)
{
- if (pdu.isException())
+ if (request.isException())
return 1;
int size = -1;
- int minimum = Private::minimumDataSize(pdu, Private::Type::Request);
+ int minimum = Private::minimumDataSize(request, Private::Type::Request);
if (minimum < 0)
return size;
- switch (pdu.functionCode()) {
+ switch (request.functionCode()) {
case QModbusPdu::WriteMultipleCoils:
minimum -= 1; // first payload payload byte
- if (data.size() >= minimum)
- size = minimum + data[minimum - 1] /*byte count*/;
+ if (request.dataSize() >= minimum)
+ size = minimum + request.data()[minimum - 1] /*byte count*/;
break;
case QModbusPdu::WriteMultipleRegisters:
case QModbusPdu::ReadWriteMultipleRegisters:
minimum -= 2; // first 2 payload payload bytes
- if (data.size() >= minimum)
- size = minimum + data[minimum - 1] /*byte count*/;
+ if (request.dataSize() >= minimum)
+ size = minimum + request.data()[minimum - 1] /*byte count*/;
break;
case QModbusPdu::ReadFileRecord:
case QModbusPdu::WriteFileRecord:
- if (data.size() >= 1)
- size = 1 /*byte count*/ + data[0] /*actual bytes*/;
- break;
- case QModbusPdu::Diagnostics:
- case QModbusPdu::EncapsulatedInterfaceTransport:
- // The following part makes sure we pass all checks in the request processing functions
- // and not handled function codes get passed on the processPrivateRequest() function.
- size = data.size();
+ if (request.dataSize() >= 1)
+ size = 1 /*byte count*/ + request.data()[0] /*actual bytes*/;
break;
default:
size = minimum;
@@ -498,36 +546,7 @@ int QModbusRequest::calculateDataSize(const QModbusPdu &pdu, const QByteArray &d
*/
QDataStream &operator>>(QDataStream &stream, QModbusRequest &pdu)
{
- quint8 code;
- stream >> code;
- pdu.setFunctionCode(static_cast<QModbusPdu::FunctionCode> (code));
-
- int size = QModbusRequest::minimumDataSize(pdu);
- if (size < 0)
- pdu.setFunctionCode(QModbusRequest::Invalid);
- if (size <= 0)
- return stream;
-
- QByteArray data(size, Qt::Uninitialized);
- if (code == QModbusPdu::Diagnostics || code == QModbusPdu::EncapsulatedInterfaceTransport) {
- data.resize(stream.device()->size() - 1); // One byte for the Function code.
- if (data.size() > 252) { // The maximum PDU data size is 252 bytes.
- pdu.setFunctionCode(QModbusRequest::Invalid);
- return stream;
- }
- }
- stream.device()->peek(data.data(), data.size());
- size = QModbusRequest::calculateDataSize(pdu, data);
-
- if (size >= 0) {
- data.resize(size);
- stream.readRawData(data.data(), data.size());
- pdu.setData(data);
- } else {
- pdu.setFunctionCode(QModbusRequest::Invalid);
- }
-
- return stream;
+ return Private::pduFromStream(stream, pdu, Private::Type::Request);
}
/*!
@@ -570,8 +589,9 @@ QDataStream &operator>>(QDataStream &stream, QModbusRequest &pdu)
Constructs a QModbusResponse with function code set to \a code and payload set to \a data.
The data is converted and stored in big-endian byte order.
- \note Usage should be limited the POD types only. This is because \c QDataStream stream
- operators will not only append raw data, but also e.g. size, count etc. for complex types.
+ \note Usage is limited \c quint8 and \c quint16 only. This is because
+ \c QDataStream stream operators will not only append raw data, but also
+ e.g. size, count etc. for complex types.
*/
/*!
@@ -582,30 +602,29 @@ QDataStream &operator>>(QDataStream &stream, QModbusRequest &pdu)
*/
/*!
- Returns the minimum data size for a response, based on the \a pdu function code.
- \note The function returns \c {-1} if the size could not be properly calculated.
+ Returns the expected minimum data size for \a response based on the
+ response's function code; \c {-1} if the function code is not known.
*/
-int QModbusResponse::minimumDataSize(const QModbusPdu &pdu)
+int QModbusResponse::minimumDataSize(const QModbusResponse &response)
{
- return Private::minimumDataSize(pdu, Private::Type::Response);
+ return Private::minimumDataSize(response, Private::Type::Response);
}
/*!
- Calculates the expected data size for a response, based on the \a pdu function code.
- The \a data byte array is expected to be the part directly following the function
- code. Returns the full size of the PDU's data part; \c {-1} if the size could not be
- properly calculated.
+ 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.
*/
-int QModbusResponse::calculateDataSize(const QModbusPdu &pdu, const QByteArray &data)
+int QModbusResponse::calculateDataSize(const QModbusResponse &response)
{
- if (pdu.isException())
+ if (response.isException())
return 1;
int size = -1;
- if (QModbusResponse::minimumDataSize(pdu) < 0)
+ if (QModbusResponse::minimumDataSize(response) < 0)
return size;
- switch (pdu.functionCode()) {
+ switch (response.functionCode()) {
case QModbusResponse::ReadCoils:
case QModbusResponse::ReadDiscreteInputs:
case QModbusResponse::ReadHoldingRegisters:
@@ -615,23 +634,18 @@ int QModbusResponse::calculateDataSize(const QModbusPdu &pdu, const QByteArray &
case QModbusResponse::WriteFileRecord:
case QModbusResponse::ReadWriteMultipleRegisters:
case QModbusResponse::ReportServerId:
- if (data.size() >= 1)
- size = 1 /*byte count*/ + data[0] /*actual bytes*/;
+ if (response.dataSize() >= 1)
+ size = 1 /*byte count*/ + response.data()[0] /*actual bytes*/;
break;
case QModbusResponse::ReadFifoQueue: {
- if (data.size() >= 2) {
- quint16 tmp;
- QDataStream rawSize(data);
- rawSize >> tmp;
- size = tmp + 2; // 2 bytes size info
+ if (response.dataSize() >= 2) {
+ quint16 rawSize;
+ response.decodeData(&rawSize);
+ size = rawSize + 2; // 2 bytes size info
}
} break;
- case QModbusPdu::Diagnostics:
- case QModbusPdu::EncapsulatedInterfaceTransport:
- size = data.size(); // Make sure we pass all checks in the response processing function.
- break;
default:
- size = QModbusResponse::minimumDataSize(pdu);
+ size = QModbusResponse::minimumDataSize(response);
break;
}
return size;
@@ -650,36 +664,7 @@ int QModbusResponse::calculateDataSize(const QModbusPdu &pdu, const QByteArray &
*/
QDataStream &operator>>(QDataStream &stream, QModbusResponse &pdu)
{
- quint8 code;
- stream >> code;
- pdu.setFunctionCode(static_cast<QModbusPdu::FunctionCode> (code));
-
- int size = QModbusResponse::minimumDataSize(pdu);
- if (size < 0)
- pdu.setFunctionCode(QModbusResponse::Invalid);
- if (size <= 0)
- return stream;
-
- QByteArray data(size, Qt::Uninitialized);
- if (code == QModbusPdu::Diagnostics || code == QModbusPdu::EncapsulatedInterfaceTransport) {
- data.resize(stream.device()->size() - 1); // One byte for the Function code.
- if (data.size() > 252) { // The maximum PDU data size is 252 bytes.
- pdu.setFunctionCode(QModbusRequest::Invalid);
- return stream;
- }
- }
- stream.device()->peek(data.data(), data.size());
- size = QModbusResponse::calculateDataSize(pdu, data);
-
- if (size >= 0) {
- data.resize(size);
- stream.readRawData(data.data(), data.size());
- pdu.setData(data);
- } else {
- pdu.setFunctionCode(QModbusResponse::Invalid);
- }
-
- return stream;
+ return Private::pduFromStream(stream, pdu, Private::Type::Response);
}
/*!
diff --git a/src/serialbus/qmodbuspdu.h b/src/serialbus/qmodbuspdu.h
index 14a113d..3bdb445 100644
--- a/src/serialbus/qmodbuspdu.h
+++ b/src/serialbus/qmodbuspdu.h
@@ -84,19 +84,15 @@ public:
UndefinedFunctionCode = 0x100
};
- QModbusPdu() Q_DECL_EQ_DEFAULT;
- virtual ~QModbusPdu() Q_DECL_EQ_DEFAULT;
+ QModbusPdu() = default;
+ virtual ~QModbusPdu() = default;
bool isValid() const {
return (m_code >= ReadCoils && m_code < UndefinedFunctionCode)
&& (m_data.size() < 253);
}
-#ifdef Q_QDOC
- static const quint8 ExceptionByte;
-#else
static const quint8 ExceptionByte = 0x80;
-#endif
ExceptionCode exceptionCode() const {
if (!m_data.size() || !isException())
return ExtendedException;
@@ -115,7 +111,6 @@ public:
QByteArray data() const { return m_data; }
void setData(const QByteArray &newData) { m_data = newData; }
-#ifndef Q_QDOC
template <typename ... Args> void encodeData(Args ... newData) {
encode(std::forward<Args>(newData)...);
}
@@ -123,30 +118,16 @@ public:
template <typename ... Args> void decodeData(Args && ... newData) const {
decode(std::forward<Args>(newData)...);
}
-#else
- // slightly modified signature to permit qdoc to have some notion of what's going on
-
- template <typename ... Args> void encodeData(Args newData) {
- encode(std::forward<Args>(newData)...);
- }
-
- template <typename ... Args> void decodeData(Args newData) const
- {
- decode(std::forward<Args>(newData)...);
- }
-#endif
protected:
-
QModbusPdu(FunctionCode code, const QByteArray &newData)
: m_code(code)
, m_data(newData)
{}
- QModbusPdu(const QModbusPdu &) Q_DECL_EQ_DEFAULT;
- QModbusPdu &operator=(const QModbusPdu &) Q_DECL_EQ_DEFAULT;
+ QModbusPdu(const QModbusPdu &) = default;
+ QModbusPdu &operator=(const QModbusPdu &) = default;
- // qdoc cannot deal with variadic templates
template <typename ... Args>
QModbusPdu(FunctionCode code, Args ... newData)
: m_code(code)
@@ -204,7 +185,7 @@ Q_SERIALBUS_EXPORT QDataStream &operator<<(QDataStream &stream, const QModbusPdu
class QModbusRequest : public QModbusPdu
{
public:
- QModbusRequest() Q_DECL_EQ_DEFAULT;
+ QModbusRequest() = default;
QModbusRequest(const QModbusPdu &pdu)
: QModbusPdu(pdu)
{}
@@ -213,10 +194,9 @@ public:
: QModbusPdu(code, newData)
{}
- Q_SERIALBUS_EXPORT static int minimumDataSize(const QModbusPdu &pdu);
- Q_SERIALBUS_EXPORT static int calculateDataSize(const QModbusPdu &pdu, const QByteArray &data);
+ Q_SERIALBUS_EXPORT static int minimumDataSize(const QModbusRequest &pdu);
+ Q_SERIALBUS_EXPORT static int calculateDataSize(const QModbusRequest &pdu);
- // TODO currently no way to document -> qdoc issue due to template usage
template <typename ... Args>
QModbusRequest(FunctionCode code, Args ... newData)
: QModbusPdu(code, newData...)
@@ -227,7 +207,7 @@ Q_SERIALBUS_EXPORT QDataStream &operator>>(QDataStream &stream, QModbusRequest &
class QModbusResponse : public QModbusPdu
{
public:
- QModbusResponse() Q_DECL_EQ_DEFAULT;
+ QModbusResponse() = default;
QModbusResponse(const QModbusPdu &pdu)
: QModbusPdu(pdu)
{}
@@ -236,10 +216,9 @@ public:
: QModbusPdu(code, newData)
{}
- Q_SERIALBUS_EXPORT static int minimumDataSize(const QModbusPdu &pdu);
- Q_SERIALBUS_EXPORT static int calculateDataSize(const QModbusPdu &pdu, const QByteArray &data);
+ Q_SERIALBUS_EXPORT static int minimumDataSize(const QModbusResponse &pdu);
+ Q_SERIALBUS_EXPORT static int calculateDataSize(const QModbusResponse &pdu);
- // TODO currently no way to document -> qdoc issue due to template usage
template <typename ... Args>
QModbusResponse(FunctionCode code, Args ... newData)
: QModbusPdu(code, newData...)
@@ -249,7 +228,7 @@ public:
class QModbusExceptionResponse : public QModbusResponse
{
public:
- QModbusExceptionResponse() Q_DECL_EQ_DEFAULT;
+ QModbusExceptionResponse() = default;
QModbusExceptionResponse(const QModbusPdu &pdu)
: QModbusResponse(pdu)
{}
diff --git a/src/serialbus/qmodbusreply.cpp b/src/serialbus/qmodbusreply.cpp
index 373dd0e..2a8ee19 100644
--- a/src/serialbus/qmodbusreply.cpp
+++ b/src/serialbus/qmodbusreply.cpp
@@ -44,9 +44,8 @@ QT_BEGIN_NAMESPACE
class QModbusReplyPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QModbusReply)
-public:
- QModbusReplyPrivate() Q_DECL_EQ_DEFAULT;
+public:
QModbusDataUnit m_unit;
int m_serverAddress = 1;
bool m_finished = false;
diff --git a/src/serialbus/qmodbusrtuserialmaster.cpp b/src/serialbus/qmodbusrtuserialmaster.cpp
index f94eef5..17d12ad 100644
--- a/src/serialbus/qmodbusrtuserialmaster.cpp
+++ b/src/serialbus/qmodbusrtuserialmaster.cpp
@@ -87,6 +87,9 @@ QModbusRtuSerialMaster::QModbusRtuSerialMaster(QModbusRtuSerialMasterPrivate &dd
/*!
\reimp
+
+ \note When calling this function, existing buffered data is removed from
+ the serial port.
*/
bool QModbusRtuSerialMaster::open()
{
@@ -98,11 +101,12 @@ bool QModbusRtuSerialMaster::open()
d->responseBuffer.clear();
d->updateSerialPortConnectionInfo();
- if (d->m_serialPort->open(QIODevice::ReadWrite))
+ if (d->m_serialPort->open(QIODevice::ReadWrite)) {
+ d->m_serialPort->clear();
setState(QModbusDevice::ConnectedState);
- else
+ } else {
setError(d->m_serialPort->errorString(), QModbusDevice::ConnectionError);
-
+ }
return (state() == QModbusDevice::ConnectedState);
}
diff --git a/src/serialbus/qmodbusrtuserialmaster_p.h b/src/serialbus/qmodbusrtuserialmaster_p.h
index ba0595f..9517777 100644
--- a/src/serialbus/qmodbusrtuserialmaster_p.h
+++ b/src/serialbus/qmodbusrtuserialmaster_p.h
@@ -38,6 +38,7 @@
#define QMODBUSSERIALMASTER_P_H
#include <QtCore/qloggingcategory.h>
+#include <QtCore/qmath.h>
#include <QtCore/qpointer.h>
#include <QtCore/qqueue.h>
#include <QtCore/qtimer.h>
@@ -46,6 +47,7 @@
#include <private/qmodbusadu_p.h>
#include <private/qmodbusclient_p.h>
+#include <private/qmodbus_symbols_p.h>
//
// W A R N I N G
@@ -66,41 +68,26 @@ Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS_LOW)
class QModbusRtuSerialMasterPrivate : public QModbusClientPrivate
{
Q_DECLARE_PUBLIC(QModbusRtuSerialMaster)
+ enum State {
+ Idle,
+ Schedule,
+ Send,
+ Receive,
+ } m_state = Idle;
public:
- QModbusRtuSerialMasterPrivate() Q_DECL_EQ_DEFAULT;
-
void setupSerialPort()
{
Q_Q(QModbusRtuSerialMaster);
- m_serialPort = new QSerialPort(q);
+ m_sendTimer.setSingleShot(true);
+ QObject::connect(&m_sendTimer, &QTimer::timeout, q, [this]() { processQueue(); });
m_responseTimer.setSingleShot(true);
- m_responseTimer.setInterval(m_responseTimeoutDuration);
- q->connect(q, &QModbusClient::timeoutChanged, &m_responseTimer, &QTimer::setInterval);
- QObject::connect(&m_responseTimer, &QTimer::timeout, q, [this]() {
- if (m_queue.isEmpty())
- return;
- m_processesTimeout = true;
- QueueElement elem = m_queue.head();
- if (elem.reply.isNull() || elem.numberOfRetries <= 0) {
- elem = m_queue.dequeue();
- if (elem.numberOfRetries <= 0) {
- elem.reply->setError(QModbusDevice::TimeoutError,
- QModbusClient::tr("Request timeout."));
- qCDebug(QT_MODBUS) << "(RTU client) Timeout of request" << elem.requestPdu;
- }
- } else {
- m_queue[0].numberOfRetries--;
- qCDebug(QT_MODBUS) << "(RTU client) Resend request:" << elem.requestPdu;
- }
- m_processesTimeout = false;
- // go to next request or send request again
- QTimer::singleShot(0, [this]() { sendNextRequest(); });
- });
+ QObject::connect(&m_responseTimer, &QTimer::timeout, q, [this]() { processQueue(); });
- QObject::connect(m_serialPort, &QSerialPort::readyRead, [this]() {
+ m_serialPort = new QSerialPort(q);
+ QObject::connect(m_serialPort, &QSerialPort::readyRead, q, [this]() {
responseBuffer += m_serialPort->read(m_serialPort->bytesAvailable());
qCDebug(QT_MODBUS_LOW) << "(RTU client) Response buffer:" << responseBuffer.toHex();
@@ -110,23 +97,37 @@ public:
}
const QModbusSerialAdu tmpAdu(QModbusSerialAdu::Rtu, responseBuffer);
- const QModbusResponse tmpPdu = tmpAdu.pdu();
- int pduSizeWithoutFcode = QModbusResponse::calculateDataSize(tmpPdu, tmpPdu.data());
+ int pduSizeWithoutFcode = QModbusResponse::calculateDataSize(tmpAdu.pdu());
if (pduSizeWithoutFcode < 0) {
// wait for more data
qCDebug(QT_MODBUS) << "(RTU client) Cannot calculate PDU size for function code:"
- << tmpPdu.functionCode() << " , delaying pending frame";
+ << tmpAdu.pdu().functionCode() << ", delaying pending frame";
return;
}
// server address byte + function code byte + PDU size + 2 bytes CRC
- const int aduSize = 2 + pduSizeWithoutFcode + 2;
+ int aduSize = 2 + pduSizeWithoutFcode + 2;
if (tmpAdu.rawSize() < aduSize) {
qCDebug(QT_MODBUS) << "(RTU client) Incomplete ADU received, ignoring";
return;
}
- m_responseTimer.stop();
+ // Special case for Diagnostics:ReturnQueryData. The response has no
+ // length indicator and is just a simple echo of what we have send.
+ if (tmpAdu.pdu().functionCode() == QModbusPdu::Diagnostics) {
+ const QModbusResponse response = tmpAdu.pdu();
+ if (canMatchRequestAndResponse(response, tmpAdu.serverAddress())) {
+ quint16 subCode = 0xffff;
+ response.decodeData(&subCode);
+ if (subCode == Diagnostics::ReturnQueryData) {
+ if (response.data() != m_current.requestPdu.data())
+ return; // echo does not match request yet
+ aduSize = 2 + response.dataSize() + 2;
+ if (tmpAdu.rawSize() < aduSize)
+ return; // echo matches, probably checksum missing
+ }
+ }
+ }
const QModbusSerialAdu adu(QModbusSerialAdu::Rtu, responseBuffer.left(aduSize));
responseBuffer.remove(0, aduSize);
@@ -150,11 +151,70 @@ public:
return;
}
- processQueueElement(response, m_queue.dequeue());
- QTimer::singleShot(0, [this]() { sendNextRequest(); });
+ m_sendTimer.stop();
+ m_responseTimer.stop();
+ processQueueElement(response, m_current);
+
+ m_state = Schedule; // reschedule, even if empty
+ m_serialPort->clear(QSerialPort::AllDirections);
+ QTimer::singleShot(m_timeoutThreeDotFiveMs, [this]() { processQueue(); });
});
- QObject::connect(m_serialPort, &QSerialPort::aboutToClose, [this]() {
+ using TypeId = void (QSerialPort::*)(QSerialPort::SerialPortError);
+ QObject::connect(m_serialPort, static_cast<TypeId>(&QSerialPort::error),
+ [this](QSerialPort::SerialPortError error) {
+ if (error == QSerialPort::NoError)
+ return;
+
+ qCDebug(QT_MODBUS) << "(RTU server) QSerialPort error:" << error
+ << (m_serialPort ? m_serialPort->errorString() : QString());
+
+ Q_Q(QModbusRtuSerialMaster);
+
+ switch (error) {
+ case QSerialPort::DeviceNotFoundError:
+ q->setError(QModbusDevice::tr("Referenced serial device does not exist."),
+ QModbusDevice::ConnectionError);
+ break;
+ case QSerialPort::PermissionError:
+ q->setError(QModbusDevice::tr("Cannot open serial device due to permissions."),
+ QModbusDevice::ConnectionError);
+ break;
+ case QSerialPort::OpenError:
+ case QSerialPort::NotOpenError:
+ q->setError(QModbusDevice::tr("Cannot open serial device."),
+ QModbusDevice::ConnectionError);
+ break;
+ case QSerialPort::WriteError:
+ q->setError(QModbusDevice::tr("Write error."), QModbusDevice::WriteError);
+ break;
+ case QSerialPort::ReadError:
+ q->setError(QModbusDevice::tr("Read error."), QModbusDevice::ReadError);
+ break;
+ case QSerialPort::ResourceError:
+ q->setError(QModbusDevice::tr("Resource error."), QModbusDevice::ConnectionError);
+ break;
+ case QSerialPort::UnsupportedOperationError:
+ q->setError(QModbusDevice::tr("Device operation is not supported error."),
+ QModbusDevice::ConfigurationError);
+ break;
+ case QSerialPort::TimeoutError:
+ q->setError(QModbusDevice::tr("Timeout error."), QModbusDevice::TimeoutError);
+ break;
+ case QSerialPort::UnknownError:
+ q->setError(QModbusDevice::tr("Unknown error."), QModbusDevice::UnknownError);
+ break;
+ default:
+ qCDebug(QT_MODBUS) << "(RTU server) Unhandled QSerialPort error" << error;
+ break;
+ }
+ });
+
+ QObject::connect(m_serialPort, &QSerialPort::bytesWritten, q, [this](qint64 bytes) {
+ m_current.bytesWritten += bytes;
+ });
+
+ QObject::connect(m_serialPort, &QSerialPort::aboutToClose, q, [this]() {
Q_Q(QModbusRtuSerialMaster);
if (q->state() != QModbusDevice::ClosingState)
q->close();
@@ -168,111 +228,140 @@ public:
m_serialPort->setBaudRate(m_baudRate);
m_serialPort->setDataBits(m_dataBits);
m_serialPort->setStopBits(m_stopBits);
- }
- }
- bool sendNextAdu(const QModbusRequest &request, int serverAddress)
- {
- Q_Q(QModbusRtuSerialMaster);
-
- const QByteArray adu = QModbusSerialAdu::create(QModbusSerialAdu::Rtu, serverAddress,
- request);
- m_serialPort->clear(QSerialPort::Output);
- int writtenBytes = m_serialPort->write(adu);
- if (writtenBytes == -1 || writtenBytes < adu.size()) {
- qCDebug(QT_MODBUS_LOW) << "(RTU client) Cannot send Serial ADU:" << adu.toHex();
- q->setError(QModbusClient::tr("Could not write request to serial bus."),
- QModbusDevice::WriteError);
- return false;
+ // 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;
+ }
}
- qCDebug(QT_MODBUS) << "(RTU client) Sent Serial PDU:" << request;
- qCDebug(QT_MODBUS_LOW)<< "(RTU client) Sent Serial ADU:" << adu.toHex();
+ }
- return true;
+ void scheduleNextRequest() {
+ m_state = Schedule;
+ m_serialPort->clear(QSerialPort::AllDirections);
+ QTimer::singleShot(m_timeoutThreeDotFiveMs, [this]() { processQueue(); });
}
- void sendNextRequest()
+ QModbusReply *enqueueRequest(const QModbusRequest &request, int serverAddress,
+ const QModbusDataUnit &unit, QModbusReply::ReplyType type) Q_DECL_OVERRIDE
{
- if (m_queue.isEmpty()) {
- m_responseTimer.stop();
- return;
- }
-
- if (m_responseTimer.isActive() || m_processesTimeout)
- return;
-
- QueueElement elem = m_queue.head();
- if (elem.reply.isNull()) { // reply deleted, skip it
- m_queue.dequeue();
- QTimer::singleShot(0, [this]() { sendNextRequest(); });
- return;
- }
-
- bool success = sendNextAdu(elem.requestPdu, elem.reply->serverAddress());
- if (!success) {
- elem = m_queue.dequeue();
- elem.reply->setError(QModbusDevice::WriteError,
- QModbusClient::tr("Could not write message to serial bus."));
- QTimer::singleShot(0, [this]() { sendNextRequest(); });
- return;
- }
-
- if (elem.reply->serverAddress() == 0) {
- // broadcasts return immediately but we delay a bit to avoid spaming of bus
- elem = m_queue.dequeue();
- elem.reply->setFinished(true);
+ Q_Q(QModbusRtuSerialMaster);
- QTimer::singleShot(0, [this]() { sendNextRequest(); });
- return;
- }
+ QModbusReply *reply = new QModbusReply(type, serverAddress, q);
+ QueueElement element(reply, request, unit, m_numberOfRetries + 1);
+ element.adu = QModbusSerialAdu::create(QModbusSerialAdu::Rtu, serverAddress, request);
+ m_queue.enqueue(element);
- // regular send -> keep in queue
- m_responseTimer.start();
+ if (m_state == Idle)
+ scheduleNextRequest();
+ return reply;
}
- QModbusReply *enqueueRequest(const QModbusRequest &request, int slaveAddress,
- const QModbusDataUnit &unit,
- QModbusReply::ReplyType type) Q_DECL_OVERRIDE
+ void processQueue()
{
- Q_Q(QModbusRtuSerialMaster);
-
- QModbusReply *reply = new QModbusReply(type, slaveAddress, q);
- m_queue.enqueue(QueueElement{ reply, request, unit, m_numberOfRetries });
-
- q->connect(reply, &QObject::destroyed, q, [this](QObject *obj) {
- foreach (const QueueElement &element, m_queue) {
- if (element.reply != obj)
- continue;
- m_queue.removeAll(element);
- QTimer::singleShot(0, [this]() { sendNextRequest(); });
+ Q_ASSERT_X(!m_sendTimer.isActive(), "processQueue", "send timer active");
+ Q_ASSERT_X(!m_responseTimer.isActive(), "processQueue", "response timer active");
+
+ auto writeAdu = [this]() {
+ responseBuffer.clear();
+ m_current.bytesWritten = 0;
+ m_current.numberOfRetries--;
+ m_serialPort->write(m_current.adu);
+ // Example: 9600 baud, 11 bit per packet -> 872 char/sec
+ // so: 1000 ms / 872 char = 1.147 ms/char * 3.5 character
+ m_sendTimer.start((1000. / (qreal(m_baudRate) / 11.)) * m_current.adu.size());
+
+ qCDebug(QT_MODBUS) << "(RTU client) Sent Serial PDU:" << m_current.requestPdu;
+ qCDebug(QT_MODBUS_LOW).noquote() << "(RTU client) Sent Serial ADU: 0x" + m_current.adu
+ .toHex();
+ };
+
+ switch (m_state) {
+ case Schedule:
+ m_current = QueueElement();
+ if (!m_queue.isEmpty()) {
+ m_current = m_queue.dequeue();
+ if (m_current.reply) {
+ m_state = Send;
+ QTimer::singleShot(0, [writeAdu]() { writeAdu(); });
+ } else {
+ QTimer::singleShot(0, [this]() { processQueue(); });
+ }
+ } else {
+ m_state = Idle;
}
- });
+ break;
+
+ case Send:
+ // send timeout will always happen
+ if (m_current.reply.isNull()) {
+ scheduleNextRequest();
+ } else if (m_current.bytesWritten < m_current.adu.size()) {
+ qCDebug(QT_MODBUS) << "(RTU client) Send failed:" << m_current.requestPdu;
+
+ if (m_current.numberOfRetries <= 0) {
+ if (m_current.reply) {
+ m_current.reply->setError(QModbusDevice::TimeoutError,
+ QModbusClient::tr("Request timeout."));
+ }
+ scheduleNextRequest();
+ } else {
+ m_serialPort->clear(QSerialPort::AllDirections);
+ QTimer::singleShot(m_timeoutThreeDotFiveMs, [writeAdu]() { writeAdu(); });
+ }
+ } else {
+ qCDebug(QT_MODBUS) << "(RTU client) Send successful:" << m_current.requestPdu;
+ m_state = Receive;
+ m_responseTimer.start(m_responseTimeoutDuration);
+ }
+ break;
+
+ case Receive:
+ // receive timeout will only happen after successful send
+ qCDebug(QT_MODBUS) << "(RTU client) Receive timeout:" << m_current.requestPdu;
+ if (m_current.reply.isNull()) {
+ scheduleNextRequest();
+ } else if (m_current.numberOfRetries <= 0) {
+ if (m_current.reply) {
+ m_current.reply->setError(QModbusDevice::TimeoutError,
+ QModbusClient::tr("Response timeout."));
+ }
+ scheduleNextRequest();
+ } else {
+ m_state = Send;
+ m_serialPort->clear(QSerialPort::AllDirections);
+ QTimer::singleShot(m_timeoutThreeDotFiveMs, [this, writeAdu]() { writeAdu(); });
+ }
+ break;
- if (!m_responseTimer.isActive())
- QTimer::singleShot(0, [this]() { sendNextRequest(); });
- return reply;
+ case Idle:
+ default:
+ Q_ASSERT_X(false, "processQueue", QByteArray("unexpected state: ").append(m_state));
+ break;
+ }
}
bool canMatchRequestAndResponse(const QModbusResponse &response, int sendingServer) const
{
- if (m_queue.isEmpty()) // nothing pending
- return false;
-
- const QueueElement &head = m_queue.head(); // reply deleted
- if (head.reply.isNull())
- return false;
-
- if (head.reply->serverAddress() != sendingServer) // server mismatch
- return false;
-
- // request for different fcode
- if (head.requestPdu.functionCode() != response.functionCode())
- return false;
-
+ if (m_current.reply.isNull())
+ return false; // reply deleted
+ if (m_current.reply->serverAddress() != sendingServer)
+ return false; // server mismatch
+ if (m_current.requestPdu.functionCode() != response.functionCode())
+ return false; // request for different function code
return true;
}
- // TODO: Review once we have a transport layer in place.
bool isOpen() const Q_DECL_OVERRIDE
{
if (m_serialPort)
@@ -280,11 +369,16 @@ public:
return false;
}
- QSerialPort *m_serialPort;
+ QTimer m_sendTimer;
+ QTimer m_responseTimer;
+
+ QueueElement m_current;
QByteArray responseBuffer;
+
QQueue<QueueElement> m_queue;
- QTimer m_responseTimer;
- bool m_processesTimeout = false;
+ QSerialPort *m_serialPort = Q_NULLPTR;
+
+ int m_timeoutThreeDotFiveMs = 2; // A approximated value of 1.750 msec.
};
QT_END_NAMESPACE
diff --git a/src/serialbus/qmodbusrtuserialslave.cpp b/src/serialbus/qmodbusrtuserialslave.cpp
index 547ad38..41f3215 100644
--- a/src/serialbus/qmodbusrtuserialslave.cpp
+++ b/src/serialbus/qmodbusrtuserialslave.cpp
@@ -97,6 +97,9 @@ bool QModbusRtuSerialSlave::processesBroadcast() const
/*!
\reimp
+
+ \note When calling this function, existing buffered data is removed from
+ the serial port.
*/
bool QModbusRtuSerialSlave::open()
{
@@ -105,11 +108,12 @@ bool QModbusRtuSerialSlave::open()
Q_D(QModbusRtuSerialSlave);
d->updateSerialPortConnectionInfo();
- if (d->m_serialPort->open(QIODevice::ReadWrite))
+ if (d->m_serialPort->open(QIODevice::ReadWrite)) {
+ d->m_serialPort->clear();
setState(QModbusDevice::ConnectedState);
- else
+ } else {
setError(d->m_serialPort->errorString(), QModbusDevice::ConnectionError);
-
+ }
return (state() == QModbusDevice::ConnectedState);
}
diff --git a/src/serialbus/qmodbusrtuserialslave_p.h b/src/serialbus/qmodbusrtuserialslave_p.h
index b3582d2..96f3db4 100644
--- a/src/serialbus/qmodbusrtuserialslave_p.h
+++ b/src/serialbus/qmodbusrtuserialslave_p.h
@@ -66,8 +66,6 @@ class QModbusRtuSerialSlavePrivate : public QModbusServerPrivate
Q_DECLARE_PUBLIC(QModbusRtuSerialSlave)
public:
- QModbusRtuSerialSlavePrivate() Q_DECL_EQ_DEFAULT;
-
void setupSerialPort()
{
Q_Q(QModbusRtuSerialSlave);
@@ -107,8 +105,7 @@ public:
if (q->processesBroadcast())
event |= QModbusCommEvent::ReceiveFlag::BroadcastReceived;
- const QModbusRequest req = adu.pdu();
- const int pduSizeWithoutFcode = QModbusRequest::calculateDataSize(req, req.data());
+ const int pduSizeWithoutFcode = QModbusRequest::calculateDataSize(adu.pdu());
// server address byte + function code byte + PDU size + 2 bytes CRC
if ((pduSizeWithoutFcode < 0) || ((2 + pduSizeWithoutFcode + 2) != adu.rawSize())) {
@@ -151,6 +148,7 @@ public:
storeModbusCommEvent(event); // store the final event before processing
+ const QModbusRequest req = adu.pdu();
qCDebug(QT_MODBUS) << "(RTU server) Request PDU:" << req;
QModbusResponse response; // If the device ...
if (q->value(QModbusServer::DeviceBusy).value<quint16>() == 0xffff) {
@@ -328,10 +326,6 @@ public:
}
}
- void handleErrorOccurred(QSerialPort::SerialPortError);
- void serialPortReadyRead();
- void aboutToClose();
-
QSerialPort *m_serialPort;
bool m_processesBroadcast = false;
};
diff --git a/src/serialbus/qmodbustcpclient_p.h b/src/serialbus/qmodbustcpclient_p.h
index 7c890bd..d196bb5 100644
--- a/src/serialbus/qmodbustcpclient_p.h
+++ b/src/serialbus/qmodbustcpclient_p.h
@@ -65,8 +65,6 @@ class QModbusTcpClientPrivate : public QModbusClientPrivate
Q_DECLARE_PUBLIC(QModbusTcpClient)
public:
- QModbusTcpClientPrivate() Q_DECL_EQ_DEFAULT;
-
void setupTcpSocket()
{
Q_Q(QModbusTcpClient);
diff --git a/src/serialbus/qmodbustcpserver_p.h b/src/serialbus/qmodbustcpserver_p.h
index 9de51b9..c7f393c 100644
--- a/src/serialbus/qmodbustcpserver_p.h
+++ b/src/serialbus/qmodbustcpserver_p.h
@@ -69,8 +69,6 @@ class QModbusTcpServerPrivate : public QModbusServerPrivate
Q_DECLARE_PUBLIC(QModbusTcpServer)
public:
- QModbusTcpServerPrivate() Q_DECL_EQ_DEFAULT;
-
/*
This function is a workaround since 2nd level lambda below cannot
call protected QModbusTcpServer::processRequest(..) function on VS2013.
diff --git a/tests/auto/qmodbuspdu/tst_qmodbuspdu.cpp b/tests/auto/qmodbuspdu/tst_qmodbuspdu.cpp
index 75bc310..1713d0d 100644
--- a/tests/auto/qmodbuspdu/tst_qmodbuspdu.cpp
+++ b/tests/auto/qmodbuspdu/tst_qmodbuspdu.cpp
@@ -316,7 +316,7 @@ private slots:
d << QModbusExceptionResponse(QModbusExceptionResponse::ReadCoils,
QModbusExceptionResponse::IllegalDataAddress);
}
- QCOMPARE(s_msg, QString::fromLatin1("0x0102"));
+ QCOMPARE(s_msg, QString::fromLatin1("0x8102"));
}
void testMinimumDataSize()
diff --git a/tests/auto/qmodbusserver/tst_qmodbusserver.cpp b/tests/auto/qmodbusserver/tst_qmodbusserver.cpp
index ee4bd94..114063a 100644
--- a/tests/auto/qmodbusserver/tst_qmodbusserver.cpp
+++ b/tests/auto/qmodbusserver/tst_qmodbusserver.cpp
@@ -1086,7 +1086,6 @@ private slots:
class InheritanceTestServer : public QModbusServer
{
public:
- InheritanceTestServer() Q_DECL_EQ_DEFAULT;
void close() Q_DECL_OVERRIDE {}
bool open() Q_DECL_OVERRIDE { return true; }