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