| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 The Qt Company Ltd. |
| ** Contact: http://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtSerialBus module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL3$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see http://www.qt.io/terms-conditions. For further |
| ** information use the contact form at http://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or later as published by the Free |
| ** Software Foundation and appearing in the file LICENSE.GPL included in |
| ** the packaging of this file. Please review the following information to |
| ** ensure the GNU General Public License version 2.0 requirements will be |
| ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qmodbusdeviceidentification.h" |
| #include "qmodbusserver.h" |
| #include "qmodbusserver_p.h" |
| #include "qmodbus_symbols_p.h" |
| |
| #include <QtCore/qbitarray.h> |
| #include <QtCore/qdebug.h> |
| #include <QtCore/qloggingcategory.h> |
| #include <QtCore/qvector.h> |
| |
| #include <algorithm> |
| |
| QT_BEGIN_NAMESPACE |
| |
| Q_DECLARE_LOGGING_CATEGORY(QT_MODBUS) |
| |
| /*! |
| \class QModbusServer |
| \inmodule QtSerialBus |
| \since 5.8 |
| |
| \brief The QModbusServer class is the interface to receive and process Modbus requests. |
| |
| Modbus networks can have multiple Modbus servers. Modbus Servers are read/written by a |
| Modbus client represented by \l QModbusClient. QModbusServer communicates with a Modbus |
| backend, providing users with a convenient API. |
| */ |
| |
| /*! |
| \enum QModbusServer::Option |
| |
| Each Modbus server has a set of values associated with it, each with its own option. |
| |
| The general purpose options (and the associated types) are: |
| |
| \value DiagnosticRegister The diagnostic register of the server. \c quint16 |
| \value ExceptionStatusOffset The exception status byte offset of the server. \c quint16 |
| \value DeviceBusy Flag to signal the server is engaged in processing a |
| long-duration program command. \c quint16 |
| \value AsciiInputDelimiter The Modbus ASCII end of message delimiter. \c char |
| \value ListenOnlyMode Flag to set listen only mode of the server. |
| This function is typically supported only |
| by Modbus serial devices. \c bool |
| \value ServerIdentifier The identifier of the server, \b not the server address. \c quint8 |
| \value RunIndicatorStatus The run indicator of the server. \c quint8 |
| \value AdditionalData The additional data of the server. \c QByteArray |
| \value DeviceIdentification The physical and functional description of the server. \c QModbusDeviceIdentification |
| |
| User options: |
| |
| \value UserOption The first option that can be used for user-specific purposes. |
| |
| For user options, it is up to the developer to decide which types to use and ensure that |
| components use the correct types when accessing and setting values. |
| */ |
| |
| /*! |
| Constructs a Modbus server with the specified \a parent. |
| */ |
| QModbusServer::QModbusServer(QObject *parent) : |
| QModbusDevice(*new QModbusServerPrivate, parent) |
| { |
| } |
| |
| /*! |
| \internal |
| */ |
| QModbusServer::~QModbusServer() |
| { |
| } |
| |
| /*! |
| \internal |
| */ |
| QModbusServer::QModbusServer(QModbusServerPrivate &dd, QObject *parent) : |
| QModbusDevice(dd, parent) |
| { |
| } |
| |
| /*! |
| Sets the registered map structure for requests from other ModBus clients to \a map. |
| The register values are initialized with zero. Returns \c true on success; otherwise \c false. |
| |
| If this function is not called before connecting, a default register with zero |
| entries is setup. |
| |
| \note Calling this function discards any register value that was previously set. |
| */ |
| bool QModbusServer::setMap(const QModbusDataUnitMap &map) |
| { |
| return d_func()->setMap(map); |
| } |
| |
| /*! |
| Sets the address for this Modbus server instance to \a serverAddress. |
| |
| \sa serverAddress() |
| */ |
| void QModbusServer::setServerAddress(int serverAddress) |
| { |
| Q_D(QModbusServer); |
| d->m_serverAddress = serverAddress; |
| } |
| |
| /*! |
| Returns the address of this Mobus server instance. |
| |
| \sa setServerAddress() |
| */ |
| int QModbusServer::serverAddress() const |
| { |
| Q_D(const QModbusServer); |
| |
| return d->m_serverAddress; |
| } |
| |
| /*! |
| Returns the value for \a option or an invalid \c QVariant if the option is |
| not set. |
| |
| \table |
| \header |
| \li Option |
| \li Description |
| \row |
| \li \l QModbusServer::DiagnosticRegister |
| \li Returns the diagnostic register value of the server. The |
| diagnostic register contains device specific contents where |
| each bit has a specific meaning. |
| \row |
| \li \l QModbusServer::ExceptionStatusOffset |
| \li Returns the offset address of the exception status byte |
| location in the coils register. |
| \row |
| \li \l QModbusServer::DeviceBusy |
| \li Returns a flag that signals if the server is engaged in |
| processing a long-duration program command. |
| \row |
| \li \l QModbusServer::AsciiInputDelimiter |
| \li Returns a end of message delimiter of Modbus ASCII messages. |
| \row |
| \li \l QModbusServer::ListenOnlyMode |
| \li Returns the server's listen only state. Messages are monitored |
| but no response will be sent. |
| \row |
| \li \l QModbusServer::ServerIdentifier |
| \li Returns the server manufacturer's identifier code. This can be |
| an arbitrary value in the range of \c 0x00 to 0xff. |
| \row |
| \li \l QModbusServer::RunIndicatorStatus |
| \li Returns the server's run indicator status. This data is used as |
| addendum by the \l QModbusPdu::ReportServerId function code. |
| \row |
| \li \l QModbusServer::AdditionalData |
| \li Returns the server's additional data. This data is used as |
| addendum by the \l QModbusPdu::ReportServerId function code. |
| \row |
| \li \l QModbusServer::DeviceIdentification |
| \li Returns the server's physical and functional description. |
| \row |
| \li \l QModbusServer::UserOption |
| \li Returns the value of a user option. |
| |
| \note For user options, it is up to the developer to decide |
| which types to use and ensure that components use the correct |
| types when accessing and setting values. |
| \endtable |
| */ |
| QVariant QModbusServer::value(int option) const |
| { |
| Q_D(const QModbusServer); |
| |
| switch (option) { |
| case DiagnosticRegister: |
| return d->m_serverOptions.value(option, quint16(0x0000)); |
| case ExceptionStatusOffset: |
| return d->m_serverOptions.value(option, quint16(0x0000)); |
| case DeviceBusy: |
| return d->m_serverOptions.value(option, quint16(0x0000)); |
| case AsciiInputDelimiter: |
| return d->m_serverOptions.value(option, '\n'); |
| case ListenOnlyMode: |
| return d->m_serverOptions.value(option, false); |
| case ServerIdentifier: |
| return d->m_serverOptions.value(option, quint8(0x0a)); |
| case RunIndicatorStatus: |
| return d->m_serverOptions.value(option, quint8(0xff)); |
| case AdditionalData: |
| return d->m_serverOptions.value(option, QByteArray("Qt Modbus Server")); |
| case DeviceIdentification: |
| return d->m_serverOptions.value(option, QVariant()); |
| }; |
| |
| if (option < UserOption) |
| return QVariant(); |
| |
| return d->m_serverOptions.value(option, QVariant()); |
| } |
| |
| /*! |
| Sets the \a newValue for \a option and returns \c true on success; \c false |
| otherwise. |
| |
| \note If the option's associated type is \c quint8 or \c quint16 and the |
| type of \a newValue is larger, the data will be truncated or conversation |
| will fail. |
| |
| \table |
| \header |
| \li Key |
| \li Description |
| \row |
| \li \l QModbusServer::DiagnosticRegister |
| \li Sets the diagnostic register of the server in a device specific |
| encoding to \a newValue. The default value preset is \c 0x0000. |
| The bit values of the register need device specific documentation. |
| \row |
| \li \l QModbusServer::ExceptionStatusOffset |
| \li Sets the exception status byte offset of the server to |
| \a newValue which is the absolute offset address in the coils |
| (0x register). Modbus register table starting with \c 0x0000h. |
| The default value preset is \c 0x0000, using the exception |
| status coils similar to Modicon 984 CPUs (coils 1-8). |
| |
| The function returns \c true if the coils register contains the |
| 8 bits required for storing and retrieving the status coils, |
| otherwise \c false. |
| \row |
| \li \l QModbusServer::DeviceBusy |
| \li Sets a flag that signals that the server is engaged in |
| processing a long-duration program command. Valid values are |
| \c 0x0000 (not busy) and \c 0xffff (busy). |
| The default value preset is \c 0x0000. |
| \row |
| \li \l QModbusServer::AsciiInputDelimiter |
| \li The \a newValue becomes the end of message delimiter for future |
| Modbus ASCII messages. The default value preset is \c {\n}. |
| \row |
| \li \l QModbusServer::ListenOnlyMode |
| \li Ss the server's listen only state to \a newValue. If listen only |
| mode is set to \c true, messages are monitored but no response |
| will be sent. The default value preset is \c false. |
| \row |
| \li \l QModbusServer::ServerIdentifier |
| \li Sets the server's manufacturer identifier to \a newValue. |
| Possible values are in the range of \c 0x00 to 0xff. |
| The default value preset is \c 0x0a. |
| \row |
| \li \l QModbusServer::RunIndicatorStatus |
| \li Sets the servers' run indicator status to \a newValue. This |
| data is used as addendum by the \l QModbusPdu::ReportServerId |
| function code. Valid values are \c 0x00 (OFF) and \c 0xff (ON). |
| The default value preset is \c 0xff (ON). |
| \row |
| \li \l QModbusServer::AdditionalData |
| \li Sets the server's additional data to \a newValue. This data is |
| used as addendum by the \l QModbusPdu::ReportServerId function |
| code. The maximum data size cannot exceed 249 bytes to match |
| response message size restrictions. |
| The default value preset is \c {Qt Modbus Server}. |
| \row |
| \li \l QModbusServer::DeviceIdentification |
| \li Sets the server's physical and functional description. By default |
| there is no additional device identification data set. |
| \row |
| \li \l QModbusServer::UserOption |
| \li Sets the value of a user option to \a newValue. |
| |
| \note For user options, it is up to the developer to decide |
| which types to use and ensure that components use the correct |
| types when accessing and setting values. |
| \endtable |
| */ |
| bool QModbusServer::setValue(int option, const QVariant &newValue) |
| { |
| #define CHECK_INT_OR_UINT(val) \ |
| do { \ |
| if ((val.type() != QVariant::Int) && (val.type() != QVariant::UInt)) \ |
| return false; \ |
| } while (0) |
| |
| Q_D(QModbusServer); |
| switch (option) { |
| case DiagnosticRegister: |
| CHECK_INT_OR_UINT(newValue); |
| d->m_serverOptions.insert(option, newValue); |
| return true; |
| case ExceptionStatusOffset: { |
| CHECK_INT_OR_UINT(newValue); |
| const quint16 tmp = newValue.value<quint16>(); |
| QModbusDataUnit coils(QModbusDataUnit::Coils, tmp, 8); |
| if (!data(&coils)) |
| return false; |
| d->m_serverOptions.insert(option, tmp); |
| return true; |
| } |
| case DeviceBusy: { |
| CHECK_INT_OR_UINT(newValue); |
| const quint16 tmp = newValue.value<quint16>(); |
| if ((tmp != 0x0000) && (tmp != 0xffff)) |
| return false; |
| d->m_serverOptions.insert(option, tmp); |
| return true; |
| } |
| case AsciiInputDelimiter: { |
| CHECK_INT_OR_UINT(newValue); |
| bool ok = false; |
| if (newValue.toUInt(&ok) > 0xff || !ok) |
| return false; |
| d->m_serverOptions.insert(option, newValue); |
| return true; |
| } |
| case ListenOnlyMode: { |
| if (newValue.type() != QVariant::Bool) |
| return false; |
| d->m_serverOptions.insert(option, newValue); |
| return true; |
| } |
| case ServerIdentifier: |
| CHECK_INT_OR_UINT(newValue); |
| d->m_serverOptions.insert(option, newValue); |
| return true; |
| case RunIndicatorStatus: { |
| CHECK_INT_OR_UINT(newValue); |
| const quint8 tmp = newValue.value<quint8>(); |
| if ((tmp != 0x00) && (tmp != 0xff)) |
| return false; |
| d->m_serverOptions.insert(option, tmp); |
| return true; |
| } |
| case AdditionalData: { |
| if (newValue.type() != QVariant::ByteArray) |
| return false; |
| const QByteArray additionalData = newValue.toByteArray(); |
| if (additionalData.size() > 249) |
| return false; |
| d->m_serverOptions.insert(option, additionalData); |
| return true; |
| } |
| case DeviceIdentification: |
| if (!newValue.canConvert<QModbusDeviceIdentification>()) |
| return false; |
| d->m_serverOptions.insert(option, newValue); |
| return true; |
| default: |
| break; |
| }; |
| |
| if (option < UserOption) |
| return false; |
| d->m_serverOptions.insert(option, newValue); |
| return true; |
| |
| #undef CHECK_INT_OR_UINT |
| } |
| |
| /*! |
| \fn bool QModbusServer::processesBroadcast() const |
| |
| Subclasses should implement this function if the transport layer shall handle broadcasts. |
| The implementation then should return \c true if the currently processed request is a |
| broadcast request; otherwise \c false. The default implementation returns always \c false. |
| |
| \note The return value of this function only makes sense from within processRequest() or |
| processPrivateRequest(), otherwise it can only tell that the last request processed |
| was a broadcast request. |
| */ |
| |
| /*! |
| Reads data stored in the Modbus server. A Modbus server has four tables (\a table) and each |
| have a unique \a address field, which is used to read \a data from the desired field. |
| See QModbusDataUnit::RegisterType for more information about the different tables. |
| Returns \c false if address is outside of the map range or the register type is not even defined. |
| |
| \sa QModbusDataUnit::RegisterType, setData() |
| */ |
| bool QModbusServer::data(QModbusDataUnit::RegisterType table, quint16 address, quint16 *data) const |
| { |
| QModbusDataUnit unit(table, address, 1u); |
| if (data && readData(&unit)) { |
| *data = unit.value(0); |
| return true; |
| } |
| return false; |
| } |
| |
| /*! |
| Returns the values in the register range given by \a newData. |
| |
| \a newData must provide a valid register type, start address |
| and valueCount. The returned \a newData will contain the register values |
| associated with the given range. |
| |
| If \a newData contains a valid register type but a negative start address |
| the entire register map is returned and \a newData appropriately sized. |
| */ |
| bool QModbusServer::data(QModbusDataUnit *newData) const |
| { |
| return readData(newData); |
| } |
| |
| /*! |
| Writes data to the Modbus server. A Modbus server has four tables (\a table) and each have a |
| unique \a address field, which is used to write \a data to the desired field. |
| Returns \c false if address outside of the map range. |
| |
| If the call was successful the \l dataWritten() signal is emitted. Note that |
| the signal is not emitted when \a data has not changed. Nevertheless this function |
| returns \c true in such cases. |
| |
| \sa QModbusDataUnit::RegisterType, data(), dataWritten() |
| */ |
| bool QModbusServer::setData(QModbusDataUnit::RegisterType table, quint16 address, quint16 data) |
| { |
| return writeData(QModbusDataUnit(table, address, QVector<quint16>() << data)); |
| } |
| |
| /*! |
| Writes \a newData to the Modbus server map. |
| Returns \c false if the \a newData range is outside of the map range. |
| |
| If the call was successful the \l dataWritten() signal is emitted. Note that |
| the signal is not emitted when the addressed register has not changed. This |
| may happen when \a newData contains exactly the same values as the |
| register already. Nevertheless this function returns \c true in such cases. |
| |
| \sa data() |
| */ |
| bool QModbusServer::setData(const QModbusDataUnit &newData) |
| { |
| return writeData(newData); |
| } |
| |
| /*! |
| Writes \a newData to the Modbus server map. Returns \c true on success, |
| or \c false if the \a newData range is outside of the map range or the |
| registerType() does not exist. |
| |
| \note Sub-classes that implement writing to a different backing store |
| then default one, also need to implement setMap() and readData(). The |
| dataWritten() signal needs to be emitted from within the functions |
| implementation as well. |
| |
| \sa setMap(), readData(), dataWritten() |
| */ |
| bool QModbusServer::writeData(const QModbusDataUnit &newData) |
| { |
| Q_D(QModbusServer); |
| if (!d->m_modbusDataUnitMap.contains(newData.registerType())) |
| return false; |
| |
| QModbusDataUnit ¤t = d->m_modbusDataUnitMap[newData.registerType()]; |
| if (!current.isValid()) |
| return false; |
| |
| // check range start is within internal map range |
| int internalRangeEndAddress = current.startAddress() + current.valueCount() - 1; |
| if (newData.startAddress() < current.startAddress() |
| || newData.startAddress() > internalRangeEndAddress) { |
| return false; |
| } |
| |
| // check range end is within internal map range |
| int rangeEndAddress = newData.startAddress() + newData.valueCount() - 1; |
| if (rangeEndAddress < current.startAddress() || rangeEndAddress > internalRangeEndAddress) |
| return false; |
| |
| bool changeRequired = false; |
| for (uint i = 0; i < newData.valueCount(); i++) { |
| const quint16 newValue = newData.value(i); |
| const int translatedIndex = newData.startAddress() - current.startAddress() + i; |
| changeRequired |= (current.value(translatedIndex) != newValue); |
| current.setValue(translatedIndex, newValue); |
| } |
| |
| if (changeRequired) |
| emit dataWritten(newData.registerType(), newData.startAddress(), newData.valueCount()); |
| return true; |
| } |
| |
| /*! |
| Reads the values in the register range given by \a newData and writes the |
| data back to \a newData. Returns \c true on success or \c false if |
| \a newData is \c 0, the \a newData range is outside of the map range or the |
| registerType() does not exist. |
| |
| \note Sub-classes that implement reading from a different backing store |
| then default one, also need to implement setMap() and writeData(). |
| |
| \sa setMap(), writeData() |
| */ |
| bool QModbusServer::readData(QModbusDataUnit *newData) const |
| { |
| Q_D(const QModbusServer); |
| |
| if ((!newData) || (!d->m_modbusDataUnitMap.contains(newData->registerType()))) |
| return false; |
| |
| const QModbusDataUnit ¤t = d->m_modbusDataUnitMap.value(newData->registerType()); |
| if (!current.isValid()) |
| return false; |
| |
| // return entire map for given type |
| if (newData->startAddress() < 0) { |
| *newData = current; |
| return true; |
| } |
| |
| // check range start is within internal map range |
| int internalRangeEndAddress = current.startAddress() + current.valueCount() - 1; |
| if (newData->startAddress() < current.startAddress() |
| || newData->startAddress() > internalRangeEndAddress) { |
| return false; |
| } |
| |
| // check range end is within internal map range |
| const int rangeEndAddress = newData->startAddress() + newData->valueCount() - 1; |
| if (rangeEndAddress < current.startAddress() || rangeEndAddress > internalRangeEndAddress) |
| return false; |
| |
| newData->setValues(current.values().mid(newData->startAddress() - current.startAddress(), newData->valueCount())); |
| return true; |
| } |
| |
| /*! |
| \fn void QModbusServer::dataWritten(QModbusDataUnit::RegisterType table, int address, int size) |
| |
| This signal is emitted when a Modbus client has written one or more fields of data to the |
| Modbus server. The signal contains information about the fields that were written: |
| \list |
| \li Register type (\a table) that was written, |
| \li \a address of the first field that was written, |
| \li and \a size of consecutive fields that were written starting from \a address. |
| \endlist |
| |
| The signal is not emitted when the to-be-written fields have not changed |
| due to no change in value. |
| */ |
| |
| /*! |
| Processes a Modbus client \a request and returns a Modbus response. |
| This function returns a \l QModbusResponse or \l QModbusExceptionResponse depending |
| on the nature of the request. |
| |
| The default implementation of this function handles all standard Modbus |
| function codes as defined by the Modbus Application Protocol Specification 1.1b. |
| All other Modbus function codes not included in the specification are forwarded to |
| \l processPrivateRequest(). |
| |
| The default handling of the standard Modbus function code requests can be overwritten |
| by reimplementing this function. The override must handle the request type |
| in question and return the appropriate \l QModbusResponse. A common reason might be to |
| filter out function code requests for data values to limit read/write access and |
| function codes not desired in particular implementations such as serial line diagnostics |
| on ethernet or Modbus Plus transport layers. Every other request type should be |
| forwarded to this default implementation. |
| |
| \note This function should not be overridden to provide a custom implementation for |
| non-standard Modbus request types. |
| |
| \sa processPrivateRequest() |
| */ |
| QModbusResponse QModbusServer::processRequest(const QModbusPdu &request) |
| { |
| return d_func()->processRequest(request); |
| } |
| |
| /*! |
| This function should be implemented by custom Modbus servers. It is |
| called by \l processRequest() if the given \a request is not a standard |
| Modbus request. |
| |
| Overwriting this function allows handling of additional function codes and |
| subfunction-codes not specified in the Modbus Application Protocol |
| Specification 1.1b. Reimplementations should call this function again to |
| ensure an exception response is returned for all unknown function codes the |
| custom Modbus implementation does not handle. |
| |
| This default implementation returns a \c QModbusExceptionResponse with the |
| \a request function code and error code set to illegal function. |
| |
| \sa processRequest() |
| */ |
| QModbusResponse QModbusServer::processPrivateRequest(const QModbusPdu &request) |
| { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalFunction); |
| } |
| |
| // -- QModbusServerPrivate |
| |
| bool QModbusServerPrivate::setMap(const QModbusDataUnitMap &map) |
| { |
| m_modbusDataUnitMap = map; |
| return true; |
| } |
| |
| QModbusResponse QModbusServerPrivate::processRequest(const QModbusPdu &request) |
| { |
| switch (request.functionCode()) { |
| case QModbusRequest::ReadCoils: |
| return processReadCoilsRequest(request); |
| case QModbusRequest::ReadDiscreteInputs: |
| return processReadDiscreteInputsRequest(request); |
| case QModbusRequest::ReadHoldingRegisters: |
| return processReadHoldingRegistersRequest(request); |
| case QModbusRequest::ReadInputRegisters: |
| return processReadInputRegistersRequest(request); |
| case QModbusRequest::WriteSingleCoil: |
| return processWriteSingleCoilRequest(request); |
| case QModbusRequest::WriteSingleRegister: |
| return processWriteSingleRegisterRequest(request); |
| case QModbusRequest::ReadExceptionStatus: |
| return processReadExceptionStatusRequest(request); |
| case QModbusRequest::Diagnostics: |
| return processDiagnosticsRequest(request); |
| case QModbusRequest::GetCommEventCounter: |
| return processGetCommEventCounterRequest(request); |
| case QModbusRequest::GetCommEventLog: |
| return processGetCommEventLogRequest(request); |
| case QModbusRequest::WriteMultipleCoils: |
| return processWriteMultipleCoilsRequest(request); |
| case QModbusRequest::WriteMultipleRegisters: |
| return processWriteMultipleRegistersRequest(request); |
| case QModbusRequest::ReportServerId: |
| return processReportServerIdRequest(request); |
| case QModbusRequest::ReadFileRecord: // TODO: Implement. |
| case QModbusRequest::WriteFileRecord: // TODO: Implement. |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalFunction); |
| case QModbusRequest::MaskWriteRegister: |
| return processMaskWriteRegisterRequest(request); |
| case QModbusRequest::ReadWriteMultipleRegisters: |
| return processReadWriteMultipleRegistersRequest(request); |
| case QModbusRequest::ReadFifoQueue: |
| return processReadFifoQueueRequest(request); |
| case QModbusRequest::EncapsulatedInterfaceTransport: |
| return processEncapsulatedInterfaceTransportRequest(request); |
| default: |
| break; |
| } |
| return q_func()->processPrivateRequest(request); |
| } |
| |
| #define CHECK_SIZE_EQUALS(req) \ |
| do { \ |
| if (req.dataSize() != QModbusRequest::minimumDataSize(req)) { \ |
| qCDebug(QT_MODBUS) << "(Server) The request's data size does not equal the expected size."; \ |
| return QModbusExceptionResponse(req.functionCode(), \ |
| QModbusExceptionResponse::IllegalDataValue); \ |
| } \ |
| } while (0) |
| |
| #define CHECK_SIZE_LESS_THAN(req) \ |
| do { \ |
| if (req.dataSize() < QModbusRequest::minimumDataSize(req)) { \ |
| qCDebug(QT_MODBUS) << "(Server) The request's data size is less than the expected size."; \ |
| return QModbusExceptionResponse(req.functionCode(), \ |
| QModbusExceptionResponse::IllegalDataValue); \ |
| } \ |
| } while (0) |
| |
| QModbusResponse QModbusServerPrivate::processReadCoilsRequest(const QModbusRequest &request) |
| { |
| return readBits(request, QModbusDataUnit::Coils); |
| } |
| |
| QModbusResponse QModbusServerPrivate::processReadDiscreteInputsRequest(const QModbusRequest &rqst) |
| { |
| return readBits(rqst, QModbusDataUnit::DiscreteInputs); |
| } |
| |
| QModbusResponse QModbusServerPrivate::readBits(const QModbusPdu &request, |
| QModbusDataUnit::RegisterType unitType) |
| { |
| CHECK_SIZE_EQUALS(request); |
| quint16 address, count; |
| request.decodeData(&address, &count); |
| |
| if ((count < 0x0001) || (count > 0x07D0)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataValue); |
| } |
| |
| // Get the requested range out of the registers. |
| QModbusDataUnit unit(unitType, address, count); |
| if (!q_func()->data(&unit)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataAddress); |
| } |
| |
| quint8 byteCount = count / 8; |
| if ((count % 8) != 0) { |
| byteCount += 1; |
| // If the range is not a multiple of 8, resize. |
| unit.setValueCount(byteCount * 8); |
| } |
| |
| // Using byteCount * 8 so the remaining bits in the last byte are zero |
| QBitArray bytes(byteCount * 8); |
| |
| address = 0; // The data range now starts with zero. |
| for ( ; address < count; ++address) |
| bytes.setBit(address, unit.value(address)); |
| |
| QByteArray payload = QByteArray::fromRawData(bytes.bits(), byteCount); |
| payload.prepend(char(byteCount)); |
| return QModbusResponse(request.functionCode(), payload); |
| } |
| |
| QModbusResponse QModbusServerPrivate::processReadHoldingRegistersRequest(const QModbusRequest &rqst) |
| { |
| return readBytes(rqst, QModbusDataUnit::HoldingRegisters); |
| } |
| |
| QModbusResponse QModbusServerPrivate::processReadInputRegistersRequest(const QModbusRequest &rqst) |
| { |
| return readBytes(rqst, QModbusDataUnit::InputRegisters); |
| } |
| |
| QModbusResponse QModbusServerPrivate::readBytes(const QModbusPdu &request, |
| QModbusDataUnit::RegisterType unitType) |
| { |
| CHECK_SIZE_EQUALS(request); |
| quint16 address, count; |
| request.decodeData(&address, &count); |
| |
| if ((count < 0x0001) || (count > 0x007D)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataValue); |
| } |
| |
| // Get the requested range out of the registers. |
| QModbusDataUnit unit(unitType, address, count); |
| if (!q_func()->data(&unit)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataAddress); |
| } |
| |
| return QModbusResponse(request.functionCode(), quint8(count * 2), unit.values()); |
| } |
| |
| QModbusResponse QModbusServerPrivate::processWriteSingleCoilRequest(const QModbusRequest &request) |
| { |
| return writeSingle(request, QModbusDataUnit::Coils); |
| } |
| |
| QModbusResponse QModbusServerPrivate::processWriteSingleRegisterRequest(const QModbusRequest &rqst) |
| { |
| return writeSingle(rqst, QModbusDataUnit::HoldingRegisters); |
| } |
| |
| QModbusResponse QModbusServerPrivate::writeSingle(const QModbusPdu &request, |
| QModbusDataUnit::RegisterType unitType) |
| { |
| CHECK_SIZE_EQUALS(request); |
| quint16 address, value; |
| request.decodeData(&address, &value); |
| |
| if ((unitType == QModbusDataUnit::Coils) && ((value != Coil::Off) && (value != Coil::On))) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataValue); |
| } |
| |
| quint16 reg; // Get the requested register, but deliberately ignore. |
| if (!q_func()->data(unitType, address, ®)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataAddress); |
| } |
| |
| if (!q_func()->setData(unitType, address, value)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::ServerDeviceFailure); |
| } |
| |
| return QModbusResponse(request.functionCode(), address, value); |
| } |
| |
| QModbusResponse QModbusServerPrivate::processReadExceptionStatusRequest(const QModbusRequest &request) |
| { |
| CHECK_SIZE_EQUALS(request); |
| |
| // Get the requested range out of the registers. |
| const QVariant tmp = q_func()->value(QModbusServer::ExceptionStatusOffset); |
| if (tmp.isNull() || (!tmp.isValid())) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::ServerDeviceFailure); |
| } |
| const quint16 exceptionStatusOffset = tmp.value<quint16>(); |
| QModbusDataUnit coils(QModbusDataUnit::Coils, exceptionStatusOffset, 8); |
| if (!q_func()->data(&coils)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataAddress); |
| } |
| |
| quint16 address = 0; |
| quint8 byte = 0; |
| for (int currentBit = 0; currentBit < 8; ++currentBit) |
| if (coils.value(address++)) // The padding happens inside value(). |
| byte |= (1U << currentBit); |
| |
| return QModbusResponse(request.functionCode(), byte); |
| } |
| |
| QModbusResponse QModbusServerPrivate::processDiagnosticsRequest(const QModbusRequest &request) |
| { |
| #define CHECK_SIZE_AND_CONDITION(req, condition) \ |
| CHECK_SIZE_EQUALS(req); \ |
| do { \ |
| if ((condition)) { \ |
| return QModbusExceptionResponse(req.functionCode(), \ |
| QModbusExceptionResponse::IllegalDataValue); \ |
| } \ |
| } while (0) |
| |
| quint16 subFunctionCode, data = 0xffff; |
| request.decodeData(&subFunctionCode, &data); |
| |
| switch (subFunctionCode) { |
| case Diagnostics::ReturnQueryData: |
| return QModbusResponse(request.functionCode(), request.data()); |
| |
| case Diagnostics::RestartCommunicationsOption: { |
| CHECK_SIZE_AND_CONDITION(request, ((data != 0xff00) && (data != 0x0000))); |
| // Restarts the communication by closing the connection and re-opening. After closing, |
| // all communication counters are cleared and the listen only mode set to false. This |
| // function is the only way to remotely clear the listen only mode and bring the device |
| // back into communication. If data is 0xff00, the event log history is also cleared. |
| q_func()->disconnectDevice(); |
| if (data == 0xff00) |
| m_commEventLog.clear(); |
| |
| resetCommunicationCounters(); |
| q_func()->setValue(QModbusServer::ListenOnlyMode, false); |
| storeModbusCommEvent(QModbusCommEvent::InitiatedCommunicationRestart); |
| |
| if (!q_func()->connectDevice()) { |
| qCWarning(QT_MODBUS) << "(Server) Cannot restart server communication"; |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::ServerDeviceFailure); |
| } |
| return QModbusResponse(request.functionCode(), request.data()); |
| } break; |
| |
| case Diagnostics::ChangeAsciiInputDelimiter: { |
| const QByteArray data = request.data().mid(2, 2); |
| CHECK_SIZE_AND_CONDITION(request, (data[1] != 0x00)); |
| q_func()->setValue(QModbusServer::AsciiInputDelimiter, data[0]); |
| return QModbusResponse(request.functionCode(), request.data()); |
| } break; |
| |
| case Diagnostics::ForceListenOnlyMode: |
| CHECK_SIZE_AND_CONDITION(request, (data != 0x0000)); |
| q_func()->setValue(QModbusServer::ListenOnlyMode, true); |
| storeModbusCommEvent(QModbusCommEvent::EnteredListenOnlyMode); |
| return QModbusResponse(); |
| |
| case Diagnostics::ClearCountersAndDiagnosticRegister: |
| CHECK_SIZE_AND_CONDITION(request, (data != 0x0000)); |
| resetCommunicationCounters(); |
| q_func()->setValue(QModbusServer::DiagnosticRegister, 0x0000); |
| return QModbusResponse(request.functionCode(), request.data()); |
| |
| case Diagnostics::ReturnDiagnosticRegister: |
| case Diagnostics::ReturnBusMessageCount: |
| case Diagnostics::ReturnBusCommunicationErrorCount: |
| case Diagnostics::ReturnBusExceptionErrorCount: |
| case Diagnostics::ReturnServerMessageCount: |
| case Diagnostics::ReturnServerNoResponseCount: |
| case Diagnostics::ReturnServerNAKCount: |
| case Diagnostics::ReturnServerBusyCount: |
| case Diagnostics::ReturnBusCharacterOverrunCount: |
| CHECK_SIZE_AND_CONDITION(request, (data != 0x0000)); |
| return QModbusResponse(request.functionCode(), subFunctionCode, |
| m_counters[static_cast<Counter> (subFunctionCode)]); |
| |
| case Diagnostics::ClearOverrunCounterAndFlag: { |
| CHECK_SIZE_AND_CONDITION(request, (data != 0x0000)); |
| m_counters[Diagnostics::ReturnBusCharacterOverrunCount] = 0; |
| quint16 reg = q_func()->value(QModbusServer::DiagnosticRegister).value<quint16>(); |
| q_func()->setValue(QModbusServer::DiagnosticRegister, reg &~ 1); // clear first bit |
| return QModbusResponse(request.functionCode(), request.data()); |
| } |
| } |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalFunction); |
| |
| #undef CHECK_SIZE_AND_CONDITION |
| } |
| |
| QModbusResponse QModbusServerPrivate::processGetCommEventCounterRequest(const QModbusRequest &request) |
| { |
| CHECK_SIZE_EQUALS(request); |
| const QVariant tmp = q_func()->value(QModbusServer::DeviceBusy); |
| if (tmp.isNull() || (!tmp.isValid())) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::ServerDeviceFailure); |
| } |
| const quint16 deviceBusy = tmp.value<quint16>(); |
| return QModbusResponse(request.functionCode(), deviceBusy, m_counters[Counter::CommEvent]); |
| } |
| |
| QModbusResponse QModbusServerPrivate::processGetCommEventLogRequest(const QModbusRequest &request) |
| { |
| CHECK_SIZE_EQUALS(request); |
| const QVariant tmp = q_func()->value(QModbusServer::DeviceBusy); |
| if (tmp.isNull() || (!tmp.isValid())) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::ServerDeviceFailure); |
| } |
| const quint16 deviceBusy = tmp.value<quint16>(); |
| |
| QVector<quint8> eventLog(int(m_commEventLog.size())); |
| std::copy(m_commEventLog.cbegin(), m_commEventLog.cend(), eventLog.begin()); |
| |
| // 6 -> 3 x 2 Bytes (Status, Event Count and Message Count) |
| return QModbusResponse(request.functionCode(), quint8(eventLog.size() + 6), deviceBusy, |
| m_counters[Counter::CommEvent], m_counters[Counter::BusMessage], eventLog); |
| } |
| |
| QModbusResponse QModbusServerPrivate::processWriteMultipleCoilsRequest(const QModbusRequest &request) |
| { |
| CHECK_SIZE_LESS_THAN(request); |
| quint16 address, numberOfCoils; |
| quint8 byteCount; |
| request.decodeData(&address, &numberOfCoils, &byteCount); |
| |
| // byte count does not match number of data bytes following |
| if (byteCount != (request.dataSize() - 5 )) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataValue); |
| } |
| |
| quint8 expectedBytes = numberOfCoils / 8; |
| if ((numberOfCoils % 8) != 0) |
| expectedBytes += 1; |
| |
| if ((numberOfCoils < 0x0001) || (numberOfCoils > 0x07B0) || (expectedBytes != byteCount)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataValue); |
| } |
| |
| // Get the requested range out of the registers. |
| QModbusDataUnit coils(QModbusDataUnit::Coils, address, numberOfCoils); |
| if (!q_func()->data(&coils)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataAddress); |
| } |
| |
| QVector<quint8> bytes; |
| const QByteArray payload = request.data().mid(5); |
| for (qint32 i = payload.size() - 1; i >= 0; --i) |
| bytes.append(quint8(payload[i])); |
| |
| // Since we picked the coils at start address, data |
| // range is numberOfCoils and therefore index too. |
| quint16 coil = numberOfCoils; |
| qint32 currentBit = 8 - ((byteCount * 8) - numberOfCoils); |
| for (quint8 currentByte : qAsConst(bytes)) { |
| for (currentBit -= 1; currentBit >= 0; --currentBit) |
| coils.setValue(--coil, currentByte & (1U << currentBit) ? 1 : 0); |
| currentBit = 8; |
| } |
| |
| if (!q_func()->setData(coils)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::ServerDeviceFailure); |
| } |
| |
| return QModbusResponse(request.functionCode(), address, numberOfCoils); |
| } |
| |
| QModbusResponse QModbusServerPrivate::processWriteMultipleRegistersRequest( |
| const QModbusRequest &request) |
| { |
| CHECK_SIZE_LESS_THAN(request); |
| quint16 address, numberOfRegisters; |
| quint8 byteCount; |
| request.decodeData(&address, &numberOfRegisters, &byteCount); |
| |
| // byte count does not match number of data bytes following or register count |
| if ((byteCount != (request.dataSize() - 5 )) || (byteCount != (numberOfRegisters * 2))) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataValue); |
| } |
| |
| if ((numberOfRegisters < 0x0001) || (numberOfRegisters > 0x007B)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataValue); |
| } |
| |
| // Get the requested range out of the registers. |
| QModbusDataUnit registers(QModbusDataUnit::HoldingRegisters, address, numberOfRegisters); |
| if (!q_func()->data(®isters)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataAddress); |
| } |
| |
| const QByteArray pduData = request.data().remove(0,5); |
| QDataStream stream(pduData); |
| |
| QVector<quint16> values; |
| quint16 tmp; |
| for (int i = 0; i < numberOfRegisters; i++) { |
| stream >> tmp; |
| values.append(tmp); |
| } |
| |
| registers.setValues(values); |
| |
| if (!q_func()->setData(registers)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::ServerDeviceFailure); |
| } |
| |
| return QModbusResponse(request.functionCode(), address, numberOfRegisters); |
| } |
| |
| QModbusResponse QModbusServerPrivate::processReportServerIdRequest(const QModbusRequest &request) |
| { |
| CHECK_SIZE_EQUALS(request); |
| |
| Q_Q(QModbusServer); |
| |
| QByteArray data; |
| QVariant tmp = q->value(QModbusServer::ServerIdentifier); |
| if (tmp.isNull() || (!tmp.isValid())) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::ServerDeviceFailure); |
| } |
| data.append(tmp.value<quint8>()); |
| |
| tmp = q->value(QModbusServer::RunIndicatorStatus); |
| if (tmp.isNull() || (!tmp.isValid())) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::ServerDeviceFailure); |
| } |
| data.append(tmp.value<quint8>()); |
| |
| tmp = q->value(QModbusServer::AdditionalData); |
| if (!tmp.isNull() && tmp.isValid()) |
| data.append(tmp.toByteArray()); |
| |
| data.prepend(data.size()); // byte count |
| return QModbusResponse(request.functionCode(), data); |
| } |
| |
| QModbusResponse QModbusServerPrivate::processMaskWriteRegisterRequest(const QModbusRequest &request) |
| { |
| CHECK_SIZE_EQUALS(request); |
| quint16 address, andMask, orMask; |
| request.decodeData(&address, &andMask, &orMask); |
| |
| quint16 reg; |
| if (!q_func()->data(QModbusDataUnit::HoldingRegisters, address, ®)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataAddress); |
| } |
| |
| const quint16 result = (reg & andMask) | (orMask & (~ andMask)); |
| if (!q_func()->setData(QModbusDataUnit::HoldingRegisters, address, result)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::ServerDeviceFailure); |
| } |
| return QModbusResponse(request.functionCode(), request.data()); |
| } |
| |
| QModbusResponse QModbusServerPrivate::processReadWriteMultipleRegistersRequest( |
| const QModbusRequest &request) |
| { |
| CHECK_SIZE_LESS_THAN(request); |
| quint16 readStartAddress, readQuantity, writeStartAddress, writeQuantity; |
| quint8 byteCount; |
| request.decodeData(&readStartAddress, &readQuantity, |
| &writeStartAddress, &writeQuantity, &byteCount); |
| |
| // byte count does not match number of data bytes following or register count |
| if ((byteCount != (request.dataSize() - 9 )) || (byteCount != (writeQuantity * 2))) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataValue); |
| } |
| |
| if ((readQuantity < 0x0001) || (readQuantity > 0x007B) |
| || (writeQuantity < 0x0001) || (writeQuantity > 0x0079)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataValue); |
| } |
| |
| // According to spec, write operation is executed before the read operation |
| // Get the requested range out of the registers. |
| QModbusDataUnit writeRegisters(QModbusDataUnit::HoldingRegisters, writeStartAddress, |
| writeQuantity); |
| if (!q_func()->data(&writeRegisters)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataAddress); |
| } |
| |
| const QByteArray pduData = request.data().remove(0,9); |
| QDataStream stream(pduData); |
| |
| QVector<quint16> values; |
| quint16 tmp; |
| for (int i = 0; i < writeQuantity; i++) { |
| stream >> tmp; |
| values.append(tmp); |
| } |
| |
| writeRegisters.setValues(values); |
| |
| if (!q_func()->setData(writeRegisters)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::ServerDeviceFailure); |
| } |
| |
| // Get the requested range out of the registers. |
| QModbusDataUnit readRegisters(QModbusDataUnit::HoldingRegisters, readStartAddress, |
| readQuantity); |
| if (!q_func()->data(&readRegisters)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataAddress); |
| } |
| |
| return QModbusResponse(request.functionCode(), quint8(readQuantity * 2), |
| readRegisters.values()); |
| } |
| |
| QModbusResponse QModbusServerPrivate::processReadFifoQueueRequest(const QModbusRequest &request) |
| { |
| CHECK_SIZE_LESS_THAN(request); |
| quint16 address; |
| request.decodeData(&address); |
| |
| quint16 fifoCount; |
| if (!q_func()->data(QModbusDataUnit::HoldingRegisters, address, &fifoCount)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataAddress); |
| } |
| |
| if (fifoCount > 31u) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataValue); |
| } |
| |
| QModbusDataUnit fifoRegisters(QModbusDataUnit::HoldingRegisters, address + 1u, fifoCount); |
| if (!q_func()->data(&fifoRegisters)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataAddress); |
| } |
| |
| return QModbusResponse(request.functionCode(), quint16((fifoCount * 2) + 2u), fifoCount, |
| fifoRegisters.values()); |
| } |
| |
| QModbusResponse QModbusServerPrivate::processEncapsulatedInterfaceTransportRequest( |
| const QModbusRequest &request) |
| { |
| CHECK_SIZE_LESS_THAN(request); |
| quint8 MEIType; |
| request.decodeData(&MEIType); |
| |
| switch (MEIType) { |
| case EncapsulatedInterfaceTransport::CanOpenGeneralReference: |
| break; |
| case EncapsulatedInterfaceTransport::ReadDeviceIdentification: { |
| if (request.dataSize() != 3u) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataValue); |
| } |
| |
| const QVariant tmp = q_func()->value(QModbusServer::DeviceIdentification); |
| if (tmp.isNull() || (!tmp.isValid())) { |
| // TODO: Is this correct? |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::ServerDeviceFailure); |
| } |
| |
| QModbusDeviceIdentification objectPool = tmp.value<QModbusDeviceIdentification>(); |
| if (!objectPool.isValid()) { |
| // TODO: Is this correct? |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::ServerDeviceFailure); |
| } |
| |
| quint8 readDeviceIdCode, objectId; |
| request.decodeData(&MEIType, &readDeviceIdCode, &objectId); |
| if (!objectPool.contains(objectId)) { |
| // Individual access requires the object Id to be present, so we will always fail. |
| // For all other cases we will reevaluate object Id after we reset it as per spec. |
| objectId = QModbusDeviceIdentification::VendorNameObjectId; |
| if (readDeviceIdCode == QModbusDeviceIdentification::IndividualReadDeviceIdCode |
| || !objectPool.contains(objectId)) { |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataAddress); |
| } |
| } |
| |
| auto payload = [MEIType, readDeviceIdCode, objectId, objectPool](int lastObjectId) { |
| // TODO: Take conformity level into account. |
| QByteArray payload(6, Qt::Uninitialized); |
| payload[0] = MEIType; |
| payload[1] = readDeviceIdCode; |
| payload[2] = quint8(objectPool.conformityLevel()); |
| payload[3] = quint8(0x00); // no more follows |
| payload[4] = quint8(0x00); // next object id |
| payload[5] = quint8(0x00); // number of objects |
| |
| const QList<int> objectIds = objectPool.objectIds(); |
| for (int id : objectIds) { |
| if (id < objectId) |
| continue; |
| if (id > lastObjectId) |
| break; |
| const QByteArray object = objectPool.value(id); |
| QByteArray objectData(2, Qt::Uninitialized); |
| objectData[0] = id; |
| objectData[1] = quint8(object.size()); |
| objectData += object; |
| if (payload.size() + objectData.size() > 253) { |
| payload[3] = char(0xff); // more follows |
| payload[4] = id; // next object id |
| break; |
| } |
| payload.append(objectData); |
| payload[5] = payload[5] + 1u; // number of objects |
| } |
| return payload; |
| }; |
| |
| switch (readDeviceIdCode) { |
| case QModbusDeviceIdentification::BasicReadDeviceIdCode: |
| // TODO: How to handle a valid Id <> VendorName ... MajorMinorRevision |
| return QModbusResponse(request.functionCode(), |
| payload(QModbusDeviceIdentification::MajorMinorRevisionObjectId)); |
| case QModbusDeviceIdentification::RegularReadDeviceIdCode: |
| // TODO: How to handle a valid Id <> VendorUrl ... UserApplicationName |
| return QModbusResponse(request.functionCode(), |
| payload(QModbusDeviceIdentification::UserApplicationNameObjectId)); |
| case QModbusDeviceIdentification::ExtendedReadDeviceIdCode: |
| // TODO: How to handle a valid Id < ProductDependent |
| return QModbusResponse(request.functionCode(), |
| payload(QModbusDeviceIdentification::UndefinedObjectId)); |
| case QModbusDeviceIdentification::IndividualReadDeviceIdCode: { |
| // TODO: Take conformity level into account. |
| const QByteArray object = objectPool.value(objectId); |
| QByteArray header(8, Qt::Uninitialized); |
| header[0] = MEIType; |
| header[1] = readDeviceIdCode; |
| header[2] = quint8(objectPool.conformityLevel()); |
| header[3] = quint8(0x00); // no more follows |
| header[4] = quint8(0x00); // next object id |
| header[5] = quint8(0x01); // number of objects |
| header[6] = objectId; |
| header[7] = quint8(object.length()); |
| return QModbusResponse(request.functionCode(), QByteArray(header + object)); |
| } |
| default: |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalDataValue); |
| } |
| } break; |
| } |
| return QModbusExceptionResponse(request.functionCode(), |
| QModbusExceptionResponse::IllegalFunction); |
| } |
| |
| void QModbusServerPrivate::storeModbusCommEvent(const QModbusCommEvent &eventByte) |
| { |
| // Inserts an event byte at the start of the event log. If the event log |
| // is already full, the byte at the end of the log will be removed. The |
| // event log size is 64 bytes, starting at index 0. |
| m_commEventLog.push_front(eventByte); |
| if (m_commEventLog.size() > 64) |
| m_commEventLog.pop_back(); |
| } |
| |
| #undef CHECK_SIZE_EQUALS |
| #undef CHECK_SIZE_LESS_THAN |
| |
| QT_END_NAMESPACE |