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