1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
5#ifndef QMODBUSRTUSERIALCLIENT_P_H
6#define QMODBUSRTUSERIALCLIENT_P_H
7
8#include <QtCore/qloggingcategory.h>
9#include <QtCore/qmath.h>
10#include <QtCore/qpointer.h>
11#include <QtCore/qqueue.h>
12#include <QtCore/qtimer.h>
13#include <QtSerialBus/qmodbusrtuserialclient.h>
14#include <QtSerialPort/qserialport.h>
15
16#include <private/qmodbusadu_p.h>
17#include <private/qmodbusclient_p.h>
18#include <private/qmodbus_symbols_p.h>
19
20//
21// W A R N I N G
22// -------------
23//
24// This file is not part of the Qt API. It exists purely as an
25// implementation detail. This header file may change from version to
26// version without notice, or even be removed.
27//
28// We mean it.
29//
30
31QT_BEGIN_NAMESPACE
32
33Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)
34Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS_LOW)
35
36class Timer : public QObject
37{
38 Q_OBJECT
39
40public:
41 Timer() = default;
42 int start(int msec)
43 {
44 m_timer = QBasicTimer();
45 m_timer.start(msec, t: Qt::PreciseTimer, obj: this);
46 return m_timer.timerId();
47 }
48 void stop() { m_timer.stop(); }
49 bool isActive() const { return m_timer.isActive(); }
50
51signals:
52 void timeout(int timerId);
53
54private:
55 void timerEvent(QTimerEvent *event) override
56 {
57 const auto id = m_timer.timerId();
58 if (event->timerId() == id)
59 emit timeout(timerId: id);
60 }
61
62private:
63 QBasicTimer m_timer;
64};
65
66class QModbusRtuSerialClientPrivate : public QModbusClientPrivate
67{
68 Q_DECLARE_PUBLIC(QModbusRtuSerialClient)
69 enum State
70 {
71 Idle,
72 WaitingForReplay,
73 ProcessReply
74 } m_state = Idle;
75
76public:
77 void onReadyRead()
78 {
79 m_responseBuffer += m_serialPort->read(maxlen: m_serialPort->bytesAvailable());
80 qCDebug(QT_MODBUS_LOW) << "(RTU client) Response buffer:" << m_responseBuffer.toHex();
81
82 if (m_responseBuffer.size() < 2) {
83 qCDebug(QT_MODBUS) << "(RTU client) Modbus ADU not complete";
84 return;
85 }
86
87 const QModbusSerialAdu tmpAdu(QModbusSerialAdu::Rtu, m_responseBuffer);
88 int pduSizeWithoutFcode = QModbusResponse::calculateDataSize(pdu: tmpAdu.pdu());
89 if (pduSizeWithoutFcode < 0) {
90 // wait for more data
91 qCDebug(QT_MODBUS) << "(RTU client) Cannot calculate PDU size for function code:"
92 << tmpAdu.pdu().functionCode() << ", delaying pending frame";
93 return;
94 }
95
96 // server address byte + function code byte + PDU size + 2 bytes CRC
97 int aduSize = 2 + pduSizeWithoutFcode + 2;
98 if (tmpAdu.rawSize() < aduSize) {
99 qCDebug(QT_MODBUS) << "(RTU client) Incomplete ADU received, ignoring";
100 return;
101 }
102
103 if (m_queue.isEmpty())
104 return;
105 auto &current = m_queue.first();
106
107 // Special case for Diagnostics:ReturnQueryData. The response has no
108 // length indicator and is just a simple echo of what we have send.
109 if (tmpAdu.pdu().functionCode() == QModbusPdu::Diagnostics) {
110 const QModbusResponse response = tmpAdu.pdu();
111 if (canMatchRequestAndResponse(response, sendingServer: tmpAdu.serverAddress())) {
112 quint16 subCode = 0xffff;
113 response.decodeData(newData: &subCode);
114 if (subCode == Diagnostics::ReturnQueryData) {
115 if (response.data() != current.requestPdu.data())
116 return; // echo does not match request yet
117 aduSize = 2 + response.dataSize() + 2;
118 if (tmpAdu.rawSize() < aduSize)
119 return; // echo matches, probably checksum missing
120 }
121 }
122 }
123
124 const QModbusSerialAdu adu(QModbusSerialAdu::Rtu, m_responseBuffer.left(n: aduSize));
125 m_responseBuffer.remove(index: 0, len: aduSize);
126
127 qCDebug(QT_MODBUS) << "(RTU client) Received ADU:" << adu.rawData().toHex();
128 if (QT_MODBUS().isDebugEnabled() && !m_responseBuffer.isEmpty())
129 qCDebug(QT_MODBUS_LOW) << "(RTU client) Pending buffer:" << m_responseBuffer.toHex();
130
131 // check CRC
132 if (!adu.matchingChecksum()) {
133 qCWarning(QT_MODBUS) << "(RTU client) Discarding response with wrong CRC, received:"
134 << adu.checksum<quint16>() << ", calculated CRC:"
135 << QModbusSerialAdu::calculateCRC(data: adu.data(), len: adu.size());
136 if (!current.reply.isNull())
137 current.reply->addIntermediateError(error: QModbusClient::ResponseCrcError);
138 return;
139 }
140
141 const QModbusResponse response = adu.pdu();
142 if (!canMatchRequestAndResponse(response, sendingServer: adu.serverAddress())) {
143 qCWarning(QT_MODBUS) << "(RTU client) Cannot match response with open request, "
144 "ignoring";
145 if (!current.reply.isNull())
146 current.reply->addIntermediateError(error: QModbusClient::ResponseRequestMismatch);
147 return;
148 }
149
150 m_state = ProcessReply;
151 m_responseTimer.stop();
152 current.m_timerId = INT_MIN;
153
154 processQueueElement(pdu: response, element: m_queue.dequeue());
155
156 m_state = Idle;
157 scheduleNextRequest(delay: m_interFrameDelayMilliseconds);
158 }
159
160 void onAboutToClose()
161 {
162 Q_Q(QModbusRtuSerialClient);
163 Q_UNUSED(q); // avoid warning in release mode
164 Q_ASSERT(q->state() == QModbusDevice::ClosingState);
165
166 m_responseTimer.stop();
167 }
168
169 void onResponseTimeout(int timerId)
170 {
171 m_responseTimer.stop();
172 if (m_state != State::WaitingForReplay || m_queue.isEmpty())
173 return;
174 const auto &current = m_queue.first();
175
176 if (current.m_timerId != timerId)
177 return;
178
179 qCDebug(QT_MODBUS) << "(RTU client) Receive timeout:" << current.requestPdu;
180
181 if (current.numberOfRetries <= 0) {
182 auto item = m_queue.dequeue();
183 if (item.reply) {
184 item.reply->setError(error: QModbusDevice::TimeoutError,
185 errorText: QModbusClient::tr(s: "Request timeout."));
186 }
187 }
188
189 m_state = Idle;
190 scheduleNextRequest(delay: m_interFrameDelayMilliseconds);
191 }
192
193 void onBytesWritten(qint64 bytes)
194 {
195 if (m_queue.isEmpty())
196 return;
197 auto &current = m_queue.first();
198
199 current.bytesWritten += bytes;
200 if (current.bytesWritten != current.adu.size())
201 return;
202
203 qCDebug(QT_MODBUS) << "(RTU client) Send successful:" << current.requestPdu;
204
205 if (!current.reply.isNull() && current.reply->type() == QModbusReply::Broadcast) {
206 m_state = ProcessReply;
207 processQueueElement(pdu: {}, element: m_queue.dequeue());
208 m_state = Idle;
209 scheduleNextRequest(delay: m_turnaroundDelay);
210 } else {
211 current.m_timerId = m_responseTimer.start(msec: m_responseTimeoutDuration);
212 }
213 }
214
215 void onError(QSerialPort::SerialPortError error)
216 {
217 if (error == QSerialPort::NoError)
218 return;
219
220 qCDebug(QT_MODBUS) << "(RTU server) QSerialPort error:" << error
221 << (m_serialPort ? m_serialPort->errorString() : QString());
222
223 Q_Q(QModbusRtuSerialClient);
224
225 switch (error) {
226 case QSerialPort::DeviceNotFoundError:
227 q->setError(errorText: QModbusDevice::tr(s: "Referenced serial device does not exist."),
228 error: QModbusDevice::ConnectionError);
229 break;
230 case QSerialPort::PermissionError:
231 q->setError(errorText: QModbusDevice::tr(s: "Cannot open serial device due to permissions."),
232 error: QModbusDevice::ConnectionError);
233 break;
234 case QSerialPort::OpenError:
235 case QSerialPort::NotOpenError:
236 q->setError(errorText: QModbusDevice::tr(s: "Cannot open serial device."),
237 error: QModbusDevice::ConnectionError);
238 break;
239 case QSerialPort::WriteError:
240 q->setError(errorText: QModbusDevice::tr(s: "Write error."), error: QModbusDevice::WriteError);
241 break;
242 case QSerialPort::ReadError:
243 q->setError(errorText: QModbusDevice::tr(s: "Read error."), error: QModbusDevice::ReadError);
244 break;
245 case QSerialPort::ResourceError:
246 q->setError(errorText: QModbusDevice::tr(s: "Resource error."), error: QModbusDevice::ConnectionError);
247 break;
248 case QSerialPort::UnsupportedOperationError:
249 q->setError(errorText: QModbusDevice::tr(s: "Device operation is not supported error."),
250 error: QModbusDevice::ConfigurationError);
251 break;
252 case QSerialPort::TimeoutError:
253 q->setError(errorText: QModbusDevice::tr(s: "Timeout error."), error: QModbusDevice::TimeoutError);
254 break;
255 case QSerialPort::UnknownError:
256 q->setError(errorText: QModbusDevice::tr(s: "Unknown error."), error: QModbusDevice::UnknownError);
257 break;
258 default:
259 qCDebug(QT_MODBUS) << "(RTU server) Unhandled QSerialPort error" << error;
260 break;
261 }
262 }
263
264 void setupSerialPort()
265 {
266 Q_Q(QModbusRtuSerialClient);
267 m_serialPort = new QSerialPort(q);
268
269 QObject::connect(sender: &m_responseTimer, signal: &Timer::timeout, context: q, slot: [this](int timerId) {
270 onResponseTimeout(timerId);
271 });
272
273 QObject::connect(sender: m_serialPort, signal: &QSerialPort::readyRead, context: q, slot: [this]() {
274 onReadyRead();
275 });
276
277 QObject::connect(sender: m_serialPort, signal: &QSerialPort::aboutToClose, context: q, slot: [this]() {
278 onAboutToClose();
279 });
280
281 QObject::connect(sender: m_serialPort, signal: &QSerialPort::bytesWritten, context: q, slot: [this](qint64 bytes) {
282 onBytesWritten(bytes);
283 });
284
285 QObject::connect(sender: m_serialPort, signal: &QSerialPort::errorOccurred,
286 context: q, slot: [this](QSerialPort::SerialPortError error) {
287 onError(error);
288 });
289 }
290
291 void setupEnvironment()
292 {
293 if (m_serialPort) {
294 m_serialPort->setPortName(m_comPort);
295 m_serialPort->setParity(m_parity);
296 m_serialPort->setBaudRate(baudRate: m_baudRate);
297 m_serialPort->setDataBits(m_dataBits);
298 m_serialPort->setStopBits(m_stopBits);
299 }
300
301 calculateInterFrameDelay();
302
303 m_responseBuffer.clear();
304 m_state = QModbusRtuSerialClientPrivate::Idle;
305 }
306
307 QModbusReply *enqueueRequest(const QModbusRequest &request, int serverAddress,
308 const QModbusDataUnit &unit, QModbusReply::ReplyType type) override
309 {
310 Q_Q(QModbusRtuSerialClient);
311
312 auto reply = new QModbusReply(serverAddress == 0 ? QModbusReply::Broadcast : type,
313 serverAddress, q);
314 QueueElement element(reply, request, unit, m_numberOfRetries + 1);
315 element.adu = QModbusSerialAdu::create(type: QModbusSerialAdu::Rtu, serverAddress, pdu: request);
316 m_queue.enqueue(t: element);
317
318 scheduleNextRequest(delay: m_interFrameDelayMilliseconds);
319
320 return reply;
321 }
322
323 void scheduleNextRequest(int delay)
324 {
325 Q_Q(QModbusRtuSerialClient);
326
327 if (m_state == Idle && !m_queue.isEmpty()) {
328 m_state = WaitingForReplay;
329 QTimer::singleShot(interval: delay, receiver: q, slot: [this]() { processQueue(); });
330 }
331 }
332
333 void processQueue()
334 {
335 m_responseBuffer.clear();
336 m_serialPort->clear(directions: QSerialPort::AllDirections);
337
338 if (m_queue.isEmpty())
339 return;
340 auto &current = m_queue.first();
341
342 if (current.reply.isNull()) {
343 m_queue.dequeue();
344 m_state = Idle;
345 scheduleNextRequest(delay: m_interFrameDelayMilliseconds);
346 } else {
347 current.bytesWritten = 0;
348 current.numberOfRetries--;
349 m_serialPort->write(data: current.adu);
350
351 qCDebug(QT_MODBUS) << "(RTU client) Sent Serial PDU:" << current.requestPdu;
352 qCDebug(QT_MODBUS_LOW).noquote() << "(RTU client) Sent Serial ADU: 0x" + current.adu
353 .toHex();
354 }
355 }
356
357 bool canMatchRequestAndResponse(const QModbusResponse &response, int sendingServer) const
358 {
359 if (m_queue.isEmpty())
360 return false;
361 const auto &current = m_queue.first();
362
363 if (current.reply.isNull())
364 return false; // reply deleted
365 if (current.reply->serverAddress() != sendingServer)
366 return false; // server mismatch
367 if (current.requestPdu.functionCode() != response.functionCode())
368 return false; // request for different function code
369 return true;
370 }
371
372 bool isOpen() const override
373 {
374 if (m_serialPort)
375 return m_serialPort->isOpen();
376 return false;
377 }
378
379 QIODevice *device() const override { return m_serialPort; }
380
381 Timer m_responseTimer;
382 QByteArray m_responseBuffer;
383
384 QQueue<QueueElement> m_queue;
385 QSerialPort *m_serialPort = nullptr;
386
387 int m_turnaroundDelay = 100; // Recommended value is between 100 and 200 msec.
388};
389
390QT_END_NAMESPACE
391
392#endif // QMODBUSRTUSERIALCLIENT_P_H
393

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