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 "qmodbusdeviceidentification.h" |
5 | #include "qmodbusserver.h" |
6 | #include "qmodbusserver_p.h" |
7 | #include "qmodbus_symbols_p.h" |
8 | |
9 | #include <QtCore/qbitarray.h> |
10 | #include <QtCore/qdebug.h> |
11 | #include <QtCore/qlist.h> |
12 | #include <QtCore/qloggingcategory.h> |
13 | |
14 | #include <algorithm> |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS) |
19 | |
20 | /*! |
21 | \class QModbusServer |
22 | \inmodule QtSerialBus |
23 | \since 5.8 |
24 | |
25 | \brief The QModbusServer class is the interface to receive and process Modbus requests. |
26 | |
27 | Modbus networks can have multiple Modbus servers. Modbus Servers are read/written by a |
28 | Modbus client represented by \l QModbusClient. QModbusServer communicates with a Modbus |
29 | backend, providing users with a convenient API. |
30 | */ |
31 | |
32 | /*! |
33 | \enum QModbusServer::Option |
34 | |
35 | Each Modbus server has a set of values associated with it, each with its own option. |
36 | |
37 | The general purpose options (and the associated types) are: |
38 | |
39 | \value DiagnosticRegister The diagnostic register of the server. \c quint16 |
40 | \value ExceptionStatusOffset The exception status byte offset of the server. \c quint16 |
41 | \value DeviceBusy Flag to signal the server is engaged in processing a |
42 | long-duration program command. \c quint16 |
43 | \value AsciiInputDelimiter The Modbus ASCII end of message delimiter. \c char |
44 | \value ListenOnlyMode Flag to set listen only mode of the server. |
45 | This function is typically supported only |
46 | by Modbus serial devices. \c bool |
47 | \value ServerIdentifier The identifier of the server, \b not the server address. \c quint8 |
48 | \value RunIndicatorStatus The run indicator of the server. \c quint8 |
49 | \value AdditionalData The additional data of the server. \c QByteArray |
50 | \value DeviceIdentification The physical and functional description of the server. \c QModbusDeviceIdentification |
51 | |
52 | User options: |
53 | |
54 | \value UserOption The first option that can be used for user-specific purposes. |
55 | |
56 | For user options, it is up to the developer to decide which types to use and ensure that |
57 | components use the correct types when accessing and setting values. |
58 | */ |
59 | |
60 | /*! |
61 | Constructs a Modbus server with the specified \a parent. |
62 | */ |
63 | QModbusServer::QModbusServer(QObject *parent) : |
64 | QModbusDevice(*new QModbusServerPrivate, parent) |
65 | { |
66 | } |
67 | |
68 | /*! |
69 | \internal |
70 | */ |
71 | QModbusServer::~QModbusServer() |
72 | { |
73 | } |
74 | |
75 | /*! |
76 | \internal |
77 | */ |
78 | QModbusServer::QModbusServer(QModbusServerPrivate &dd, QObject *parent) : |
79 | QModbusDevice(dd, parent) |
80 | { |
81 | } |
82 | |
83 | /*! |
84 | Sets the registered map structure for requests from other ModBus clients to \a map. |
85 | The register values are initialized with zero. Returns \c true on success; otherwise \c false. |
86 | |
87 | If this function is not called before connecting, a default register with zero |
88 | entries is setup. |
89 | |
90 | \note Calling this function discards any register value that was previously set. |
91 | */ |
92 | bool QModbusServer::setMap(const QModbusDataUnitMap &map) |
93 | { |
94 | return d_func()->setMap(map); |
95 | } |
96 | |
97 | /*! |
98 | Sets the address for this Modbus server instance to \a serverAddress. |
99 | |
100 | \sa serverAddress() |
101 | */ |
102 | void QModbusServer::setServerAddress(int serverAddress) |
103 | { |
104 | Q_D(QModbusServer); |
105 | d->m_serverAddress = serverAddress; |
106 | } |
107 | |
108 | /*! |
109 | Returns the address of this Modbus server instance. |
110 | |
111 | \sa setServerAddress() |
112 | */ |
113 | int QModbusServer::serverAddress() const |
114 | { |
115 | Q_D(const QModbusServer); |
116 | |
117 | return d->m_serverAddress; |
118 | } |
119 | |
120 | /*! |
121 | Returns the value for \a option or an invalid \c QVariant if the option is |
122 | not set. |
123 | |
124 | \table |
125 | \header |
126 | \li Option |
127 | \li Description |
128 | \row |
129 | \li \l QModbusServer::DiagnosticRegister |
130 | \li Returns the diagnostic register value of the server. The |
131 | diagnostic register contains device specific contents where |
132 | each bit has a specific meaning. |
133 | \row |
134 | \li \l QModbusServer::ExceptionStatusOffset |
135 | \li Returns the offset address of the exception status byte |
136 | location in the coils register. |
137 | \row |
138 | \li \l QModbusServer::DeviceBusy |
139 | \li Returns a flag that signals if the server is engaged in |
140 | processing a long-duration program command. |
141 | \row |
142 | \li \l QModbusServer::AsciiInputDelimiter |
143 | \li Returns a end of message delimiter of Modbus ASCII messages. |
144 | \row |
145 | \li \l QModbusServer::ListenOnlyMode |
146 | \li Returns the server's listen only state. Messages are monitored |
147 | but no response will be sent. |
148 | \row |
149 | \li \l QModbusServer::ServerIdentifier |
150 | \li Returns the server manufacturer's identifier code. This can be |
151 | an arbitrary value in the range of \c 0x00 to 0xff. |
152 | \row |
153 | \li \l QModbusServer::RunIndicatorStatus |
154 | \li Returns the server's run indicator status. This data is used as |
155 | addendum by the \l QModbusPdu::ReportServerId function code. |
156 | \row |
157 | \li \l QModbusServer::AdditionalData |
158 | \li Returns the server's additional data. This data is used as |
159 | addendum by the \l QModbusPdu::ReportServerId function code. |
160 | \row |
161 | \li \l QModbusServer::DeviceIdentification |
162 | \li Returns the server's physical and functional description. |
163 | \row |
164 | \li \l QModbusServer::UserOption |
165 | \li Returns the value of a user option. |
166 | |
167 | \note For user options, it is up to the developer to decide |
168 | which types to use and ensure that components use the correct |
169 | types when accessing and setting values. |
170 | \endtable |
171 | */ |
172 | QVariant QModbusServer::value(int option) const |
173 | { |
174 | Q_D(const QModbusServer); |
175 | |
176 | switch (option) { |
177 | case DiagnosticRegister: |
178 | return d->m_serverOptions.value(key: option, defaultValue: quint16(0x0000)); |
179 | case ExceptionStatusOffset: |
180 | return d->m_serverOptions.value(key: option, defaultValue: quint16(0x0000)); |
181 | case DeviceBusy: |
182 | return d->m_serverOptions.value(key: option, defaultValue: quint16(0x0000)); |
183 | case AsciiInputDelimiter: |
184 | return d->m_serverOptions.value(key: option, defaultValue: '\n'); |
185 | case ListenOnlyMode: |
186 | return d->m_serverOptions.value(key: option, defaultValue: false); |
187 | case ServerIdentifier: |
188 | return d->m_serverOptions.value(key: option, defaultValue: quint8(0x0a)); |
189 | case RunIndicatorStatus: |
190 | return d->m_serverOptions.value(key: option, defaultValue: quint8(0xff)); |
191 | case AdditionalData: |
192 | return d->m_serverOptions.value(key: option, defaultValue: QByteArray("Qt Modbus Server" )); |
193 | case DeviceIdentification: |
194 | return d->m_serverOptions.value(key: option, defaultValue: QVariant()); |
195 | }; |
196 | |
197 | if (option < UserOption) |
198 | return QVariant(); |
199 | |
200 | return d->m_serverOptions.value(key: option, defaultValue: QVariant()); |
201 | } |
202 | |
203 | /*! |
204 | Sets the \a newValue for \a option and returns \c true on success; \c false |
205 | otherwise. |
206 | |
207 | \note If the option's associated type is \c quint8 or \c quint16 and the |
208 | type of \a newValue is larger, the data will be truncated or conversation |
209 | will fail. |
210 | |
211 | \table |
212 | \header |
213 | \li Key |
214 | \li Description |
215 | \row |
216 | \li \l QModbusServer::DiagnosticRegister |
217 | \li Sets the diagnostic register of the server in a device specific |
218 | encoding to \a newValue. The default value preset is \c 0x0000. |
219 | The bit values of the register need device specific documentation. |
220 | \row |
221 | \li \l QModbusServer::ExceptionStatusOffset |
222 | \li Sets the exception status byte offset of the server to |
223 | \a newValue which is the absolute offset address in the coils |
224 | (0x register). Modbus register table starting with \c 0x0000h. |
225 | The default value preset is \c 0x0000, using the exception |
226 | status coils similar to Modicon 984 CPUs (coils 1-8). |
227 | |
228 | The function returns \c true if the coils register contains the |
229 | 8 bits required for storing and retrieving the status coils, |
230 | otherwise \c false. |
231 | \row |
232 | \li \l QModbusServer::DeviceBusy |
233 | \li Sets a flag that signals that the server is engaged in |
234 | processing a long-duration program command. Valid values are |
235 | \c 0x0000 (not busy) and \c 0xffff (busy). |
236 | The default value preset is \c 0x0000. |
237 | \row |
238 | \li \l QModbusServer::AsciiInputDelimiter |
239 | \li The \a newValue becomes the end of message delimiter for future |
240 | Modbus ASCII messages. The default value preset is \c {\n}. |
241 | \row |
242 | \li \l QModbusServer::ListenOnlyMode |
243 | \li Ss the server's listen only state to \a newValue. If listen only |
244 | mode is set to \c true, messages are monitored but no response |
245 | will be sent. The default value preset is \c false. |
246 | \row |
247 | \li \l QModbusServer::ServerIdentifier |
248 | \li Sets the server's manufacturer identifier to \a newValue. |
249 | Possible values are in the range of \c 0x00 to 0xff. |
250 | The default value preset is \c 0x0a. |
251 | \row |
252 | \li \l QModbusServer::RunIndicatorStatus |
253 | \li Sets the servers' run indicator status to \a newValue. This |
254 | data is used as addendum by the \l QModbusPdu::ReportServerId |
255 | function code. Valid values are \c 0x00 (OFF) and \c 0xff (ON). |
256 | The default value preset is \c 0xff (ON). |
257 | \row |
258 | \li \l QModbusServer::AdditionalData |
259 | \li Sets the server's additional data to \a newValue. This data is |
260 | used as addendum by the \l QModbusPdu::ReportServerId function |
261 | code. The maximum data size cannot exceed 249 bytes to match |
262 | response message size restrictions. |
263 | The default value preset is \c {Qt Modbus Server}. |
264 | \row |
265 | \li \l QModbusServer::DeviceIdentification |
266 | \li Sets the server's physical and functional description. By default |
267 | there is no additional device identification data set. |
268 | \row |
269 | \li \l QModbusServer::UserOption |
270 | \li Sets the value of a user option to \a newValue. |
271 | |
272 | \note For user options, it is up to the developer to decide |
273 | which types to use and ensure that components use the correct |
274 | types when accessing and setting values. |
275 | \endtable |
276 | */ |
277 | bool QModbusServer::setValue(int option, const QVariant &newValue) |
278 | { |
279 | #define CHECK_INT_OR_UINT(val) \ |
280 | do { \ |
281 | if ((val.typeId() != QMetaType::Type::Int) && (val.typeId() != QMetaType::Type::UInt)) \ |
282 | return false; \ |
283 | } while (0) |
284 | |
285 | Q_D(QModbusServer); |
286 | switch (option) { |
287 | case DiagnosticRegister: |
288 | CHECK_INT_OR_UINT(newValue); |
289 | d->m_serverOptions.insert(key: option, value: newValue); |
290 | return true; |
291 | case ExceptionStatusOffset: { |
292 | CHECK_INT_OR_UINT(newValue); |
293 | const quint16 tmp = newValue.value<quint16>(); |
294 | QModbusDataUnit coils(QModbusDataUnit::Coils, tmp, 8); |
295 | if (!data(newData: &coils)) |
296 | return false; |
297 | d->m_serverOptions.insert(key: option, value: tmp); |
298 | return true; |
299 | } |
300 | case DeviceBusy: { |
301 | CHECK_INT_OR_UINT(newValue); |
302 | const quint16 tmp = newValue.value<quint16>(); |
303 | if ((tmp != 0x0000) && (tmp != 0xffff)) |
304 | return false; |
305 | d->m_serverOptions.insert(key: option, value: tmp); |
306 | return true; |
307 | } |
308 | case AsciiInputDelimiter: { |
309 | CHECK_INT_OR_UINT(newValue); |
310 | bool ok = false; |
311 | if (newValue.toUInt(ok: &ok) > 0xff || !ok) |
312 | return false; |
313 | d->m_serverOptions.insert(key: option, value: newValue); |
314 | return true; |
315 | } |
316 | case ListenOnlyMode: { |
317 | if (newValue.typeId() != QMetaType::Type::Bool) |
318 | return false; |
319 | d->m_serverOptions.insert(key: option, value: newValue); |
320 | return true; |
321 | } |
322 | case ServerIdentifier: |
323 | CHECK_INT_OR_UINT(newValue); |
324 | d->m_serverOptions.insert(key: option, value: newValue); |
325 | return true; |
326 | case RunIndicatorStatus: { |
327 | CHECK_INT_OR_UINT(newValue); |
328 | const quint8 tmp = newValue.value<quint8>(); |
329 | if ((tmp != 0x00) && (tmp != 0xff)) |
330 | return false; |
331 | d->m_serverOptions.insert(key: option, value: tmp); |
332 | return true; |
333 | } |
334 | case AdditionalData: { |
335 | if (newValue.typeId() != QMetaType::Type::QByteArray) |
336 | return false; |
337 | const QByteArray additionalData = newValue.toByteArray(); |
338 | if (additionalData.size() > 249) |
339 | return false; |
340 | d->m_serverOptions.insert(key: option, value: additionalData); |
341 | return true; |
342 | } |
343 | case DeviceIdentification: |
344 | if (!newValue.canConvert<QModbusDeviceIdentification>()) |
345 | return false; |
346 | d->m_serverOptions.insert(key: option, value: newValue); |
347 | return true; |
348 | default: |
349 | break; |
350 | }; |
351 | |
352 | if (option < UserOption) |
353 | return false; |
354 | d->m_serverOptions.insert(key: option, value: newValue); |
355 | return true; |
356 | |
357 | #undef CHECK_INT_OR_UINT |
358 | } |
359 | |
360 | /*! |
361 | \fn bool QModbusServer::processesBroadcast() const |
362 | |
363 | Subclasses should implement this function if the transport layer shall handle broadcasts. |
364 | The implementation then should return \c true if the currently processed request is a |
365 | broadcast request; otherwise \c false. The default implementation returns always \c false. |
366 | |
367 | \note The return value of this function only makes sense from within processRequest() or |
368 | processPrivateRequest(), otherwise it can only tell that the last request processed |
369 | was a broadcast request. |
370 | */ |
371 | |
372 | /*! |
373 | Reads data stored in the Modbus server. A Modbus server has four tables (\a table) and each |
374 | have a unique \a address field, which is used to read \a data from the desired field. |
375 | See QModbusDataUnit::RegisterType for more information about the different tables. |
376 | Returns \c false if address is outside of the map range or the register type is not even defined. |
377 | |
378 | \sa QModbusDataUnit::RegisterType, setData() |
379 | */ |
380 | bool QModbusServer::data(QModbusDataUnit::RegisterType table, quint16 address, quint16 *data) const |
381 | { |
382 | QModbusDataUnit unit(table, address, 1u); |
383 | if (data && readData(newData: &unit)) { |
384 | *data = unit.value(index: 0); |
385 | return true; |
386 | } |
387 | return false; |
388 | } |
389 | |
390 | /*! |
391 | Returns the values in the register range given by \a newData. |
392 | |
393 | \a newData must provide a valid register type, start address |
394 | and valueCount. The returned \a newData will contain the register values |
395 | associated with the given range. |
396 | |
397 | If \a newData contains a valid register type but a negative start address |
398 | the entire register map is returned and \a newData appropriately sized. |
399 | */ |
400 | bool QModbusServer::data(QModbusDataUnit *newData) const |
401 | { |
402 | return readData(newData); |
403 | } |
404 | |
405 | /*! |
406 | Writes data to the Modbus server. A Modbus server has four tables (\a table) and each have a |
407 | unique \a address field, which is used to write \a data to the desired field. |
408 | Returns \c false if address outside of the map range. |
409 | |
410 | If the call was successful the \l dataWritten() signal is emitted. Note that |
411 | the signal is not emitted when \a data has not changed. Nevertheless this function |
412 | returns \c true in such cases. |
413 | |
414 | \sa QModbusDataUnit::RegisterType, data(), dataWritten() |
415 | */ |
416 | bool QModbusServer::setData(QModbusDataUnit::RegisterType table, quint16 address, quint16 data) |
417 | { |
418 | return writeData(unit: QModbusDataUnit(table, address, QList<quint16> { data })); |
419 | } |
420 | |
421 | /*! |
422 | Writes \a newData to the Modbus server map. |
423 | Returns \c false if the \a newData range is outside of the map range. |
424 | |
425 | If the call was successful the \l dataWritten() signal is emitted. Note that |
426 | the signal is not emitted when the addressed register has not changed. This |
427 | may happen when \a newData contains exactly the same values as the |
428 | register already. Nevertheless this function returns \c true in such cases. |
429 | |
430 | \sa data() |
431 | */ |
432 | bool QModbusServer::setData(const QModbusDataUnit &newData) |
433 | { |
434 | return writeData(unit: newData); |
435 | } |
436 | |
437 | /*! |
438 | Writes \a newData to the Modbus server map. Returns \c true on success, |
439 | or \c false if the \a newData range is outside of the map range or the |
440 | registerType() does not exist. |
441 | |
442 | \note Sub-classes that implement writing to a different backing store |
443 | then default one, also need to implement setMap() and readData(). The |
444 | dataWritten() signal needs to be emitted from within the functions |
445 | implementation as well. |
446 | |
447 | \sa setMap(), readData(), dataWritten() |
448 | */ |
449 | bool QModbusServer::writeData(const QModbusDataUnit &newData) |
450 | { |
451 | Q_D(QModbusServer); |
452 | if (!d->m_modbusDataUnitMap.contains(key: newData.registerType())) |
453 | return false; |
454 | |
455 | QModbusDataUnit ¤t = d->m_modbusDataUnitMap[newData.registerType()]; |
456 | if (!current.isValid()) |
457 | return false; |
458 | |
459 | // check range start is within internal map range |
460 | int internalRangeEndAddress = current.startAddress() + current.valueCount() - 1; |
461 | if (newData.startAddress() < current.startAddress() |
462 | || newData.startAddress() > internalRangeEndAddress) { |
463 | return false; |
464 | } |
465 | |
466 | // check range end is within internal map range |
467 | int rangeEndAddress = newData.startAddress() + newData.valueCount() - 1; |
468 | if (rangeEndAddress < current.startAddress() || rangeEndAddress > internalRangeEndAddress) |
469 | return false; |
470 | |
471 | bool changeRequired = false; |
472 | for (qsizetype i = 0; i < newData.valueCount(); i++) { |
473 | const quint16 newValue = newData.value(index: i); |
474 | const qsizetype translatedIndex = newData.startAddress() - current.startAddress() + i; |
475 | changeRequired |= (current.value(index: translatedIndex) != newValue); |
476 | current.setValue(index: translatedIndex, newValue); |
477 | } |
478 | |
479 | if (changeRequired) |
480 | emit dataWritten(table: newData.registerType(), address: newData.startAddress(), size: newData.valueCount()); |
481 | return true; |
482 | } |
483 | |
484 | /*! |
485 | Reads the values in the register range given by \a newData and writes the |
486 | data back to \a newData. Returns \c true on success or \c false if |
487 | \a newData is \c 0, the \a newData range is outside of the map range or the |
488 | registerType() does not exist. |
489 | |
490 | \note Sub-classes that implement reading from a different backing store |
491 | then default one, also need to implement setMap() and writeData(). |
492 | |
493 | \sa setMap(), writeData() |
494 | */ |
495 | bool QModbusServer::readData(QModbusDataUnit *newData) const |
496 | { |
497 | Q_D(const QModbusServer); |
498 | |
499 | if ((!newData) || (!d->m_modbusDataUnitMap.contains(key: newData->registerType()))) |
500 | return false; |
501 | |
502 | const QModbusDataUnit ¤t = d->m_modbusDataUnitMap.value(key: newData->registerType()); |
503 | if (!current.isValid()) |
504 | return false; |
505 | |
506 | // return entire map for given type |
507 | if (newData->startAddress() < 0) { |
508 | *newData = current; |
509 | return true; |
510 | } |
511 | |
512 | // check range start is within internal map range |
513 | int internalRangeEndAddress = current.startAddress() + current.valueCount() - 1; |
514 | if (newData->startAddress() < current.startAddress() |
515 | || newData->startAddress() > internalRangeEndAddress) { |
516 | return false; |
517 | } |
518 | |
519 | // check range end is within internal map range |
520 | const int rangeEndAddress = newData->startAddress() + newData->valueCount() - 1; |
521 | if (rangeEndAddress < current.startAddress() || rangeEndAddress > internalRangeEndAddress) |
522 | return false; |
523 | |
524 | newData->setValues(current.values().mid(pos: newData->startAddress() - current.startAddress(), len: newData->valueCount())); |
525 | return true; |
526 | } |
527 | |
528 | /*! |
529 | \fn void QModbusServer::dataWritten(QModbusDataUnit::RegisterType table, int address, int size) |
530 | |
531 | This signal is emitted when a Modbus client has written one or more fields of data to the |
532 | Modbus server. The signal contains information about the fields that were written: |
533 | \list |
534 | \li Register type (\a table) that was written, |
535 | \li \a address of the first field that was written, |
536 | \li and \a size of consecutive fields that were written starting from \a address. |
537 | \endlist |
538 | |
539 | The signal is not emitted when the to-be-written fields have not changed |
540 | due to no change in value. |
541 | */ |
542 | |
543 | /*! |
544 | Processes a Modbus client \a request and returns a Modbus response. |
545 | This function returns a \l QModbusResponse or \l QModbusExceptionResponse depending |
546 | on the nature of the request. |
547 | |
548 | The default implementation of this function handles all standard Modbus |
549 | function codes as defined by the Modbus Application Protocol Specification 1.1b. |
550 | All other Modbus function codes not included in the specification are forwarded to |
551 | \l processPrivateRequest(). |
552 | |
553 | The default handling of the standard Modbus function code requests can be overwritten |
554 | by reimplementing this function. The override must handle the request type |
555 | in question and return the appropriate \l QModbusResponse. A common reason might be to |
556 | filter out function code requests for data values to limit read/write access and |
557 | function codes not desired in particular implementations such as serial line diagnostics |
558 | on ethernet or Modbus Plus transport layers. Every other request type should be |
559 | forwarded to this default implementation. |
560 | |
561 | \note This function should not be overridden to provide a custom implementation for |
562 | non-standard Modbus request types. |
563 | |
564 | \sa processPrivateRequest() |
565 | */ |
566 | QModbusResponse QModbusServer::processRequest(const QModbusPdu &request) |
567 | { |
568 | return d_func()->processRequest(request); |
569 | } |
570 | |
571 | /*! |
572 | This function should be implemented by custom Modbus servers. It is |
573 | called by \l processRequest() if the given \a request is not a standard |
574 | Modbus request. |
575 | |
576 | Overwriting this function allows handling of additional function codes and |
577 | subfunction-codes not specified in the Modbus Application Protocol |
578 | Specification 1.1b. Reimplementations should call this function again to |
579 | ensure an exception response is returned for all unknown function codes the |
580 | custom Modbus implementation does not handle. |
581 | |
582 | This default implementation returns a \c QModbusExceptionResponse with the |
583 | \a request function code and error code set to illegal function. |
584 | |
585 | \sa processRequest() |
586 | */ |
587 | QModbusResponse QModbusServer::processPrivateRequest(const QModbusPdu &request) |
588 | { |
589 | return QModbusExceptionResponse(request.functionCode(), |
590 | QModbusExceptionResponse::IllegalFunction); |
591 | } |
592 | |
593 | // -- QModbusServerPrivate |
594 | |
595 | bool QModbusServerPrivate::setMap(const QModbusDataUnitMap &map) |
596 | { |
597 | m_modbusDataUnitMap = map; |
598 | return true; |
599 | } |
600 | |
601 | QModbusResponse QModbusServerPrivate::processRequest(const QModbusPdu &request) |
602 | { |
603 | switch (request.functionCode()) { |
604 | case QModbusRequest::ReadCoils: |
605 | return processReadCoilsRequest(request); |
606 | case QModbusRequest::ReadDiscreteInputs: |
607 | return processReadDiscreteInputsRequest(request); |
608 | case QModbusRequest::ReadHoldingRegisters: |
609 | return processReadHoldingRegistersRequest(request); |
610 | case QModbusRequest::ReadInputRegisters: |
611 | return processReadInputRegistersRequest(request); |
612 | case QModbusRequest::WriteSingleCoil: |
613 | return processWriteSingleCoilRequest(request); |
614 | case QModbusRequest::WriteSingleRegister: |
615 | return processWriteSingleRegisterRequest(request); |
616 | case QModbusRequest::ReadExceptionStatus: |
617 | return processReadExceptionStatusRequest(request); |
618 | case QModbusRequest::Diagnostics: |
619 | return processDiagnosticsRequest(request); |
620 | case QModbusRequest::GetCommEventCounter: |
621 | return processGetCommEventCounterRequest(request); |
622 | case QModbusRequest::GetCommEventLog: |
623 | return processGetCommEventLogRequest(request); |
624 | case QModbusRequest::WriteMultipleCoils: |
625 | return processWriteMultipleCoilsRequest(request); |
626 | case QModbusRequest::WriteMultipleRegisters: |
627 | return processWriteMultipleRegistersRequest(request); |
628 | case QModbusRequest::ReportServerId: |
629 | return processReportServerIdRequest(request); |
630 | case QModbusRequest::ReadFileRecord: // TODO: Implement. |
631 | case QModbusRequest::WriteFileRecord: // TODO: Implement. |
632 | return QModbusExceptionResponse(request.functionCode(), |
633 | QModbusExceptionResponse::IllegalFunction); |
634 | case QModbusRequest::MaskWriteRegister: |
635 | return processMaskWriteRegisterRequest(request); |
636 | case QModbusRequest::ReadWriteMultipleRegisters: |
637 | return processReadWriteMultipleRegistersRequest(request); |
638 | case QModbusRequest::ReadFifoQueue: |
639 | return processReadFifoQueueRequest(request); |
640 | case QModbusRequest::EncapsulatedInterfaceTransport: |
641 | return processEncapsulatedInterfaceTransportRequest(request); |
642 | default: |
643 | break; |
644 | } |
645 | return q_func()->processPrivateRequest(request); |
646 | } |
647 | |
648 | #define CHECK_SIZE_EQUALS(req) \ |
649 | do { \ |
650 | if (req.dataSize() != QModbusRequest::minimumDataSize(req)) { \ |
651 | qCDebug(QT_MODBUS) << "(Server) The request's data size does not equal the expected size."; \ |
652 | return QModbusExceptionResponse(req.functionCode(), \ |
653 | QModbusExceptionResponse::IllegalDataValue); \ |
654 | } \ |
655 | } while (0) |
656 | |
657 | #define CHECK_SIZE_LESS_THAN(req) \ |
658 | do { \ |
659 | if (req.dataSize() < QModbusRequest::minimumDataSize(req)) { \ |
660 | qCDebug(QT_MODBUS) << "(Server) The request's data size is less than the expected size."; \ |
661 | return QModbusExceptionResponse(req.functionCode(), \ |
662 | QModbusExceptionResponse::IllegalDataValue); \ |
663 | } \ |
664 | } while (0) |
665 | |
666 | QModbusResponse QModbusServerPrivate::processReadCoilsRequest(const QModbusRequest &request) |
667 | { |
668 | return readBits(request, unitType: QModbusDataUnit::Coils); |
669 | } |
670 | |
671 | QModbusResponse QModbusServerPrivate::processReadDiscreteInputsRequest(const QModbusRequest &rqst) |
672 | { |
673 | return readBits(request: rqst, unitType: QModbusDataUnit::DiscreteInputs); |
674 | } |
675 | |
676 | QModbusResponse QModbusServerPrivate::readBits(const QModbusPdu &request, |
677 | QModbusDataUnit::RegisterType unitType) |
678 | { |
679 | CHECK_SIZE_EQUALS(request); |
680 | quint16 address, count; |
681 | request.decodeData(newData: &address, newData: &count); |
682 | |
683 | if ((count < 0x0001) || (count > 0x07D0)) { |
684 | return QModbusExceptionResponse(request.functionCode(), |
685 | QModbusExceptionResponse::IllegalDataValue); |
686 | } |
687 | |
688 | // Get the requested range out of the registers. |
689 | QModbusDataUnit unit(unitType, address, count); |
690 | if (!q_func()->data(newData: &unit)) { |
691 | return QModbusExceptionResponse(request.functionCode(), |
692 | QModbusExceptionResponse::IllegalDataAddress); |
693 | } |
694 | |
695 | quint8 byteCount = quint8(count / 8); |
696 | if ((count % 8) != 0) { |
697 | byteCount += 1; |
698 | // If the range is not a multiple of 8, resize. |
699 | unit.setValueCount(byteCount * 8); |
700 | } |
701 | |
702 | // Using byteCount * 8 so the remaining bits in the last byte are zero |
703 | QBitArray bytes(byteCount * 8); |
704 | |
705 | address = 0; // The data range now starts with zero. |
706 | for ( ; address < count; ++address) |
707 | bytes.setBit(i: address, val: unit.value(index: address)); |
708 | |
709 | QByteArray payload = QByteArray::fromRawData(data: bytes.bits(), size: byteCount); |
710 | payload.prepend(c: char(byteCount)); |
711 | return QModbusResponse(request.functionCode(), payload); |
712 | } |
713 | |
714 | QModbusResponse QModbusServerPrivate::processReadHoldingRegistersRequest(const QModbusRequest &rqst) |
715 | { |
716 | return readBytes(request: rqst, unitType: QModbusDataUnit::HoldingRegisters); |
717 | } |
718 | |
719 | QModbusResponse QModbusServerPrivate::processReadInputRegistersRequest(const QModbusRequest &rqst) |
720 | { |
721 | return readBytes(request: rqst, unitType: QModbusDataUnit::InputRegisters); |
722 | } |
723 | |
724 | QModbusResponse QModbusServerPrivate::readBytes(const QModbusPdu &request, |
725 | QModbusDataUnit::RegisterType unitType) |
726 | { |
727 | CHECK_SIZE_EQUALS(request); |
728 | quint16 address, count; |
729 | request.decodeData(newData: &address, newData: &count); |
730 | |
731 | if ((count < 0x0001) || (count > 0x007D)) { |
732 | return QModbusExceptionResponse(request.functionCode(), |
733 | QModbusExceptionResponse::IllegalDataValue); |
734 | } |
735 | |
736 | // Get the requested range out of the registers. |
737 | QModbusDataUnit unit(unitType, address, count); |
738 | if (!q_func()->data(newData: &unit)) { |
739 | return QModbusExceptionResponse(request.functionCode(), |
740 | QModbusExceptionResponse::IllegalDataAddress); |
741 | } |
742 | |
743 | return QModbusResponse(request.functionCode(), quint8(count * 2), unit.values()); |
744 | } |
745 | |
746 | QModbusResponse QModbusServerPrivate::processWriteSingleCoilRequest(const QModbusRequest &request) |
747 | { |
748 | return writeSingle(request, unitType: QModbusDataUnit::Coils); |
749 | } |
750 | |
751 | QModbusResponse QModbusServerPrivate::processWriteSingleRegisterRequest(const QModbusRequest &rqst) |
752 | { |
753 | return writeSingle(request: rqst, unitType: QModbusDataUnit::HoldingRegisters); |
754 | } |
755 | |
756 | QModbusResponse QModbusServerPrivate::writeSingle(const QModbusPdu &request, |
757 | QModbusDataUnit::RegisterType unitType) |
758 | { |
759 | CHECK_SIZE_EQUALS(request); |
760 | quint16 address, value; |
761 | request.decodeData(newData: &address, newData: &value); |
762 | |
763 | if ((unitType == QModbusDataUnit::Coils) && ((value != Coil::Off) && (value != Coil::On))) { |
764 | return QModbusExceptionResponse(request.functionCode(), |
765 | QModbusExceptionResponse::IllegalDataValue); |
766 | } |
767 | |
768 | quint16 reg; // Get the requested register, but deliberately ignore. |
769 | if (!q_func()->data(table: unitType, address, data: ®)) { |
770 | return QModbusExceptionResponse(request.functionCode(), |
771 | QModbusExceptionResponse::IllegalDataAddress); |
772 | } |
773 | |
774 | if (!q_func()->setData(table: unitType, address, data: value)) { |
775 | return QModbusExceptionResponse(request.functionCode(), |
776 | QModbusExceptionResponse::ServerDeviceFailure); |
777 | } |
778 | |
779 | return QModbusResponse(request.functionCode(), address, value); |
780 | } |
781 | |
782 | QModbusResponse QModbusServerPrivate::processReadExceptionStatusRequest(const QModbusRequest &request) |
783 | { |
784 | CHECK_SIZE_EQUALS(request); |
785 | |
786 | // Get the requested range out of the registers. |
787 | const QVariant tmp = q_func()->value(option: QModbusServer::ExceptionStatusOffset); |
788 | if (tmp.isNull() || (!tmp.isValid())) { |
789 | return QModbusExceptionResponse(request.functionCode(), |
790 | QModbusExceptionResponse::ServerDeviceFailure); |
791 | } |
792 | const quint16 exceptionStatusOffset = tmp.value<quint16>(); |
793 | QModbusDataUnit coils(QModbusDataUnit::Coils, exceptionStatusOffset, 8); |
794 | if (!q_func()->data(newData: &coils)) { |
795 | return QModbusExceptionResponse(request.functionCode(), |
796 | QModbusExceptionResponse::IllegalDataAddress); |
797 | } |
798 | |
799 | qsizetype address = 0; |
800 | quint8 byte = 0; |
801 | for (int currentBit = 0; currentBit < 8; ++currentBit) |
802 | if (coils.value(index: address++)) // The padding happens inside value(). |
803 | byte |= (1U << currentBit); |
804 | |
805 | return QModbusResponse(request.functionCode(), byte); |
806 | } |
807 | |
808 | QModbusResponse QModbusServerPrivate::processDiagnosticsRequest(const QModbusRequest &request) |
809 | { |
810 | #define CHECK_SIZE_AND_CONDITION(req, condition) \ |
811 | CHECK_SIZE_EQUALS(req); \ |
812 | do { \ |
813 | if ((condition)) { \ |
814 | return QModbusExceptionResponse(req.functionCode(), \ |
815 | QModbusExceptionResponse::IllegalDataValue); \ |
816 | } \ |
817 | } while (0) |
818 | |
819 | quint16 subFunctionCode, data = 0xffff; |
820 | request.decodeData(newData: &subFunctionCode, newData: &data); |
821 | |
822 | switch (subFunctionCode) { |
823 | case Diagnostics::ReturnQueryData: |
824 | return QModbusResponse(request.functionCode(), request.data()); |
825 | |
826 | case Diagnostics::RestartCommunicationsOption: { |
827 | CHECK_SIZE_AND_CONDITION(request, ((data != 0xff00) && (data != 0x0000))); |
828 | // Restarts the communication by closing the connection and re-opening. After closing, |
829 | // all communication counters are cleared and the listen only mode set to false. This |
830 | // function is the only way to remotely clear the listen only mode and bring the device |
831 | // back into communication. If data is 0xff00, the event log history is also cleared. |
832 | q_func()->disconnectDevice(); |
833 | if (data == 0xff00) |
834 | m_commEventLog.clear(); |
835 | |
836 | resetCommunicationCounters(); |
837 | q_func()->setValue(option: QModbusServer::ListenOnlyMode, newValue: false); |
838 | storeModbusCommEvent(eventByte: QModbusCommEvent::InitiatedCommunicationRestart); |
839 | |
840 | if (!q_func()->connectDevice()) { |
841 | qCWarning(QT_MODBUS) << "(Server) Cannot restart server communication" ; |
842 | return QModbusExceptionResponse(request.functionCode(), |
843 | QModbusExceptionResponse::ServerDeviceFailure); |
844 | } |
845 | return QModbusResponse(request.functionCode(), request.data()); |
846 | } break; |
847 | |
848 | case Diagnostics::ChangeAsciiInputDelimiter: { |
849 | const QByteArray data = request.data().mid(index: 2, len: 2); |
850 | CHECK_SIZE_AND_CONDITION(request, (data[1] != 0x00)); |
851 | q_func()->setValue(option: QModbusServer::AsciiInputDelimiter, newValue: data[0]); |
852 | return QModbusResponse(request.functionCode(), request.data()); |
853 | } break; |
854 | |
855 | case Diagnostics::ForceListenOnlyMode: |
856 | CHECK_SIZE_AND_CONDITION(request, (data != 0x0000)); |
857 | q_func()->setValue(option: QModbusServer::ListenOnlyMode, newValue: true); |
858 | storeModbusCommEvent(eventByte: QModbusCommEvent::EnteredListenOnlyMode); |
859 | return QModbusResponse(); |
860 | |
861 | case Diagnostics::ClearCountersAndDiagnosticRegister: |
862 | CHECK_SIZE_AND_CONDITION(request, (data != 0x0000)); |
863 | resetCommunicationCounters(); |
864 | q_func()->setValue(option: QModbusServer::DiagnosticRegister, newValue: 0x0000); |
865 | return QModbusResponse(request.functionCode(), request.data()); |
866 | |
867 | case Diagnostics::ReturnDiagnosticRegister: |
868 | case Diagnostics::ReturnBusMessageCount: |
869 | case Diagnostics::ReturnBusCommunicationErrorCount: |
870 | case Diagnostics::ReturnBusExceptionErrorCount: |
871 | case Diagnostics::ReturnServerMessageCount: |
872 | case Diagnostics::ReturnServerNoResponseCount: |
873 | case Diagnostics::ReturnServerNAKCount: |
874 | case Diagnostics::ReturnServerBusyCount: |
875 | case Diagnostics::ReturnBusCharacterOverrunCount: |
876 | CHECK_SIZE_AND_CONDITION(request, (data != 0x0000)); |
877 | return QModbusResponse(request.functionCode(), subFunctionCode, |
878 | m_counters[static_cast<Counter> (subFunctionCode)]); |
879 | |
880 | case Diagnostics::ClearOverrunCounterAndFlag: { |
881 | CHECK_SIZE_AND_CONDITION(request, (data != 0x0000)); |
882 | m_counters[Diagnostics::ReturnBusCharacterOverrunCount] = 0; |
883 | quint16 reg = q_func()->value(option: QModbusServer::DiagnosticRegister).value<quint16>(); |
884 | q_func()->setValue(option: QModbusServer::DiagnosticRegister, newValue: reg &~ 1); // clear first bit |
885 | return QModbusResponse(request.functionCode(), request.data()); |
886 | } |
887 | } |
888 | return QModbusExceptionResponse(request.functionCode(), |
889 | QModbusExceptionResponse::IllegalFunction); |
890 | |
891 | #undef CHECK_SIZE_AND_CONDITION |
892 | } |
893 | |
894 | QModbusResponse QModbusServerPrivate::processGetCommEventCounterRequest(const QModbusRequest &request) |
895 | { |
896 | CHECK_SIZE_EQUALS(request); |
897 | const QVariant tmp = q_func()->value(option: QModbusServer::DeviceBusy); |
898 | if (tmp.isNull() || (!tmp.isValid())) { |
899 | return QModbusExceptionResponse(request.functionCode(), |
900 | QModbusExceptionResponse::ServerDeviceFailure); |
901 | } |
902 | const quint16 deviceBusy = tmp.value<quint16>(); |
903 | return QModbusResponse(request.functionCode(), deviceBusy, m_counters[Counter::CommEvent]); |
904 | } |
905 | |
906 | QModbusResponse QModbusServerPrivate::processGetCommEventLogRequest(const QModbusRequest &request) |
907 | { |
908 | CHECK_SIZE_EQUALS(request); |
909 | const QVariant tmp = q_func()->value(option: QModbusServer::DeviceBusy); |
910 | if (tmp.isNull() || (!tmp.isValid())) { |
911 | return QModbusExceptionResponse(request.functionCode(), |
912 | QModbusExceptionResponse::ServerDeviceFailure); |
913 | } |
914 | const quint16 deviceBusy = tmp.value<quint16>(); |
915 | |
916 | QList<quint8> eventLog(int(m_commEventLog.size())); |
917 | std::copy(first: m_commEventLog.cbegin(), last: m_commEventLog.cend(), result: eventLog.begin()); |
918 | |
919 | // 6 -> 3 x 2 Bytes (Status, Event Count and Message Count) |
920 | return QModbusResponse(request.functionCode(), quint8(eventLog.size() + 6), deviceBusy, |
921 | m_counters[Counter::CommEvent], m_counters[Counter::BusMessage], eventLog); |
922 | } |
923 | |
924 | QModbusResponse QModbusServerPrivate::processWriteMultipleCoilsRequest(const QModbusRequest &request) |
925 | { |
926 | CHECK_SIZE_LESS_THAN(request); |
927 | quint16 address, numberOfCoils; |
928 | quint8 byteCount; |
929 | request.decodeData(newData: &address, newData: &numberOfCoils, newData: &byteCount); |
930 | |
931 | // byte count does not match number of data bytes following |
932 | if (byteCount != (request.dataSize() - 5 )) { |
933 | return QModbusExceptionResponse(request.functionCode(), |
934 | QModbusExceptionResponse::IllegalDataValue); |
935 | } |
936 | |
937 | quint8 expectedBytes = numberOfCoils / 8; |
938 | if ((numberOfCoils % 8) != 0) |
939 | expectedBytes += 1; |
940 | |
941 | if ((numberOfCoils < 0x0001) || (numberOfCoils > 0x07B0) || (expectedBytes != byteCount)) { |
942 | return QModbusExceptionResponse(request.functionCode(), |
943 | QModbusExceptionResponse::IllegalDataValue); |
944 | } |
945 | |
946 | // Get the requested range out of the registers. |
947 | QModbusDataUnit coils(QModbusDataUnit::Coils, address, numberOfCoils); |
948 | if (!q_func()->data(newData: &coils)) { |
949 | return QModbusExceptionResponse(request.functionCode(), |
950 | QModbusExceptionResponse::IllegalDataAddress); |
951 | } |
952 | |
953 | QList<quint8> bytes; |
954 | const QByteArray payload = request.data().mid(index: 5); |
955 | for (qint32 i = payload.size() - 1; i >= 0; --i) |
956 | bytes.append(t: quint8(payload[i])); |
957 | |
958 | // Since we picked the coils at start address, data |
959 | // range is numberOfCoils and therefore index too. |
960 | quint16 coil = numberOfCoils; |
961 | qint32 currentBit = 8 - ((byteCount * 8) - numberOfCoils); |
962 | for (quint8 currentByte : std::as_const(t&: bytes)) { |
963 | for (currentBit -= 1; currentBit >= 0; --currentBit) |
964 | coils.setValue(index: --coil, newValue: currentByte & (1U << currentBit) ? 1 : 0); |
965 | currentBit = 8; |
966 | } |
967 | |
968 | if (!q_func()->setData(coils)) { |
969 | return QModbusExceptionResponse(request.functionCode(), |
970 | QModbusExceptionResponse::ServerDeviceFailure); |
971 | } |
972 | |
973 | return QModbusResponse(request.functionCode(), address, numberOfCoils); |
974 | } |
975 | |
976 | QModbusResponse QModbusServerPrivate::processWriteMultipleRegistersRequest( |
977 | const QModbusRequest &request) |
978 | { |
979 | CHECK_SIZE_LESS_THAN(request); |
980 | quint16 address, numberOfRegisters; |
981 | quint8 byteCount; |
982 | request.decodeData(newData: &address, newData: &numberOfRegisters, newData: &byteCount); |
983 | |
984 | // byte count does not match number of data bytes following or register count |
985 | if ((byteCount != (request.dataSize() - 5 )) || (byteCount != (numberOfRegisters * 2))) { |
986 | return QModbusExceptionResponse(request.functionCode(), |
987 | QModbusExceptionResponse::IllegalDataValue); |
988 | } |
989 | |
990 | if ((numberOfRegisters < 0x0001) || (numberOfRegisters > 0x007B)) { |
991 | return QModbusExceptionResponse(request.functionCode(), |
992 | QModbusExceptionResponse::IllegalDataValue); |
993 | } |
994 | |
995 | // Get the requested range out of the registers. |
996 | QModbusDataUnit registers(QModbusDataUnit::HoldingRegisters, address, numberOfRegisters); |
997 | if (!q_func()->data(newData: ®isters)) { |
998 | return QModbusExceptionResponse(request.functionCode(), |
999 | QModbusExceptionResponse::IllegalDataAddress); |
1000 | } |
1001 | |
1002 | const QByteArray pduData = request.data().remove(index: 0,len: 5); |
1003 | QDataStream stream(pduData); |
1004 | |
1005 | QList<quint16> values; |
1006 | quint16 tmp; |
1007 | for (int i = 0; i < numberOfRegisters; i++) { |
1008 | stream >> tmp; |
1009 | values.append(t: tmp); |
1010 | } |
1011 | |
1012 | registers.setValues(values); |
1013 | |
1014 | if (!q_func()->setData(registers)) { |
1015 | return QModbusExceptionResponse(request.functionCode(), |
1016 | QModbusExceptionResponse::ServerDeviceFailure); |
1017 | } |
1018 | |
1019 | return QModbusResponse(request.functionCode(), address, numberOfRegisters); |
1020 | } |
1021 | |
1022 | QModbusResponse QModbusServerPrivate::processReportServerIdRequest(const QModbusRequest &request) |
1023 | { |
1024 | CHECK_SIZE_EQUALS(request); |
1025 | |
1026 | Q_Q(QModbusServer); |
1027 | |
1028 | QByteArray data; |
1029 | QVariant tmp = q->value(option: QModbusServer::ServerIdentifier); |
1030 | if (tmp.isNull() || (!tmp.isValid())) { |
1031 | return QModbusExceptionResponse(request.functionCode(), |
1032 | QModbusExceptionResponse::ServerDeviceFailure); |
1033 | } |
1034 | data.append(c: tmp.value<quint8>()); |
1035 | |
1036 | tmp = q->value(option: QModbusServer::RunIndicatorStatus); |
1037 | if (tmp.isNull() || (!tmp.isValid())) { |
1038 | return QModbusExceptionResponse(request.functionCode(), |
1039 | QModbusExceptionResponse::ServerDeviceFailure); |
1040 | } |
1041 | data.append(c: tmp.value<quint8>()); |
1042 | |
1043 | tmp = q->value(option: QModbusServer::AdditionalData); |
1044 | if (!tmp.isNull() && tmp.isValid()) |
1045 | data.append(a: tmp.toByteArray()); |
1046 | |
1047 | data.prepend(c: data.size()); // byte count |
1048 | return QModbusResponse(request.functionCode(), data); |
1049 | } |
1050 | |
1051 | QModbusResponse QModbusServerPrivate::processMaskWriteRegisterRequest(const QModbusRequest &request) |
1052 | { |
1053 | CHECK_SIZE_EQUALS(request); |
1054 | quint16 address, andMask, orMask; |
1055 | request.decodeData(newData: &address, newData: &andMask, newData: &orMask); |
1056 | |
1057 | quint16 reg; |
1058 | if (!q_func()->data(table: QModbusDataUnit::HoldingRegisters, address, data: ®)) { |
1059 | return QModbusExceptionResponse(request.functionCode(), |
1060 | QModbusExceptionResponse::IllegalDataAddress); |
1061 | } |
1062 | |
1063 | const quint16 result = (reg & andMask) | (orMask & (~ andMask)); |
1064 | if (!q_func()->setData(table: QModbusDataUnit::HoldingRegisters, address, data: result)) { |
1065 | return QModbusExceptionResponse(request.functionCode(), |
1066 | QModbusExceptionResponse::ServerDeviceFailure); |
1067 | } |
1068 | return QModbusResponse(request.functionCode(), request.data()); |
1069 | } |
1070 | |
1071 | QModbusResponse QModbusServerPrivate::processReadWriteMultipleRegistersRequest( |
1072 | const QModbusRequest &request) |
1073 | { |
1074 | CHECK_SIZE_LESS_THAN(request); |
1075 | quint16 readStartAddress, readQuantity, writeStartAddress, writeQuantity; |
1076 | quint8 byteCount; |
1077 | request.decodeData(newData: &readStartAddress, newData: &readQuantity, |
1078 | newData: &writeStartAddress, newData: &writeQuantity, newData: &byteCount); |
1079 | |
1080 | // byte count does not match number of data bytes following or register count |
1081 | if ((byteCount != (request.dataSize() - 9 )) || (byteCount != (writeQuantity * 2))) { |
1082 | return QModbusExceptionResponse(request.functionCode(), |
1083 | QModbusExceptionResponse::IllegalDataValue); |
1084 | } |
1085 | |
1086 | if ((readQuantity < 0x0001) || (readQuantity > 0x007B) |
1087 | || (writeQuantity < 0x0001) || (writeQuantity > 0x0079)) { |
1088 | return QModbusExceptionResponse(request.functionCode(), |
1089 | QModbusExceptionResponse::IllegalDataValue); |
1090 | } |
1091 | |
1092 | // According to spec, write operation is executed before the read operation |
1093 | // Get the requested range out of the registers. |
1094 | QModbusDataUnit writeRegisters(QModbusDataUnit::HoldingRegisters, writeStartAddress, |
1095 | writeQuantity); |
1096 | if (!q_func()->data(newData: &writeRegisters)) { |
1097 | return QModbusExceptionResponse(request.functionCode(), |
1098 | QModbusExceptionResponse::IllegalDataAddress); |
1099 | } |
1100 | |
1101 | const QByteArray pduData = request.data().remove(index: 0,len: 9); |
1102 | QDataStream stream(pduData); |
1103 | |
1104 | QList<quint16> values; |
1105 | quint16 tmp; |
1106 | for (int i = 0; i < writeQuantity; i++) { |
1107 | stream >> tmp; |
1108 | values.append(t: tmp); |
1109 | } |
1110 | |
1111 | writeRegisters.setValues(values); |
1112 | |
1113 | if (!q_func()->setData(writeRegisters)) { |
1114 | return QModbusExceptionResponse(request.functionCode(), |
1115 | QModbusExceptionResponse::ServerDeviceFailure); |
1116 | } |
1117 | |
1118 | // Get the requested range out of the registers. |
1119 | QModbusDataUnit readRegisters(QModbusDataUnit::HoldingRegisters, readStartAddress, |
1120 | readQuantity); |
1121 | if (!q_func()->data(newData: &readRegisters)) { |
1122 | return QModbusExceptionResponse(request.functionCode(), |
1123 | QModbusExceptionResponse::IllegalDataAddress); |
1124 | } |
1125 | |
1126 | return QModbusResponse(request.functionCode(), quint8(readQuantity * 2), |
1127 | readRegisters.values()); |
1128 | } |
1129 | |
1130 | QModbusResponse QModbusServerPrivate::processReadFifoQueueRequest(const QModbusRequest &request) |
1131 | { |
1132 | CHECK_SIZE_LESS_THAN(request); |
1133 | quint16 address; |
1134 | request.decodeData(newData: &address); |
1135 | |
1136 | quint16 fifoCount; |
1137 | if (!q_func()->data(table: QModbusDataUnit::HoldingRegisters, address, data: &fifoCount)) { |
1138 | return QModbusExceptionResponse(request.functionCode(), |
1139 | QModbusExceptionResponse::IllegalDataAddress); |
1140 | } |
1141 | |
1142 | if (fifoCount > 31u) { |
1143 | return QModbusExceptionResponse(request.functionCode(), |
1144 | QModbusExceptionResponse::IllegalDataValue); |
1145 | } |
1146 | |
1147 | QModbusDataUnit fifoRegisters(QModbusDataUnit::HoldingRegisters, address + 1u, fifoCount); |
1148 | if (!q_func()->data(newData: &fifoRegisters)) { |
1149 | return QModbusExceptionResponse(request.functionCode(), |
1150 | QModbusExceptionResponse::IllegalDataAddress); |
1151 | } |
1152 | |
1153 | return QModbusResponse(request.functionCode(), quint16((fifoCount * 2) + 2u), fifoCount, |
1154 | fifoRegisters.values()); |
1155 | } |
1156 | |
1157 | QModbusResponse QModbusServerPrivate::processEncapsulatedInterfaceTransportRequest( |
1158 | const QModbusRequest &request) |
1159 | { |
1160 | CHECK_SIZE_LESS_THAN(request); |
1161 | quint8 MEIType; |
1162 | request.decodeData(newData: &MEIType); |
1163 | |
1164 | switch (MEIType) { |
1165 | case EncapsulatedInterfaceTransport::CanOpenGeneralReference: |
1166 | break; |
1167 | case EncapsulatedInterfaceTransport::ReadDeviceIdentification: { |
1168 | if (request.dataSize() != 3u) { |
1169 | return QModbusExceptionResponse(request.functionCode(), |
1170 | QModbusExceptionResponse::IllegalDataValue); |
1171 | } |
1172 | |
1173 | const QVariant tmp = q_func()->value(option: QModbusServer::DeviceIdentification); |
1174 | if (tmp.isNull() || (!tmp.isValid())) { |
1175 | // TODO: Is this correct? |
1176 | return QModbusExceptionResponse(request.functionCode(), |
1177 | QModbusExceptionResponse::ServerDeviceFailure); |
1178 | } |
1179 | |
1180 | QModbusDeviceIdentification objectPool = tmp.value<QModbusDeviceIdentification>(); |
1181 | if (!objectPool.isValid()) { |
1182 | // TODO: Is this correct? |
1183 | return QModbusExceptionResponse(request.functionCode(), |
1184 | QModbusExceptionResponse::ServerDeviceFailure); |
1185 | } |
1186 | |
1187 | quint8 readDeviceIdCode, objectId; |
1188 | request.decodeData(newData: &MEIType, newData: &readDeviceIdCode, newData: &objectId); |
1189 | if (!objectPool.contains(objectId)) { |
1190 | // Individual access requires the object Id to be present, so we will always fail. |
1191 | // For all other cases we will reevaluate object Id after we reset it as per spec. |
1192 | objectId = QModbusDeviceIdentification::VendorNameObjectId; |
1193 | if (readDeviceIdCode == QModbusDeviceIdentification::IndividualReadDeviceIdCode |
1194 | || !objectPool.contains(objectId)) { |
1195 | return QModbusExceptionResponse(request.functionCode(), |
1196 | QModbusExceptionResponse::IllegalDataAddress); |
1197 | } |
1198 | } |
1199 | |
1200 | auto payload = [MEIType, readDeviceIdCode, objectId, objectPool](int lastObjectId) { |
1201 | // TODO: Take conformity level into account. |
1202 | QByteArray payload(6, Qt::Uninitialized); |
1203 | payload[0] = MEIType; |
1204 | payload[1] = readDeviceIdCode; |
1205 | payload[2] = quint8(objectPool.conformityLevel()); |
1206 | payload[3] = quint8(0x00); // no more follows |
1207 | payload[4] = quint8(0x00); // next object id |
1208 | payload[5] = quint8(0x00); // number of objects |
1209 | |
1210 | const QList<int> objectIds = objectPool.objectIds(); |
1211 | for (int id : objectIds) { |
1212 | if (id < objectId) |
1213 | continue; |
1214 | if (id > lastObjectId) |
1215 | break; |
1216 | const QByteArray object = objectPool.value(objectId: id); |
1217 | QByteArray objectData(2, Qt::Uninitialized); |
1218 | objectData[0] = id; |
1219 | objectData[1] = quint8(object.size()); |
1220 | objectData += object; |
1221 | if (payload.size() + objectData.size() > 253) { |
1222 | payload[3] = char(0xff); // more follows |
1223 | payload[4] = id; // next object id |
1224 | break; |
1225 | } |
1226 | payload.append(a: objectData); |
1227 | payload[5] = payload[5] + 1u; // number of objects |
1228 | } |
1229 | return payload; |
1230 | }; |
1231 | |
1232 | switch (readDeviceIdCode) { |
1233 | case QModbusDeviceIdentification::BasicReadDeviceIdCode: |
1234 | // TODO: How to handle a valid Id <> VendorName ... MajorMinorRevision |
1235 | return QModbusResponse(request.functionCode(), |
1236 | payload(QModbusDeviceIdentification::MajorMinorRevisionObjectId)); |
1237 | case QModbusDeviceIdentification::RegularReadDeviceIdCode: |
1238 | // TODO: How to handle a valid Id <> VendorUrl ... UserApplicationName |
1239 | return QModbusResponse(request.functionCode(), |
1240 | payload(QModbusDeviceIdentification::UserApplicationNameObjectId)); |
1241 | case QModbusDeviceIdentification::ExtendedReadDeviceIdCode: |
1242 | // TODO: How to handle a valid Id < ProductDependent |
1243 | return QModbusResponse(request.functionCode(), |
1244 | payload(QModbusDeviceIdentification::UndefinedObjectId)); |
1245 | case QModbusDeviceIdentification::IndividualReadDeviceIdCode: { |
1246 | // TODO: Take conformity level into account. |
1247 | const QByteArray object = objectPool.value(objectId); |
1248 | QByteArray (8, Qt::Uninitialized); |
1249 | header[0] = MEIType; |
1250 | header[1] = readDeviceIdCode; |
1251 | header[2] = quint8(objectPool.conformityLevel()); |
1252 | header[3] = quint8(0x00); // no more follows |
1253 | header[4] = quint8(0x00); // next object id |
1254 | header[5] = quint8(0x01); // number of objects |
1255 | header[6] = objectId; |
1256 | header[7] = quint8(object.size()); |
1257 | return QModbusResponse(request.functionCode(), QByteArray(header + object)); |
1258 | } |
1259 | default: |
1260 | return QModbusExceptionResponse(request.functionCode(), |
1261 | QModbusExceptionResponse::IllegalDataValue); |
1262 | } |
1263 | } break; |
1264 | } |
1265 | return QModbusExceptionResponse(request.functionCode(), |
1266 | QModbusExceptionResponse::IllegalFunction); |
1267 | } |
1268 | |
1269 | void QModbusServerPrivate::storeModbusCommEvent(const QModbusCommEvent &eventByte) |
1270 | { |
1271 | // Inserts an event byte at the start of the event log. If the event log |
1272 | // is already full, the byte at the end of the log will be removed. The |
1273 | // event log size is 64 bytes, starting at index 0. |
1274 | m_commEventLog.push_front(x: eventByte); |
1275 | if (m_commEventLog.size() > 64) |
1276 | m_commEventLog.pop_back(); |
1277 | } |
1278 | |
1279 | #undef CHECK_SIZE_EQUALS |
1280 | #undef CHECK_SIZE_LESS_THAN |
1281 | |
1282 | QT_END_NAMESPACE |
1283 | |