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