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 | |