1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtSerialBus module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36 
37 #ifndef QMODBUSSERIALMASTER_P_H
38 #define QMODBUSSERIALMASTER_P_H
39 
40 #include <QtCore/qloggingcategory.h>
41 #include <QtCore/qmath.h>
42 #include <QtCore/qpointer.h>
43 #include <QtCore/qqueue.h>
44 #include <QtCore/qtimer.h>
45 #include <QtSerialBus/qmodbusrtuserialmaster.h>
46 #include <QtSerialPort/qserialport.h>
47 
48 #include <private/qmodbusadu_p.h>
49 #include <private/qmodbusclient_p.h>
50 #include <private/qmodbus_symbols_p.h>
51 
52 //
53 //  W A R N I N G
54 //  -------------
55 //
56 // This file is not part of the Qt API. It exists purely as an
57 // implementation detail. This header file may change from version to
58 // version without notice, or even be removed.
59 //
60 // We mean it.
61 //
62 
63 QT_BEGIN_NAMESPACE
64 
Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)65 Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)
66 Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS_LOW)
67 
68 class Timer : public QObject
69 {
70     Q_OBJECT
71 
72 public:
73     Timer() = default;
74     int start(int msec)
75     {
76         m_timer = QBasicTimer();
77         m_timer.start(msec, Qt::PreciseTimer, this);
78         return m_timer.timerId();
79     }
80     void stop() { m_timer.stop(); }
81     bool isActive() const { return m_timer.isActive(); }
82 
83 signals:
84     void timeout(int timerId);
85 
86 private:
87     void timerEvent(QTimerEvent *event) override
88     {
89         const auto id = m_timer.timerId();
90         if (event->timerId() == id)
91             emit timeout(id);
92     }
93 
94 private:
95     QBasicTimer m_timer;
96 };
97 
98 class QModbusRtuSerialMasterPrivate : public QModbusClientPrivate
99 {
100     Q_DECLARE_PUBLIC(QModbusRtuSerialMaster)
101         enum State
102     {
103         Idle,
104         WaitingForReplay,
105         ProcessReply
106     } m_state = Idle;
107 
108 public:
onReadyRead()109     void onReadyRead()
110     {
111         m_responseBuffer += m_serialPort->read(m_serialPort->bytesAvailable());
112         qCDebug(QT_MODBUS_LOW) << "(RTU client) Response buffer:" << m_responseBuffer.toHex();
113 
114         if (m_responseBuffer.size() < 2) {
115             qCDebug(QT_MODBUS) << "(RTU client) Modbus ADU not complete";
116             return;
117         }
118 
119         const QModbusSerialAdu tmpAdu(QModbusSerialAdu::Rtu, m_responseBuffer);
120         int pduSizeWithoutFcode = QModbusResponse::calculateDataSize(tmpAdu.pdu());
121         if (pduSizeWithoutFcode < 0) {
122             // wait for more data
123             qCDebug(QT_MODBUS) << "(RTU client) Cannot calculate PDU size for function code:"
124                 << tmpAdu.pdu().functionCode() << ", delaying pending frame";
125             return;
126         }
127 
128         // server address byte + function code byte + PDU size + 2 bytes CRC
129         int aduSize = 2 + pduSizeWithoutFcode + 2;
130         if (tmpAdu.rawSize() < aduSize) {
131             qCDebug(QT_MODBUS) << "(RTU client) Incomplete ADU received, ignoring";
132             return;
133         }
134 
135         if (m_queue.isEmpty())
136             return;
137         auto &current = m_queue.first();
138 
139         // Special case for Diagnostics:ReturnQueryData. The response has no
140         // length indicator and is just a simple echo of what we have send.
141         if (tmpAdu.pdu().functionCode() == QModbusPdu::Diagnostics) {
142             const QModbusResponse response = tmpAdu.pdu();
143             if (canMatchRequestAndResponse(response, tmpAdu.serverAddress())) {
144                 quint16 subCode = 0xffff;
145                 response.decodeData(&subCode);
146                 if (subCode == Diagnostics::ReturnQueryData) {
147                     if (response.data() != current.requestPdu.data())
148                         return; // echo does not match request yet
149                     aduSize = 2 + response.dataSize() + 2;
150                     if (tmpAdu.rawSize() < aduSize)
151                         return; // echo matches, probably checksum missing
152                 }
153             }
154         }
155 
156         const QModbusSerialAdu adu(QModbusSerialAdu::Rtu, m_responseBuffer.left(aduSize));
157         m_responseBuffer.remove(0, aduSize);
158 
159         qCDebug(QT_MODBUS) << "(RTU client) Received ADU:" << adu.rawData().toHex();
160         if (QT_MODBUS().isDebugEnabled() && !m_responseBuffer.isEmpty())
161             qCDebug(QT_MODBUS_LOW) << "(RTU client) Pending buffer:" << m_responseBuffer.toHex();
162 
163         // check CRC
164         if (!adu.matchingChecksum()) {
165             qCWarning(QT_MODBUS) << "(RTU client) Discarding response with wrong CRC, received:"
166                 << adu.checksum<quint16>() << ", calculated CRC:"
167                 << QModbusSerialAdu::calculateCRC(adu.data(), adu.size());
168             return;
169         }
170 
171         const QModbusResponse response = adu.pdu();
172         if (!canMatchRequestAndResponse(response, adu.serverAddress())) {
173             qCWarning(QT_MODBUS) << "(RTU client) Cannot match response with open request, "
174                 "ignoring";
175             return;
176         }
177 
178         m_state = ProcessReply;
179         m_responseTimer.stop();
180         current.m_timerId = INT_MIN;
181 
182         processQueueElement(response, m_queue.dequeue());
183 
184         m_state = Idle;
185         scheduleNextRequest(m_interFrameDelayMilliseconds);
186     }
187 
onAboutToClose()188     void onAboutToClose()
189     {
190         Q_Q(QModbusRtuSerialMaster);
191         Q_UNUSED(q) // avoid warning in release mode
192         Q_ASSERT(q->state() == QModbusDevice::ClosingState);
193 
194         m_responseTimer.stop();
195     }
196 
onResponseTimeout(int timerId)197     void onResponseTimeout(int timerId)
198     {
199         m_responseTimer.stop();
200         if (m_state != State::WaitingForReplay || m_queue.isEmpty())
201             return;
202         const auto current = m_queue.first();
203 
204         if (current.m_timerId != timerId)
205             return;
206 
207         qCDebug(QT_MODBUS) << "(RTU client) Receive timeout:" << current.requestPdu;
208 
209         if (current.numberOfRetries <= 0) {
210             auto item = m_queue.dequeue();
211             if (item.reply) {
212                 item.reply->setError(QModbusDevice::TimeoutError,
213                     QModbusClient::tr("Request timeout."));
214             }
215         }
216 
217         m_state = Idle;
218         scheduleNextRequest(m_interFrameDelayMilliseconds);
219     }
220 
onBytesWritten(qint64 bytes)221     void onBytesWritten(qint64 bytes)
222     {
223         if (m_queue.isEmpty())
224             return;
225         auto &current = m_queue.first();
226 
227         current.bytesWritten += bytes;
228         if (current.bytesWritten != current.adu.size())
229             return;
230 
231         qCDebug(QT_MODBUS) << "(RTU client) Send successful:" << current.requestPdu;
232 
233         if (!current.reply.isNull() && current.reply->type() == QModbusReply::Broadcast) {
234             m_state = ProcessReply;
235             processQueueElement({}, m_queue.dequeue());
236             m_state = Idle;
237             scheduleNextRequest(m_turnaroundDelay);
238         } else {
239             current.m_timerId = m_responseTimer.start(m_responseTimeoutDuration);
240         }
241     }
242 
onError(QSerialPort::SerialPortError error)243     void onError(QSerialPort::SerialPortError error)
244     {
245         if (error == QSerialPort::NoError)
246             return;
247 
248         qCDebug(QT_MODBUS) << "(RTU server) QSerialPort error:" << error
249             << (m_serialPort ? m_serialPort->errorString() : QString());
250 
251         Q_Q(QModbusRtuSerialMaster);
252 
253         switch (error) {
254         case QSerialPort::DeviceNotFoundError:
255             q->setError(QModbusDevice::tr("Referenced serial device does not exist."),
256                 QModbusDevice::ConnectionError);
257             break;
258         case QSerialPort::PermissionError:
259             q->setError(QModbusDevice::tr("Cannot open serial device due to permissions."),
260                 QModbusDevice::ConnectionError);
261             break;
262         case QSerialPort::OpenError:
263         case QSerialPort::NotOpenError:
264             q->setError(QModbusDevice::tr("Cannot open serial device."),
265                 QModbusDevice::ConnectionError);
266             break;
267         case QSerialPort::WriteError:
268             q->setError(QModbusDevice::tr("Write error."), QModbusDevice::WriteError);
269             break;
270         case QSerialPort::ReadError:
271             q->setError(QModbusDevice::tr("Read error."), QModbusDevice::ReadError);
272             break;
273         case QSerialPort::ResourceError:
274             q->setError(QModbusDevice::tr("Resource error."), QModbusDevice::ConnectionError);
275             break;
276         case QSerialPort::UnsupportedOperationError:
277             q->setError(QModbusDevice::tr("Device operation is not supported error."),
278                 QModbusDevice::ConfigurationError);
279             break;
280         case QSerialPort::TimeoutError:
281             q->setError(QModbusDevice::tr("Timeout error."), QModbusDevice::TimeoutError);
282             break;
283         case QSerialPort::UnknownError:
284             q->setError(QModbusDevice::tr("Unknown error."), QModbusDevice::UnknownError);
285             break;
286         default:
287             qCDebug(QT_MODBUS) << "(RTU server) Unhandled QSerialPort error" << error;
288             break;
289         }
290     }
291 
setupSerialPort()292     void setupSerialPort()
293     {
294         Q_Q(QModbusRtuSerialMaster);
295         m_serialPort = new QSerialPort(q);
296 
297         QObject::connect(&m_responseTimer, &Timer::timeout, q, [this](int timerId) {
298             onResponseTimeout(timerId);
299         });
300 
301         QObject::connect(m_serialPort, &QSerialPort::readyRead, q, [this]() {
302             onReadyRead();
303         });
304 
305         QObject::connect(m_serialPort, &QSerialPort::aboutToClose, q, [this]() {
306             onAboutToClose();
307         });
308 
309         QObject::connect(m_serialPort, &QSerialPort::bytesWritten, q, [this](qint64 bytes) {
310             onBytesWritten(bytes);
311         });
312 
313         QObject::connect(m_serialPort, &QSerialPort::errorOccurred,
314                 q, [this](QSerialPort::SerialPortError error) {
315             onError(error);
316         });
317     }
318 
319     /*!
320         According to the Modbus specification, in RTU mode message frames
321         are separated by a silent interval of at least 3.5 character times.
322         Calculate the timeout if we are less than 19200 baud, use a fixed
323         timeout for everything equal or greater than 19200 baud.
324         If the user set the timeout to be longer than the calculated one,
325         we'll keep the user defined.
326     */
calculateInterFrameDelay()327     void calculateInterFrameDelay()
328     {
329         // The spec recommends a timeout value of 1.750 msec. Without such
330         // precise single-shot timers use a approximated value of 1.750 msec.
331         int delayMilliSeconds = 2;
332         if (m_baudRate < 19200) {
333             // Example: 9600 baud, 11 bit per packet -> 872 char/sec
334             // so: 1000 ms / 872 char = 1.147 ms/char * 3.5 character
335             // Always round up because the spec requests at least 3.5 char.
336             delayMilliSeconds = qCeil(3500. / (qreal(m_baudRate) / 11.));
337         }
338         if (m_interFrameDelayMilliseconds < delayMilliSeconds)
339             m_interFrameDelayMilliseconds = delayMilliSeconds;
340     }
341 
setupEnvironment()342     void setupEnvironment()
343     {
344         if (m_serialPort) {
345             m_serialPort->setPortName(m_comPort);
346             m_serialPort->setParity(m_parity);
347             m_serialPort->setBaudRate(m_baudRate);
348             m_serialPort->setDataBits(m_dataBits);
349             m_serialPort->setStopBits(m_stopBits);
350         }
351 
352         calculateInterFrameDelay();
353 
354         m_responseBuffer.clear();
355         m_state = QModbusRtuSerialMasterPrivate::Idle;
356     }
357 
enqueueRequest(const QModbusRequest & request,int serverAddress,const QModbusDataUnit & unit,QModbusReply::ReplyType type)358     QModbusReply *enqueueRequest(const QModbusRequest &request, int serverAddress,
359         const QModbusDataUnit &unit, QModbusReply::ReplyType type) override
360     {
361         Q_Q(QModbusRtuSerialMaster);
362 
363         auto reply = new QModbusReply(serverAddress == 0 ? QModbusReply::Broadcast : type,
364             serverAddress, q);
365         QueueElement element(reply, request, unit, m_numberOfRetries + 1);
366         element.adu = QModbusSerialAdu::create(QModbusSerialAdu::Rtu, serverAddress, request);
367         m_queue.enqueue(element);
368 
369         scheduleNextRequest(m_interFrameDelayMilliseconds);
370 
371         return reply;
372     }
373 
scheduleNextRequest(int delay)374     void scheduleNextRequest(int delay)
375     {
376         Q_Q(QModbusRtuSerialMaster);
377 
378         if (m_state == Idle && !m_queue.isEmpty()) {
379             m_state = WaitingForReplay;
380             QTimer::singleShot(delay, q, [this]() { processQueue(); });
381         }
382     }
383 
processQueue()384     void processQueue()
385     {
386         m_responseBuffer.clear();
387         m_serialPort->clear(QSerialPort::AllDirections);
388 
389         if (m_queue.isEmpty())
390             return;
391         auto &current = m_queue.first();
392 
393         if (current.reply.isNull()) {
394             m_queue.dequeue();
395             m_state = Idle;
396             scheduleNextRequest(m_interFrameDelayMilliseconds);
397         } else {
398             current.bytesWritten = 0;
399             current.numberOfRetries--;
400             m_serialPort->write(current.adu);
401 
402             qCDebug(QT_MODBUS) << "(RTU client) Sent Serial PDU:" << current.requestPdu;
403             qCDebug(QT_MODBUS_LOW).noquote() << "(RTU client) Sent Serial ADU: 0x" + current.adu
404                 .toHex();
405         }
406     }
407 
canMatchRequestAndResponse(const QModbusResponse & response,int sendingServer)408     bool canMatchRequestAndResponse(const QModbusResponse &response, int sendingServer) const
409     {
410         if (m_queue.isEmpty())
411             return false;
412         const auto &current = m_queue.first();
413 
414         if (current.reply.isNull())
415             return false;   // reply deleted
416         if (current.reply->serverAddress() != sendingServer)
417             return false;   // server mismatch
418         if (current.requestPdu.functionCode() != response.functionCode())
419             return false;   // request for different function code
420         return true;
421     }
422 
isOpen()423     bool isOpen() const override
424     {
425         if (m_serialPort)
426             return m_serialPort->isOpen();
427         return false;
428     }
429 
device()430     QIODevice *device() const override { return m_serialPort; }
431 
432     Timer m_responseTimer;
433     QByteArray m_responseBuffer;
434 
435     QQueue<QueueElement> m_queue;
436     QSerialPort *m_serialPort = nullptr;
437 
438     int m_interFrameDelayMilliseconds = 2; // A approximated value of 1.750 msec.
439     int m_turnaroundDelay = 100; // Recommended value is between 100 and 200 msec.
440 };
441 
442 QT_END_NAMESPACE
443 
444 #include "qmodbusrtuserialmaster_p.h"
445 
446 #endif // QMODBUSSERIALMASTER_P_H
447