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

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