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

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