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
63QT_BEGIN_NAMESPACE
64
65Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)
66Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS_LOW)
67
68class Timer : public QObject
69{
70 Q_OBJECT
71
72public:
73 Timer() = default;
74 int start(int msec)
75 {
76 m_timer = QBasicTimer();
77 m_timer.start(msec, timerType: Qt::PreciseTimer, obj: this);
78 return m_timer.timerId();
79 }
80 void stop() { m_timer.stop(); }
81 bool isActive() const { return m_timer.isActive(); }
82
83signals:
84 void timeout(int timerId);
85
86private:
87 void timerEvent(QTimerEvent *event) override
88 {
89 const auto id = m_timer.timerId();
90 if (event->timerId() == id)
91 emit timeout(timerId: id);
92 }
93
94private:
95 QBasicTimer m_timer;
96};
97
98class QModbusRtuSerialMasterPrivate : public QModbusClientPrivate
99{
100 Q_DECLARE_PUBLIC(QModbusRtuSerialMaster)
101 enum State
102 {
103 Idle,
104 WaitingForReplay,
105 ProcessReply
106 } m_state = Idle;
107
108public:
109 void onReadyRead()
110 {
111 m_responseBuffer += m_serialPort->read(maxlen: 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(pdu: 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, sendingServer: tmpAdu.serverAddress())) {
144 quint16 subCode = 0xffff;
145 response.decodeData(newData: &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(len: aduSize));
157 m_responseBuffer.remove(index: 0, len: 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(data: adu.data(), len: adu.size());
168 return;
169 }
170
171 const QModbusResponse response = adu.pdu();
172 if (!canMatchRequestAndResponse(response, sendingServer: 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(pdu: response, element: m_queue.dequeue());
183
184 m_state = Idle;
185 scheduleNextRequest(delay: m_interFrameDelayMilliseconds);
186 }
187
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
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(error: QModbusDevice::TimeoutError,
213 errorText: QModbusClient::tr(s: "Request timeout."));
214 }
215 }
216
217 m_state = Idle;
218 scheduleNextRequest(delay: m_interFrameDelayMilliseconds);
219 }
220
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(pdu: {}, element: m_queue.dequeue());
236 m_state = Idle;
237 scheduleNextRequest(delay: m_turnaroundDelay);
238 } else {
239 current.m_timerId = m_responseTimer.start(msec: m_responseTimeoutDuration);
240 }
241 }
242
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(errorText: QModbusDevice::tr(s: "Referenced serial device does not exist."),
256 error: QModbusDevice::ConnectionError);
257 break;
258 case QSerialPort::PermissionError:
259 q->setError(errorText: QModbusDevice::tr(s: "Cannot open serial device due to permissions."),
260 error: QModbusDevice::ConnectionError);
261 break;
262 case QSerialPort::OpenError:
263 case QSerialPort::NotOpenError:
264 q->setError(errorText: QModbusDevice::tr(s: "Cannot open serial device."),
265 error: QModbusDevice::ConnectionError);
266 break;
267 case QSerialPort::WriteError:
268 q->setError(errorText: QModbusDevice::tr(s: "Write error."), error: QModbusDevice::WriteError);
269 break;
270 case QSerialPort::ReadError:
271 q->setError(errorText: QModbusDevice::tr(s: "Read error."), error: QModbusDevice::ReadError);
272 break;
273 case QSerialPort::ResourceError:
274 q->setError(errorText: QModbusDevice::tr(s: "Resource error."), error: QModbusDevice::ConnectionError);
275 break;
276 case QSerialPort::UnsupportedOperationError:
277 q->setError(errorText: QModbusDevice::tr(s: "Device operation is not supported error."),
278 error: QModbusDevice::ConfigurationError);
279 break;
280 case QSerialPort::TimeoutError:
281 q->setError(errorText: QModbusDevice::tr(s: "Timeout error."), error: QModbusDevice::TimeoutError);
282 break;
283 case QSerialPort::UnknownError:
284 q->setError(errorText: QModbusDevice::tr(s: "Unknown error."), error: QModbusDevice::UnknownError);
285 break;
286 default:
287 qCDebug(QT_MODBUS) << "(RTU server) Unhandled QSerialPort error" << error;
288 break;
289 }
290 }
291
292 void setupSerialPort()
293 {
294 Q_Q(QModbusRtuSerialMaster);
295 m_serialPort = new QSerialPort(q);
296
297 QObject::connect(sender: &m_responseTimer, signal: &Timer::timeout, context: q, slot: [this](int timerId) {
298 onResponseTimeout(timerId);
299 });
300
301 QObject::connect(sender: m_serialPort, signal: &QSerialPort::readyRead, context: q, slot: [this]() {
302 onReadyRead();
303 });
304
305 QObject::connect(sender: m_serialPort, signal: &QSerialPort::aboutToClose, context: q, slot: [this]() {
306 onAboutToClose();
307 });
308
309 QObject::connect(sender: m_serialPort, signal: &QSerialPort::bytesWritten, context: q, slot: [this](qint64 bytes) {
310 onBytesWritten(bytes);
311 });
312
313 QObject::connect(sender: m_serialPort, signal: &QSerialPort::errorOccurred,
314 context: q, slot: [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 */
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(v: 3500. / (qreal(m_baudRate) / 11.));
337 }
338 if (m_interFrameDelayMilliseconds < delayMilliSeconds)
339 m_interFrameDelayMilliseconds = delayMilliSeconds;
340 }
341
342 void setupEnvironment()
343 {
344 if (m_serialPort) {
345 m_serialPort->setPortName(m_comPort);
346 m_serialPort->setParity(m_parity);
347 m_serialPort->setBaudRate(baudRate: 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
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(type: QModbusSerialAdu::Rtu, serverAddress, pdu: request);
367 m_queue.enqueue(t: element);
368
369 scheduleNextRequest(delay: m_interFrameDelayMilliseconds);
370
371 return reply;
372 }
373
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(interval: delay, context: q, slot: [this]() { processQueue(); });
381 }
382 }
383
384 void processQueue()
385 {
386 m_responseBuffer.clear();
387 m_serialPort->clear(directions: 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(delay: m_interFrameDelayMilliseconds);
397 } else {
398 current.bytesWritten = 0;
399 current.numberOfRetries--;
400 m_serialPort->write(data: 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
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
423 bool isOpen() const override
424 {
425 if (m_serialPort)
426 return m_serialPort->isOpen();
427 return false;
428 }
429
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
442QT_END_NAMESPACE
443
444#include "qmodbusrtuserialmaster_p.h"
445
446#endif // QMODBUSSERIALMASTER_P_H
447

source code of qtserialbus/src/serialbus/qmodbusrtuserialmaster_p.h