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#include "qmodbusclient.h"
6#include "qmodbusclient_p.h"
7#include "qmodbus_symbols_p.h"
8
9#include <QtCore/qdebug.h>
10#include <QtCore/qloggingcategory.h>
11
12QT_BEGIN_NAMESPACE
13
14Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS)
15
16/*!
17 \class QModbusClient
18 \inmodule QtSerialBus
19 \since 5.8
20
21 \brief The QModbusClient class is the interface to send Modbus requests.
22
23 The QModbusClient API is constructed around one QModbusClient object, which holds the common
24 configuration and settings for the requests it sends. One QModbusClient should be enough for
25 the whole Qt application.
26
27 Once a QModbusClient object has been created, the application can use it to send requests.
28 The returned object is used to obtain any data returned in response to the corresponding request.
29
30 QModbusClient has an asynchronous API. When the finished slot is called, the parameter
31 it takes is the QModbusReply object containing the PDU as well as meta-data (Addressing, etc.).
32
33 Note: QModbusClient queues the requests it receives. The number of requests executed in
34 parallel is dependent on the protocol. For example, the HTTP protocol on desktop platforms
35 issues 6 requests in parallel for one host/port combination.
36*/
37
38/*!
39 Constructs a Modbus client device with the specified \a parent.
40*/
41QModbusClient::QModbusClient(QObject *parent)
42 : QModbusDevice(*new QModbusClientPrivate, parent)
43{
44}
45
46/*!
47 \internal
48*/
49QModbusClient::~QModbusClient()
50{
51}
52
53/*!
54 Sends a request to read the contents of the data pointed by \a read.
55 Returns a new valid \l QModbusReply object if no error occurred, otherwise
56 nullptr. Modbus network may have multiple servers, each server has unique
57 \a serverAddress.
58*/
59QModbusReply *QModbusClient::sendReadRequest(const QModbusDataUnit &read, int serverAddress)
60{
61 Q_D(QModbusClient);
62 return d->sendRequest(request: d->createReadRequest(data: read), serverAddress, unit: &read);
63}
64
65/*!
66 Sends a request to modify the contents of the data pointed by \a write.
67 Returns a new valid \l QModbusReply object if no error occurred, otherwise
68 nullptr. Modbus network may have multiple servers, each server has unique
69 \a serverAddress.
70*/
71QModbusReply *QModbusClient::sendWriteRequest(const QModbusDataUnit &write, int serverAddress)
72{
73 Q_D(QModbusClient);
74 return d->sendRequest(request: d->createWriteRequest(data: write), serverAddress, unit: &write);
75}
76
77/*!
78 Sends a request to read the contents of the data pointed by \a read and to
79 modify the contents of the data pointed by \a write using Modbus function
80 code \l QModbusPdu::ReadWriteMultipleRegisters.
81 Returns a new valid \l QModbusReply object if no error occurred, otherwise
82 nullptr. Modbus network may have multiple servers, each server has unique
83 \a serverAddress.
84
85 \note: Sending this kind of request is only valid of both \a read and
86 \a write are of type QModbusDataUnit::HoldingRegisters.
87*/
88QModbusReply *QModbusClient::sendReadWriteRequest(const QModbusDataUnit &read,
89 const QModbusDataUnit &write, int serverAddress)
90{
91 Q_D(QModbusClient);
92 return d->sendRequest(request: d->createRWRequest(read, write), serverAddress, unit: &read);
93}
94
95/*!
96 Sends a raw Modbus \a request. A raw request can contain anything that
97 fits inside the Modbus PDU data section and has a valid function code.
98 The only check performed before sending is therefore the validity check,
99 see \l QModbusPdu::isValid. If no error occurred the function returns a
100 new valid \l QModbusReply; nullptr otherwise. Modbus networks may have
101 multiple servers, each server has a unique \a serverAddress.
102
103 \sa QModbusReply::rawResult()
104*/
105QModbusReply *QModbusClient::sendRawRequest(const QModbusRequest &request, int serverAddress)
106{
107 return d_func()->sendRequest(request, serverAddress, unit: nullptr);
108}
109
110/*!
111 Returns the timeout value used by this QModbusClient instance in ms.
112 A timeout is indicated by a \l TimeoutError. The default value is 1000 ms.
113
114 \sa setTimeout timeoutChanged()
115*/
116int QModbusClient::timeout() const
117{
118 Q_D(const QModbusClient);
119 return d->m_responseTimeoutDuration;
120}
121
122/*!
123 \fn void QModbusClient::timeoutChanged(int newTimeout)
124
125 This signal is emitted when the timeout used by this QModbusClient instance
126 is changed. The new response timeout for the device is passed as \a newTimeout.
127
128 \sa setTimeout()
129*/
130
131/*!
132 Sets the \a newTimeout for this QModbusClient instance. The minimum timeout
133 is 10 ms.
134
135 The timeout is used by the client to determine how long it waits for
136 a response from the server. If the response is not received within the
137 required timeout, the \l TimeoutError is set.
138
139 Already active/running timeouts are not affected by such timeout duration
140 changes.
141
142 \sa timeout timeoutChanged()
143*/
144void QModbusClient::setTimeout(int newTimeout)
145{
146 if (newTimeout < 10)
147 return;
148
149 Q_D(QModbusClient);
150 if (d->m_responseTimeoutDuration != newTimeout) {
151 d->m_responseTimeoutDuration = newTimeout;
152 emit timeoutChanged(newTimeout);
153 }
154}
155
156/*!
157 Returns the number of retries a client will perform before a
158 request fails. The default value is set to \c 3.
159*/
160int QModbusClient::numberOfRetries() const
161{
162 Q_D(const QModbusClient);
163 return d->m_numberOfRetries;
164}
165
166/*!
167 Sets the \a number of retries a client will perform before a
168 request fails. The default value is set to \c 3.
169
170 \note The new value must be greater than or equal to \c 0. Changing this
171 property will only effect new requests, not already scheduled ones.
172*/
173void QModbusClient::setNumberOfRetries(int number)
174{
175 Q_D(QModbusClient);
176 if (number >= 0)
177 d->m_numberOfRetries = number;
178}
179
180/*!
181 \internal
182*/
183QModbusClient::QModbusClient(QModbusClientPrivate &dd, QObject *parent) :
184 QModbusDevice(dd, parent)
185{
186
187}
188
189/*!
190 Processes a Modbus server \a response and stores the decoded information in
191 \a data. Returns \c true on success; otherwise \c false.
192
193 \note The default implementation does not support all
194 \l {QModbusPdu::}{FunctionCode}s. Override this method in a custom Modbus
195 client implementations to handle the needed functions.
196*/
197bool QModbusClient::processResponse(const QModbusResponse &response, QModbusDataUnit *data)
198{
199 return d_func()->processResponse(response, data);
200}
201
202/*!
203 To be implemented by custom Modbus client implementation. The default implementation ignores
204 \a response and \a data. It always returns false to indicate error.
205*/
206bool QModbusClient::processPrivateResponse(const QModbusResponse &response, QModbusDataUnit *data)
207{
208 Q_UNUSED(response);
209 Q_UNUSED(data);
210 return false;
211}
212
213QModbusReply *QModbusClientPrivate::sendRequest(const QModbusRequest &request, int serverAddress,
214 const QModbusDataUnit *const unit)
215{
216 Q_Q(QModbusClient);
217
218 if (!isOpen() || q->state() != QModbusDevice::ConnectedState) {
219 qCWarning(QT_MODBUS) << "(Client) Device is not connected";
220 q->setError(errorText: QModbusClient::tr(s: "Device not connected."), error: QModbusDevice::ConnectionError);
221 return nullptr;
222 }
223
224 if (!request.isValid()) {
225 qCWarning(QT_MODBUS) << "(Client) Refuse to send invalid request.";
226 q->setError(errorText: QModbusClient::tr(s: "Invalid Modbus request."), error: QModbusDevice::ProtocolError);
227 return nullptr;
228 }
229
230 if (unit)
231 return enqueueRequest(request, serverAddress, *unit, QModbusReply::Common);
232 return enqueueRequest(request, serverAddress, QModbusDataUnit(), QModbusReply::Raw);
233}
234
235QModbusRequest QModbusClientPrivate::createReadRequest(const QModbusDataUnit &data) const
236{
237 if (!data.isValid())
238 return QModbusRequest();
239
240 switch (data.registerType()) {
241 case QModbusDataUnit::Coils:
242 return QModbusRequest(QModbusRequest::ReadCoils, quint16(data.startAddress()),
243 quint16(data.valueCount()));
244 case QModbusDataUnit::DiscreteInputs:
245 return QModbusRequest(QModbusRequest::ReadDiscreteInputs, quint16(data.startAddress()),
246 quint16(data.valueCount()));
247 case QModbusDataUnit::InputRegisters:
248 return QModbusRequest(QModbusRequest::ReadInputRegisters, quint16(data.startAddress()),
249 quint16(data.valueCount()));
250 case QModbusDataUnit::HoldingRegisters:
251 return QModbusRequest(QModbusRequest::ReadHoldingRegisters, quint16(data.startAddress()),
252 quint16(data.valueCount()));
253 default:
254 break;
255 }
256
257 return QModbusRequest();
258}
259
260QModbusRequest QModbusClientPrivate::createWriteRequest(const QModbusDataUnit &data) const
261{
262 switch (data.registerType()) {
263 case QModbusDataUnit::Coils: {
264 if (data.valueCount() == 1) {
265 return QModbusRequest(QModbusRequest::WriteSingleCoil, quint16(data.startAddress()),
266 quint16((data.value(index: 0) == 0u) ? Coil::Off : Coil::On));
267 }
268
269 quint8 byteCount = quint8(data.valueCount() / 8);
270 if ((data.valueCount() % 8) != 0)
271 byteCount += 1;
272
273 qsizetype address = 0;
274 QList<quint8> bytes;
275 for (quint8 i = 0; i < byteCount; ++i) {
276 quint8 byte = 0;
277 for (int currentBit = 0; currentBit < 8; ++currentBit)
278 if (data.value(index: address++))
279 byte |= (1U << currentBit);
280 bytes.append(t: byte);
281 }
282
283 return QModbusRequest(QModbusRequest::WriteMultipleCoils, quint16(data.startAddress()),
284 quint16(data.valueCount()), byteCount, bytes);
285 } break;
286
287 case QModbusDataUnit::HoldingRegisters: {
288 if (data.valueCount() == 1) {
289 return QModbusRequest(QModbusRequest::WriteSingleRegister, quint16(data.startAddress()),
290 data.value(index: 0));
291 }
292
293 const quint8 byteCount = quint8(data.valueCount() * 2);
294 return QModbusRequest(QModbusRequest::WriteMultipleRegisters, quint16(data.startAddress()),
295 quint16(data.valueCount()), byteCount, data.values());
296 } break;
297
298 case QModbusDataUnit::DiscreteInputs:
299 case QModbusDataUnit::InputRegisters:
300 default: // fall through on purpose
301 break;
302 }
303 return QModbusRequest();
304}
305
306QModbusRequest QModbusClientPrivate::createRWRequest(const QModbusDataUnit &read,
307 const QModbusDataUnit &write) const
308{
309 if ((read.registerType() != QModbusDataUnit::HoldingRegisters)
310 || (write.registerType() != QModbusDataUnit::HoldingRegisters)) {
311 return QModbusRequest();
312 }
313
314 const quint8 byteCount = quint8(write.valueCount() * 2);
315 return QModbusRequest(QModbusRequest::ReadWriteMultipleRegisters, quint16(read.startAddress()),
316 quint16(read.valueCount()), quint16(write.startAddress()),
317 quint16(write.valueCount()), byteCount, write.values());
318}
319
320void QModbusClientPrivate::processQueueElement(const QModbusResponse &pdu,
321 const QueueElement &element)
322{
323 if (element.reply.isNull())
324 return;
325
326 element.reply->setRawResult(pdu);
327 if (pdu.isException()) {
328 element.reply->setError(error: QModbusDevice::ProtocolError,
329 errorText: QModbusClient::tr(s: "Modbus Exception Response."));
330 return;
331 }
332
333 if (element.reply->type() == QModbusReply::Broadcast) {
334 element.reply->setFinished(true);
335 return;
336 }
337
338 QModbusDataUnit unit = element.unit;
339 if (!q_func()->processResponse(response: pdu, data: &unit)) {
340 element.reply->setError(error: QModbusDevice::InvalidResponseError,
341 errorText: QModbusClient::tr(s: "An invalid response has been received."));
342 return;
343 }
344
345 element.reply->setResult(unit);
346 element.reply->setFinished(true);
347}
348
349bool QModbusClientPrivate::processResponse(const QModbusResponse &response, QModbusDataUnit *data)
350{
351 switch (response.functionCode()) {
352 case QModbusRequest::ReadCoils:
353 return processReadCoilsResponse(response, data);
354 case QModbusRequest::ReadDiscreteInputs:
355 return processReadDiscreteInputsResponse(response, data);
356 case QModbusRequest::ReadHoldingRegisters:
357 return processReadHoldingRegistersResponse(response, data);
358 case QModbusRequest::ReadInputRegisters:
359 return processReadInputRegistersResponse(response, data);
360 case QModbusRequest::WriteSingleCoil:
361 return processWriteSingleCoilResponse(response, data);
362 case QModbusRequest::WriteSingleRegister:
363 return processWriteSingleRegisterResponse(response, data);
364 case QModbusRequest::ReadExceptionStatus:
365 case QModbusRequest::Diagnostics:
366 case QModbusRequest::GetCommEventCounter:
367 case QModbusRequest::GetCommEventLog:
368 return false; // Return early, it's not a private response.
369 case QModbusRequest::WriteMultipleCoils:
370 return processWriteMultipleCoilsResponse(response, data);
371 case QModbusRequest::WriteMultipleRegisters:
372 return processWriteMultipleRegistersResponse(response, data);
373 case QModbusRequest::ReportServerId:
374 case QModbusRequest::ReadFileRecord:
375 case QModbusRequest::WriteFileRecord:
376 case QModbusRequest::MaskWriteRegister:
377 return false; // Return early, it's not a private response.
378 case QModbusRequest::ReadWriteMultipleRegisters:
379 return processReadWriteMultipleRegistersResponse(response, data);
380 case QModbusRequest::ReadFifoQueue:
381 case QModbusRequest::EncapsulatedInterfaceTransport:
382 return false; // Return early, it's not a private response.
383 default:
384 break;
385 }
386 return q_func()->processPrivateResponse(response, data);
387}
388
389static bool isValid(const QModbusResponse &response, QModbusResponse::FunctionCode fc)
390{
391 if (!response.isValid())
392 return false;
393 if (response.isException())
394 return false;
395 if (response.functionCode() != fc)
396 return false;
397 return true;
398}
399
400bool QModbusClientPrivate::processReadCoilsResponse(const QModbusResponse &response,
401 QModbusDataUnit *data)
402{
403 if (!isValid(response, fc: QModbusResponse::ReadCoils))
404 return false;
405 return collateBits(pdu: response, type: QModbusDataUnit::Coils, data);
406}
407
408bool QModbusClientPrivate::processReadDiscreteInputsResponse(const QModbusResponse &response,
409 QModbusDataUnit *data)
410{
411 if (!isValid(response, fc: QModbusResponse::ReadDiscreteInputs))
412 return false;
413 return collateBits(pdu: response, type: QModbusDataUnit::DiscreteInputs, data);
414}
415
416bool QModbusClientPrivate::collateBits(const QModbusPdu &response,
417 QModbusDataUnit::RegisterType type, QModbusDataUnit *data)
418{
419 if (response.dataSize() < QModbusResponse::minimumDataSize(pdu: response))
420 return false;
421
422 // byte count needs to match available bytes
423 const quint8 byteCount = quint8(response.data().at(i: 0));
424 if ((response.dataSize() - 1) != byteCount)
425 return false;
426
427 if (data) {
428 const int valueCount = byteCount *8;
429 const QByteArray payload = response.data();
430
431 qsizetype value = 0;
432 QList<quint16> values(valueCount);
433 for (qsizetype i = 1; i < payload.size(); ++i) {
434 const quint8 byte = quint8(payload[i]);
435 for (qint32 currentBit = 0; currentBit < 8 && value < valueCount; ++currentBit)
436 values[value++] = (byte & (1U << currentBit) ? 1 : 0);
437 }
438 data->setValues(values);
439 data->setRegisterType(type);
440 }
441 return true;
442}
443
444bool QModbusClientPrivate::processReadHoldingRegistersResponse(const QModbusResponse &response,
445 QModbusDataUnit *data)
446{
447 if (!isValid(response, fc: QModbusResponse::ReadHoldingRegisters))
448 return false;
449 return collateBytes(pdu: response, type: QModbusDataUnit::HoldingRegisters, data);
450}
451
452bool QModbusClientPrivate::processReadInputRegistersResponse(const QModbusResponse &response,
453 QModbusDataUnit *data)
454{
455 if (!isValid(response, fc: QModbusResponse::ReadInputRegisters))
456 return false;
457 return collateBytes(pdu: response, type: QModbusDataUnit::InputRegisters, data);
458}
459
460bool QModbusClientPrivate::collateBytes(const QModbusPdu &response,
461 QModbusDataUnit::RegisterType type, QModbusDataUnit *data)
462{
463 if (response.dataSize() < QModbusResponse::minimumDataSize(pdu: response))
464 return false;
465
466 // byte count needs to match available bytes
467 const quint8 byteCount = quint8(response.data().at(i: 0));
468 if ((response.dataSize() - 1) != byteCount)
469 return false;
470
471 // byte count needs to be even to match full registers
472 if (byteCount % 2 != 0)
473 return false;
474
475 if (data) {
476 QDataStream stream(response.data().remove(index: 0, len: 1));
477
478 QList<quint16> values;
479 const quint8 itemCount = byteCount / 2;
480 for (int i = 0; i < itemCount; i++) {
481 quint16 tmp;
482 stream >> tmp;
483 values.append(t: tmp);
484 }
485 data->setValues(values);
486 data->setRegisterType(type);
487 }
488 return true;
489}
490
491bool QModbusClientPrivate::processWriteSingleCoilResponse(const QModbusResponse &response,
492 QModbusDataUnit *data)
493{
494 if (!isValid(response, fc: QModbusResponse::WriteSingleCoil))
495 return false;
496 return collateSingleValue(pdu: response, type: QModbusDataUnit::Coils, data);
497}
498
499bool QModbusClientPrivate::processWriteSingleRegisterResponse(const QModbusResponse &response,
500 QModbusDataUnit *data)
501{
502 if (!isValid(response, fc: QModbusResponse::WriteSingleRegister))
503 return false;
504 return collateSingleValue(pdu: response, type: QModbusDataUnit::HoldingRegisters, data);
505}
506
507bool QModbusClientPrivate::collateSingleValue(const QModbusPdu &response,
508 QModbusDataUnit::RegisterType type, QModbusDataUnit *data)
509{
510 if (response.dataSize() != QModbusResponse::minimumDataSize(pdu: response))
511 return false;
512
513 quint16 address = 0, value = 0xffff;
514 response.decodeData(newData: &address, newData: &value);
515 if ((type == QModbusDataUnit::Coils) && (value != Coil::Off) && (value != Coil::On))
516 return false;
517
518 if (data) {
519 data->setRegisterType(type);
520 data->setStartAddress(address);
521 data->setValues(QList<quint16> { value });
522 }
523 return true;
524}
525
526bool QModbusClientPrivate::processWriteMultipleCoilsResponse(const QModbusResponse &response,
527 QModbusDataUnit *data)
528{
529 if (!isValid(response, fc: QModbusResponse::WriteMultipleCoils))
530 return false;
531 return collateMultipleValues(pdu: response, type: QModbusDataUnit::Coils, data);
532}
533
534bool QModbusClientPrivate::processWriteMultipleRegistersResponse(const QModbusResponse &response,
535 QModbusDataUnit *data)
536{
537 if (!isValid(response, fc: QModbusResponse::WriteMultipleRegisters))
538 return false;
539 return collateMultipleValues(pdu: response, type: QModbusDataUnit::HoldingRegisters, data);
540}
541
542bool QModbusClientPrivate::collateMultipleValues(const QModbusPdu &response,
543 QModbusDataUnit::RegisterType type, QModbusDataUnit *data)
544{
545 if (response.dataSize() != QModbusResponse::minimumDataSize(pdu: response))
546 return false;
547
548 quint16 address = 0, count = 0;
549 response.decodeData(newData: &address, newData: &count);
550
551 // number of registers to write is 1-123 per request
552 if ((type == QModbusDataUnit::HoldingRegisters) && (count < 1 || count > 123))
553 return false;
554
555 if (data) {
556 data->setValueCount(count);
557 data->setRegisterType(type);
558 data->setStartAddress(address);
559 }
560 return true;
561}
562
563bool QModbusClientPrivate::processReadWriteMultipleRegistersResponse(const QModbusResponse &resp,
564 QModbusDataUnit *data)
565{
566 if (!isValid(response: resp, fc: QModbusResponse::ReadWriteMultipleRegisters))
567 return false;
568 return collateBytes(response: resp, type: QModbusDataUnit::HoldingRegisters, data);
569}
570
571QT_END_NAMESPACE
572

source code of qtserialbus/src/serialbus/qmodbusclient.cpp