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

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