diff options
author | Alex Blasche <alexander.blasche@theqtcompany.com> | 2016-02-19 08:51:01 +0100 |
---|---|---|
committer | Alex Blasche <alexander.blasche@theqtcompany.com> | 2016-02-19 08:51:01 +0100 |
commit | 246c93df987e3ee49ba18a3d50cc9d96d35b9672 (patch) | |
tree | ef44b18c77bd81b5db0a8f875002face3e4594f9 | |
parent | ed6ec90cb81cdd66738627f264e928da3857d1fd (diff) | |
parent | 6956e682340221be286080ec9b40d0ecf6fc882d (diff) |
Merge remote-tracking branch 'gerrit/5.6' into 5.7
Conflicts:
.qmake.conf
Change-Id: I663edd2b2accf1548acf5a156df7fef9d2024f51
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; } |