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 "qmodbuspdu.h"
38#include "qmodbus_symbols_p.h"
39
40#include <QtCore/qdebug.h>
41#include <QtCore/qhash.h>
42
43QT_BEGIN_NAMESPACE
44
45using ReqSizeCalc = QHash<quint8, QModbusRequest::CalcFuncPtr>;
46Q_GLOBAL_STATIC(ReqSizeCalc, requestSizeCalculators);
47
48using ResSizeCalc = QHash<quint8, QModbusResponse::CalcFuncPtr>;
49Q_GLOBAL_STATIC(ResSizeCalc, responseSizeCalculators);
50
51struct QModbusPduPrivate
52{
53 QModbusPduPrivate() = delete;
54 Q_DISABLE_COPY_MOVE(QModbusPduPrivate)
55
56enum struct Type {
57 Request,
58 Response
59};
60
61/*!
62 \internal
63
64 Returns the minimum data size in bytes for the given \a pdu and the
65 Modbus PDU \a type. If the PDU's function code is invalid, undefined
66 or unknown, the return value will be \c {-1}.
67*/
68static int minimumDataSize(const QModbusPdu &pdu, Type type)
69{
70 if (pdu.isException())
71 return 1;
72
73 switch (pdu.functionCode()) {
74 case QModbusPdu::ReadCoils:
75 case QModbusPdu::ReadDiscreteInputs:
76 return type == Type::Request ? 4 : 2;
77 case QModbusPdu::WriteSingleCoil:
78 case QModbusPdu::WriteSingleRegister:
79 return 4;
80 case QModbusPdu::ReadHoldingRegisters:
81 case QModbusPdu::ReadInputRegisters:
82 return type == Type::Request ? 4 : 3;
83 case QModbusPdu::ReadExceptionStatus:
84 return type == Type::Request ? 0 : 1;
85 case QModbusPdu::Diagnostics:
86 return 4;
87 case QModbusPdu::GetCommEventCounter:
88 return type == Type::Request ? 0 : 4;
89 case QModbusPdu::GetCommEventLog:
90 return type == Type::Request ? 0 : 8;
91 case QModbusPdu::WriteMultipleCoils:
92 return type == Type::Request ? 6 : 4;
93 case QModbusPdu::WriteMultipleRegisters:
94 return type == Type::Request ? 7 : 4;
95 case QModbusPdu::ReportServerId:
96 return type == Type::Request ? 0 : 3;
97 case QModbusPdu::ReadFileRecord:
98 return type == Type::Request ? 8 : 5;
99 case QModbusPdu::WriteFileRecord:
100 return 10;
101 case QModbusPdu::MaskWriteRegister:
102 return 6;
103 case QModbusPdu::ReadWriteMultipleRegisters:
104 return type == Type::Request ? 11 : 3;
105 case QModbusPdu::ReadFifoQueue:
106 return type == Type::Request ? 2 : 6;
107 case QModbusPdu::EncapsulatedInterfaceTransport:
108 return 2;
109 case QModbusPdu::Invalid:
110 case QModbusPdu::UndefinedFunctionCode:
111 return -1;
112 }
113 return -1;
114}
115
116/*!
117 \internal
118
119 Extracts a Modbus PDU from a \a stream into the given \a pdu based on \a type.
120*/
121static QDataStream &pduFromStream(QDataStream &stream, Type type, QModbusPdu *pdu)
122{
123 struct RAII {
124 RAII(QModbusPdu *ptr = nullptr)
125 : tmp(ptr) {}
126 QModbusPdu *tmp{ nullptr };
127 ~RAII() { if (tmp) *tmp = {}; }
128 } raii = { pdu };
129
130 QModbusPdu::FunctionCode code = QModbusPdu::FunctionCode::Invalid;
131 if (stream.readRawData(reinterpret_cast<char *>(&code), len: sizeof(quint8)) != sizeof(quint8))
132 return stream;
133 pdu->setFunctionCode(code);
134
135 if (code == QModbusPdu::Invalid || code == QModbusPdu::UndefinedFunctionCode) // shortcut
136 return stream;
137
138 constexpr const int MaxPduDataSize = 252; // in bytes
139
140 // The calculateDataSize(...) function might need some data inside the
141 // given PDU argument to be able to figure out the right data size (e.g.
142 // WriteMultipleCoils contains some kind of "header"). So fake fill the PDU
143 // with the maximum available data but no more than the allowed max PDU
144 // data size.
145 QByteArray data(MaxPduDataSize, Qt::Uninitialized);
146 int read = stream.device()->peek(data: data.data(), maxlen: MaxPduDataSize);
147 if (read < 0)
148 return stream;
149
150 data.resize(size: read);
151 pdu->setData(data);
152
153 const bool isResponse = (type == Type::Response);
154 int size = isResponse ? QModbusResponse::calculateDataSize(pdu: *pdu)
155 : QModbusRequest::calculateDataSize(pdu: *pdu);
156
157 if (isResponse && (code == QModbusPdu::EncapsulatedInterfaceTransport)) {
158 quint8 meiType;
159 pdu->decodeData(newData: &meiType);
160 if (meiType == EncapsulatedInterfaceTransport::ReadDeviceIdentification) {
161 int left = size, offset = 0;
162 while ((left > 0) && (size <= MaxPduDataSize)) {
163 data.resize(size);
164 const int read = stream.readRawData(data.data() + offset, len: size - offset);
165 if ((read < 0) || (read != (size - offset))) {
166 size = 255; // bogus size
167 stream.setStatus(QDataStream::ReadCorruptData);
168 break; // error reading, bail, reset further down
169 }
170 offset += read;
171 left = QModbusResponse::calculateDataSize(pdu: QModbusResponse(code, data)) - offset;
172 size += left;
173 }
174 if ((stream.status() == QDataStream::Ok) && (size <= MaxPduDataSize)) {
175 raii = {};
176 pdu->setData(data);
177 return stream; // early return to avoid second read
178 }
179 } else {
180 data.resize(size: int(stream.device()->size() - 1)); // One byte for the function code.
181 }
182 } else if (pdu->functionCode() == QModbusPdu::Diagnostics) {
183 quint16 subCode;
184 pdu->decodeData(newData: &subCode);
185 if (subCode == Diagnostics::ReturnQueryData)
186 data.resize(size: int(stream.device()->size() - 1)); // One byte for the function code.
187 }
188
189 if (data.size() <= MaxPduDataSize) {
190 data.resize(size);
191 if (stream.readRawData(data.data(), len: data.size()) == size) {
192 raii = {};
193 pdu->setData(data);
194 }
195 }
196 return stream;
197}
198};
199
200/*!
201 \class QModbusPdu
202 \inmodule QtSerialBus
203 \since 5.8
204
205 \brief QModbusPdu is a abstract container class containing the function code and
206 payload that is stored inside a Modbus ADU.
207
208 The class provides access to the raw Modbus protocol packets as defined by
209 the Modbus Application Protocol Specification 1.1b.
210*/
211
212/*!
213 \enum QModbusPdu::ExceptionCode
214
215 This enum describes all the possible error conditions as defined by Modbus Exception Codes.
216 They are set by the server after checking the appropriate error conditions in the reply to a
217 request and must be decoded by the client to operate on the exception code.
218
219 \value IllegalFunction Function code is not supported by device.
220 \value IllegalDataAddress The received data address in the query is not an
221 allowable address for the Modbus server.
222 \value IllegalDataValue The contained value in the request data field is
223 not an allowable value for the Modbus server.
224 \value ServerDeviceFailure An irrecoverable error occurred while the server
225 was attempting to perform the requested action.
226 \value Acknowledge Specialized use in conjunction with programming
227 commands.
228 \value ServerDeviceBusy The server is engaged in processing a long duration
229 program command.
230 \value NegativeAcknowledge The server cannot perform the program function
231 received in the query. This code is returned for an
232 unsuccessful programming request. The client should
233 request diagnostic or error information from the
234 server.
235 \value MemoryParityError Indicates that the extended file area failed to
236 pass a consistency check. Used in conjunction with
237 function codes 20 and 21. The exception code does
238 not refer to any parity settings of the
239 transmission line but only to the servers' internal
240 memory of file records.
241 \value GatewayPathUnavailable Indicates that the gateway was unable to allocate
242 an internal communication path from the input port
243 to the output port for processing the request.
244 \value GatewayTargetDeviceFailedToRespond Indicates that no response was obtained from the
245 target device behind a gateway.
246 Usually this means the target device is not online
247 on the network.
248 \value ExtendedException This is an extended exception as per Modbus
249 specification. Generally this code is used to
250 describe an exception that is otherwise further
251 described.
252*/
253
254/*!
255 \enum QModbusPdu::FunctionCode
256
257 Defines the function code and the implicit type of action required by the server. Not all
258 Modbus devices can handle the same set of function codes.
259
260 \value Invalid Set by the default constructor, do not use.
261 \value ReadCoils Requests the status of one or more coils from a device.
262 \value ReadDiscreteInputs Requests the status of one or more input registers from
263 a device.
264 \value ReadHoldingRegisters Requests the status of one or more holding register
265 values from a device.
266 \value ReadInputRegisters Requests the status of one or more input register
267 values from a device.
268 \value WriteSingleCoil Requests to write a single coil on a device.
269 \value WriteSingleRegister Requests to write a single holding register on a device.
270 \value ReadExceptionStatus Requests the status of the eight Exception Status
271 outputs on a device.
272 \value Diagnostics Used to provide a series of tests for checking the
273 client server communication system, or checking internal
274 \value GetCommEventCounter Requests a status word and an event count from the
275 device's communication event counter.
276 \value GetCommEventLog Requests a status word, event count, message count,
277 and a field of event bytes from a device.
278 \value WriteMultipleCoils Requests to write one or more coils on a device.
279 \value WriteMultipleRegisters Requests to write one or more holding registers on a
280 device.
281 \value ReportServerId Requests the description of the type, the current
282 status, and other information specific to a device.
283 \value ReadFileRecord Requests a file record read.
284 \value WriteFileRecord Requests a file record write.
285 \value MaskWriteRegister Requests to modify the contents of a specified holding
286 register using a combination of an AND or OR mask, and
287 the register's current contents.
288 \value ReadWriteMultipleRegisters Requests the status of one or more holding register and
289 at the same time to write one or more holding registers
290 on a device.
291 \value ReadFifoQueue Requests to read the contents of a First-In-First-Out
292 (FIFO) queue of register in a remote device.
293 \value EncapsulatedInterfaceTransport Please refer to Annex A of the Modbus specification.
294 \value UndefinedFunctionCode Do not use.
295*/
296
297/*!
298 \fn QModbusPdu::QModbusPdu()
299
300 Constructs an invalid QModbusPdu.
301*/
302
303/*!
304 \fn QModbusPdu::~QModbusPdu()
305
306 Destroys a QModbusPdu.
307*/
308
309/*!
310 \fn QModbusPdu::QModbusPdu(const QModbusPdu &other)
311
312 Constructs a QModbusPdu that is a copy of \a other.
313
314*/
315
316/*!
317 \fn QModbusPdu &QModbusPdu::operator=(const QModbusPdu &other)
318
319 Makes a copy of the \a other and assigns it to this QModbusPdu object.
320*/
321
322/*!
323 \fn QModbusPdu::QModbusPdu(FunctionCode code, const QByteArray &data)
324
325 Constructs a QModbusPdu with function code set to \a code and payload set to \a data.
326 The data is expected to be stored in big-endian byte order already.
327*/
328
329/*!
330 \internal
331 \fn template <typename ... Args> QModbusPdu::QModbusPdu(FunctionCode code, Args ... data)
332
333 Constructs a QModbusPdu with function code set to \a code and payload set to \a data.
334 The data is converted and stored in big-endian byte order.
335
336 \note Usage is limited \c quint8 and \c quint16 only. This is because
337 \c QDataStream stream operators will not only append raw data, but also
338 e.g. size, count, etc. for complex types.
339*/
340
341/*!
342 \fn bool QModbusPdu::isValid() const
343
344 Returns true if the PDU is valid; otherwise false.
345
346 A PDU is considered valid if the message code is in the range of 1 to 255 decimal and the
347 PDU's compound size (function code + data) does not exceed 253 bytes. A default constructed
348 PDU is invalid.
349*/
350
351/*!
352 \variable QModbusPdu::ExceptionByte
353
354 The variable is initialized to 0x80.
355
356 Exceptions are reported in a defined packet format. A function code
357 is returned to the requesting client equal to the original function code,
358 except with its most significant bit set. This is equivalent to adding 0x80
359 to the value of the original function code.
360
361 This field may be used to mask the exception bit in the function field
362 of a raw Modbus packet.
363*/
364
365/*!
366 \fn bool QModbusPdu::isException() const
367
368 Returns true if the PDU contains an exception code; otherwise false.
369*/
370
371/*!
372 \fn QModbusPdu::ExceptionCode QModbusPdu::exceptionCode() const
373
374 Returns the response's exception code.
375*/
376
377/*!
378 \fn qint16 QModbusPdu::size() const
379
380 Returns the PDU's full size, including function code and data size.
381*/
382
383/*!
384 \fn qint16 QModbusPdu::dataSize() const
385
386 Returns the PDU's data size, excluding the function code.
387*/
388
389/*!
390 \fn FunctionCode QModbusPdu::functionCode() const
391
392 Returns the PDU's function code.
393*/
394
395/*!
396 \fn void QModbusPdu::setFunctionCode(FunctionCode code)
397
398 Sets the PDU's function code to \a code.
399*/
400
401/*!
402 \fn QByteArray QModbusPdu::data() const
403
404 Returns the PDU's payload, excluding the function code.
405 The payload is stored in big-endian byte order.
406*/
407
408/*!
409 \fn void QModbusPdu::setData(const QByteArray &data)
410
411 Sets the PDU's function payload to \a data. The data is expected to be stored in big-endian
412 byte order already.
413*/
414
415/*!
416 \fn template <typename ... Args> void QModbusPdu::decodeData(Args && ... data) const
417
418 Converts the payload into host endianness and reads it into \a data. Data can be a variable
419 length argument list.
420
421 \code
422 QModbusResponsePdu response(QModbusPdu::ReportServerId);
423 response.encodeData(quint8(0x02), quint8(0x01), quint8(0xff));
424 quint8 count, id, run;
425 response.decodeData(&count, &id, &run);
426 \endcode
427
428 \note Usage is limited \c quint8 and \c quint16 only. This is because
429 \c QDataStream stream operators will not only append raw data, but also
430 e.g. size, count, etc. for complex types.
431*/
432
433/*!
434 \fn template <typename ... Args> void QModbusPdu::encodeData(Args ... data)
435
436 Sets the payload to \a data. The data is converted and stored in big-endian byte order.
437
438 \code
439 QModbusRequestPdu request(QModbusPdu::ReadCoils);
440 // starting address and quantity of coils
441 request.encodeData(quint16(0x0c), quint16(0x0a));
442 \endcode
443
444 \note Usage is limited \c quint8 and \c quint16 only. This is because
445 \c QDataStream stream operators will not only append raw data, but also
446 e.g. size, count, etc. for complex types.
447*/
448
449/*!
450 \relates QModbusPdu
451
452 Writes the Modbus \a pdu to the \a debug stream.
453*/
454QDebug operator<<(QDebug debug, const QModbusPdu &pdu)
455{
456 QDebugStateSaver _(debug);
457 debug.nospace().noquote() << "0x" << Qt::hex << qSetFieldWidth(width: 2) << qSetPadChar(ch: '0')
458 << (pdu.isException() ? pdu.functionCode() | QModbusPdu::ExceptionByte : pdu.functionCode())
459 << qSetFieldWidth(width: 0) << pdu.data().toHex();
460 return debug;
461}
462
463/*!
464 \relates QModbusPdu
465
466 Writes a \a pdu to the \a stream and returns a reference to the stream.
467*/
468QDataStream &operator<<(QDataStream &stream, const QModbusPdu &pdu)
469{
470 if (pdu.isException())
471 stream << static_cast<quint8> (pdu.functionCode() | QModbusPdu::ExceptionByte);
472 else
473 stream << static_cast<quint8> (pdu.functionCode());
474 if (!pdu.data().isEmpty())
475 stream.writeRawData(pdu.data().constData(), len: pdu.data().size());
476
477 return stream;
478}
479
480/*!
481 \class QModbusRequest
482 \inmodule QtSerialBus
483 \since 5.8
484
485 \brief QModbusRequest is a container class containing the function code and payload that is
486 stored inside a Modbus ADU.
487
488 A Modbus request usually consists of a single byte describing the \c FunctionCode and N bytes
489 of payload
490
491 A typical Modbus request can looks like this:
492 \code
493 QModbusRequest request(QModbusRequest::WriteMultipleCoils,
494 QByteArray::fromHex("0013000a02cd01"));
495 \endcode
496 \note When using the constructor taking the \c QByteArray, please make sure to convert the
497 containing data to big-endian byte order before creating the request.
498
499 The same request can be created like this, if the values are known at compile time:
500 \code
501 quint16 startAddress = 19, numberOfCoils = 10;
502 quint8 payloadInBytes = 2, outputHigh = 0xcd, outputLow = 0x01;
503 QModbusRequest request(QModbusRequest::WriteMultipleCoils, startAddress, numberOfCoils,
504 payloadInBytes, outputHigh, outputLow);
505 \endcode
506*/
507
508/*!
509 \typedef QModbusRequest::CalcFuncPtr
510
511 Typedef for a pointer to a custom calculator function with the same signature as
512 \l QModbusRequest::calculateDataSize.
513*/
514
515/*!
516 \fn QModbusRequest::QModbusRequest()
517
518 Constructs an invalid QModbusRequest.
519*/
520
521/*!
522 \fn QModbusRequest::QModbusRequest(const QModbusPdu &pdu)
523
524 Constructs a copy of \a pdu.
525*/
526
527/*!
528 \fn template <typename ... Args> QModbusRequest::QModbusRequest(FunctionCode code, Args... data)
529
530 Constructs a QModbusRequest with function code set to \a code and payload set to \a data.
531 The data is converted and stored in big-endian byte order.
532
533 \note Usage is limited \c quint8 and \c quint16 only. This is because
534 \c QDataStream stream operators will not only append raw data, but also
535 e.g. size, count, etc. for complex types.
536*/
537
538/*!
539 \fn QModbusRequest::QModbusRequest(FunctionCode code, const QByteArray &data = QByteArray())
540
541 Constructs a QModbusResponse with function code set to \a code and payload set to \a data.
542 The data is expected to be stored in big-endian byte order already.
543*/
544
545/*!
546 Returns the expected minimum data size for \a request based on the
547 request's function code; \c {-1} if the function code is not known.
548*/
549int QModbusRequest::minimumDataSize(const QModbusRequest &request)
550{
551 return QModbusPduPrivate::minimumDataSize(pdu: request, type: QModbusPduPrivate::Type::Request);
552}
553
554/*!
555 Calculates the expected data size for \a request based on the request's
556 function code and data. Returns the full size of the request's data part;
557 \c {-1} if the size could not be properly calculated.
558
559 \sa minimumDataSize
560 \sa registerDataSizeCalculator
561*/
562int QModbusRequest::calculateDataSize(const QModbusRequest &request)
563{
564 if (requestSizeCalculators.exists()) {
565 if (auto ptr = requestSizeCalculators()->value(akey: quint8(request.functionCode()), adefaultValue: nullptr))
566 return ptr(request);
567 }
568
569 if (request.isException())
570 return 1;
571
572 int size = -1;
573 int minimum = QModbusPduPrivate::minimumDataSize(pdu: request, type: QModbusPduPrivate::Type::Request);
574 if (minimum < 0)
575 return size;
576
577 switch (request.functionCode()) {
578 case QModbusPdu::WriteMultipleCoils:
579 minimum -= 1; // first payload payload byte
580 if (request.dataSize() >= minimum)
581 size = minimum + quint8(request.data().at(i: minimum - 1)) /*byte count*/;
582 break;
583 case QModbusPdu::WriteMultipleRegisters:
584 case QModbusPdu::ReadWriteMultipleRegisters:
585 minimum -= 2; // first 2 payload payload bytes
586 if (request.dataSize() >= minimum)
587 size = minimum + quint8(request.data().at(i: minimum - 1)) /*byte count*/;
588 break;
589 case QModbusPdu::ReadFileRecord:
590 case QModbusPdu::WriteFileRecord:
591 if (request.dataSize() >= 1)
592 size = 1 /*byte count*/ + quint8(request.data().at(i: 0)) /*actual bytes*/;
593 break;
594 case QModbusPdu::EncapsulatedInterfaceTransport: {
595 if (request.dataSize() < minimum)
596 break; // can't calculate, let's return -1 to indicate error
597 quint8 meiType;
598 request.decodeData(newData: &meiType);
599 // ReadDeviceIdentification -> 3 == MEI type + Read device ID + Object Id
600 size = (meiType == EncapsulatedInterfaceTransport::ReadDeviceIdentification) ? 3 : minimum;
601 } break;
602 default:
603 size = minimum;
604 break;
605 }
606 return size;
607}
608
609/*!
610 This function registers a user-defined implementation to calculate the
611 request data size for function code \a fc. It can be used to extend or
612 override the implementation inside \l QModbusRequest::calculateDataSize().
613
614 The \c CalcFuncPtr is a typedef for a pointer to a custom \a calculator
615 function with the following signature:
616 \code
617 int myCalculateDataSize(const QModbusRequest &pdu);
618 \endcode
619*/
620void QModbusRequest::registerDataSizeCalculator(FunctionCode fc, CalcFuncPtr calculator)
621{
622 requestSizeCalculators()->insert(akey: quint8(fc), avalue: calculator);
623}
624
625/*!
626 \relates QModbusRequest
627
628 Reads a \a pdu from the \a stream and returns a reference to the stream.
629
630 \note The function might fail to properly stream PDU's with function code
631 \l QModbusPdu::Diagnostics or \l QModbusPdu::EncapsulatedInterfaceTransport
632 because of the missing size indicator inside the PDU. In particular this may
633 happen when the PDU is embedded into a stream that doesn't end with the
634 diagnostic/encapsulated request itself.
635*/
636QDataStream &operator>>(QDataStream &stream, QModbusRequest &pdu)
637{
638 return QModbusPduPrivate::pduFromStream(stream, type: QModbusPduPrivate::Type::Request, pdu: &pdu);
639}
640
641/*!
642 \class QModbusResponse
643 \inmodule QtSerialBus
644 \since 5.8
645
646 \brief QModbusResponse is a container class containing the function code and payload that is
647 stored inside a Modbus ADU.
648
649 A typical Modbus response can looks like this:
650 \code
651 QModbusResponse response(QModbusResponse::ReadCoils, QByteArray::fromHex("02cd01"));
652 \endcode
653 \note When using the constructor taking the \c QByteArray, please make sure to convert the
654 containing data to big-endian byte order before creating the request.
655
656 The same response can be created like this, if the values are known at compile time:
657 \code
658 quint8 payloadInBytes = 2, outputHigh = 0xcd, outputLow = 0x01;
659 QModbusResponse response(QModbusResponse::ReadCoils, payloadInBytes, outputHigh, outputLow);
660 \endcode
661*/
662
663
664/*!
665 \typedef QModbusResponse::CalcFuncPtr
666
667 Typedef for a pointer to a custom calculator function with the same signature as
668 \l QModbusResponse::calculateDataSize.
669*/
670
671/*!
672 \fn QModbusResponse::QModbusResponse()
673
674 Constructs an invalid QModbusResponse.
675*/
676
677/*!
678 \fn QModbusResponse::QModbusResponse(const QModbusPdu &pdu)
679
680 Constructs a copy of \a pdu.
681*/
682
683/*!
684 \fn template <typename ... Args> QModbusResponse::QModbusResponse(FunctionCode code, Args... data)
685
686 Constructs a QModbusResponse with function code set to \a code and payload set to \a data.
687 The data is converted and stored in big-endian byte order.
688
689 \note Usage is limited \c quint8 and \c quint16 only. This is because
690 \c QDataStream stream operators will not only append raw data, but also
691 e.g. size, count, etc. for complex types.
692*/
693
694/*!
695 \fn QModbusResponse::QModbusResponse(FunctionCode code, const QByteArray &data = QByteArray())
696
697 Constructs a QModbusResponse with function code set to \a code and payload set to \a data.
698 The data is expected to be stored in big-endian byte order already.
699*/
700
701/*!
702 Returns the expected minimum data size for \a response based on the
703 response's function code; \c {-1} if the function code is not known.
704*/
705int QModbusResponse::minimumDataSize(const QModbusResponse &response)
706{
707 return QModbusPduPrivate::minimumDataSize(pdu: response, type: QModbusPduPrivate::Type::Response);
708}
709
710/*!
711 Calculates the expected data size for \a response, based on the response's
712 function code and data. Returns the full size of the response's data part;
713 \c {-1} if the size could not be properly calculated.
714
715 \sa minimumDataSize
716 \sa registerDataSizeCalculator
717*/
718int QModbusResponse::calculateDataSize(const QModbusResponse &response)
719{
720 if (responseSizeCalculators.exists()) {
721 if (auto ptr = responseSizeCalculators()->value(akey: quint8(response.functionCode()), adefaultValue: nullptr))
722 return ptr(response);
723 }
724
725 if (response.isException())
726 return 1;
727
728 int size = -1;
729 int minimum = QModbusPduPrivate::minimumDataSize(pdu: response, type: QModbusPduPrivate::Type::Response);
730 if (minimum < 0)
731 return size;
732
733 switch (response.functionCode()) {
734 case QModbusResponse::ReadCoils:
735 case QModbusResponse::ReadDiscreteInputs:
736 case QModbusResponse::ReadHoldingRegisters:
737 case QModbusResponse::ReadInputRegisters:
738 case QModbusResponse::GetCommEventLog:
739 case QModbusResponse::ReadFileRecord:
740 case QModbusResponse::WriteFileRecord:
741 case QModbusResponse::ReadWriteMultipleRegisters:
742 case QModbusResponse::ReportServerId:
743 if (response.dataSize() >= 1)
744 size = 1 /*byte count*/ + quint8(response.data().at(i: 0)) /*actual bytes*/;
745 break;
746 case QModbusResponse::ReadFifoQueue: {
747 if (response.dataSize() >= 2) {
748 quint16 rawSize;
749 response.decodeData(newData: &rawSize);
750 size = rawSize + 2; // 2 bytes size info
751 }
752 } break;
753 case QModbusPdu::EncapsulatedInterfaceTransport: {
754 if (response.dataSize() < minimum)
755 break; // can't calculate, let's return -1 to indicate error
756
757 quint8 meiType = 0;
758 response.decodeData(newData: &meiType);
759
760 // update size, header 6 bytes: mei type + read device id + conformity level + more follows
761 // + next object id + number of object
762 // response data part 2 bytes: + object id + object size of the first object -> 8
763 size = (meiType == EncapsulatedInterfaceTransport::ReadDeviceIdentification) ? 8 : minimum;
764 if (meiType != EncapsulatedInterfaceTransport::ReadDeviceIdentification
765 || response.dataSize() < size) {
766 break; // TODO: calculate CanOpenGeneralReference instead of break
767 }
768
769 const QByteArray data = response.data();
770 quint8 numOfObjects = quint8(data[5]);
771 quint8 objectSize = quint8(data[7]);
772
773 // 6 byte header size + (2 * n bytes fixed per object) + first object size
774 size = 6 + (2 * numOfObjects) + objectSize;
775 if ((numOfObjects == 1) || (data.size() < size))
776 break;
777
778 // header + object id + object size + second object id (9 bytes) + first object size
779 int nextSizeField = 9 + objectSize;
780 for (int i = 1; i < numOfObjects; ++i) {
781 if (data.size() <= nextSizeField)
782 break;
783 objectSize = quint8(data[nextSizeField]);
784 size += objectSize;
785 nextSizeField += objectSize + 2; // object size + object id field + object size field
786 }
787 } break;
788 default:
789 size = minimum;
790 break;
791 }
792 return size;
793}
794
795/*!
796 This function registers a user-defined implementation to calculate the
797 response data size for function code \a fc. It can be used to extend or
798 override the implementation inside \l QModbusResponse::calculateDataSize().
799
800 The \c CalcFuncPtr is a typedef for a pointer to a custom \a calculator
801 function with the following signature:
802 \code
803 int myCalculateDataSize(const QModbusResponse &pdu);
804 \endcode
805*/
806void QModbusResponse::registerDataSizeCalculator(FunctionCode fc, CalcFuncPtr calculator)
807{
808 responseSizeCalculators()->insert(akey: quint8(fc), avalue: calculator);
809}
810
811/*!
812 \relates QModbusResponse
813
814 Reads a \a pdu from the \a stream and returns a reference to the stream.
815
816 \note The function might fail to properly stream PDU's with function code
817 \l QModbusPdu::Diagnostics or \l QModbusPdu::EncapsulatedInterfaceTransport
818 because of the missing size indicator inside the PDU. In particular this may
819 happen when the PDU is embedded into a stream that doesn't end with the
820 diagnostic/encapsulated request itself.
821*/
822QDataStream &operator>>(QDataStream &stream, QModbusResponse &pdu)
823{
824 return QModbusPduPrivate::pduFromStream(stream, type: QModbusPduPrivate::Type::Response, pdu: &pdu);
825}
826
827/*!
828 \class QModbusExceptionResponse
829 \inmodule QtSerialBus
830 \since 5.8
831
832 \brief QModbusExceptionResponse is a container class containing the function and error code
833 inside a Modbus ADU.
834
835 A typical QModbusExceptionResponse response can looks like this:
836 \code
837 QModbusExceptionResponse exception(QModbusExceptionResponse::ReportServerId,
838 QModbusExceptionResponse::ServerDeviceFailure);
839 \endcode
840*/
841
842/*!
843 \fn QModbusExceptionResponse::QModbusExceptionResponse()
844
845 Constructs an invalid QModbusExceptionResponse.
846*/
847
848/*!
849 \fn QModbusExceptionResponse::QModbusExceptionResponse(const QModbusPdu &pdu)
850
851 Constructs a copy of \a pdu.
852*/
853
854/*!
855 \fn QModbusExceptionResponse::QModbusExceptionResponse(FunctionCode code, ExceptionCode ec)
856
857 Constructs a QModbusExceptionResponse with function code set to \a code and exception error
858 code set to \a ec.
859*/
860
861/*!
862 \fn void QModbusExceptionResponse::setFunctionCode(FunctionCode c)
863
864 Sets the response's function code to \a c.
865*/
866
867/*!
868 \fn void QModbusExceptionResponse::setExceptionCode(ExceptionCode ec)
869
870 Sets the response's exception code to \a ec.
871*/
872
873QT_END_NAMESPACE
874

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