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