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

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