| /**************************************************************************** |
| ** |
| ** 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 <QtSerialBus/qmodbusserver.h> |
| #if QT_CONFIG(modbus_serialport) |
| #include <QtSerialBus/qmodbusrtuserialslave.h> |
| #endif |
| #include <QtSerialBus/qmodbustcpserver.h> |
| #include <QtSerialBus/qmodbusdeviceidentification.h> |
| |
| #include <QtCore/qdebug.h> |
| #include <QtTest/QtTest> |
| |
| class TestServer : public QModbusServer |
| { |
| public: |
| TestServer() { |
| qRegisterMetaType<QModbusDataUnit::RegisterType>(); |
| } |
| |
| bool open() override { |
| setState(QModbusDevice::ConnectedState); |
| return true; |
| } |
| void close() override { |
| setState(QModbusDevice::UnconnectedState); |
| } |
| QModbusResponse processRequest(const QModbusPdu &request) override |
| { |
| return QModbusServer::processRequest(request); |
| } |
| }; |
| |
| #define MAP_RANGE 500 |
| static QString s_msg; |
| static void myMessageHandler(QtMsgType, const QMessageLogContext &, const QString &msg) |
| { |
| s_msg = msg; |
| } |
| |
| class tst_QModbusServer : public QObject |
| { |
| Q_OBJECT |
| |
| private: |
| TestServer server; |
| |
| private slots: |
| void init() |
| { |
| QModbusDataUnitMap map; |
| map.insert(QModbusDataUnit::DiscreteInputs, { QModbusDataUnit::DiscreteInputs, 0, MAP_RANGE }); |
| map.insert(QModbusDataUnit::Coils, { QModbusDataUnit::Coils, 0, MAP_RANGE }); |
| map.insert(QModbusDataUnit::InputRegisters, { QModbusDataUnit::InputRegisters, 0, MAP_RANGE }); |
| map.insert(QModbusDataUnit::HoldingRegisters, { QModbusDataUnit::HoldingRegisters, 0, MAP_RANGE }); |
| server.setMap(map); |
| } |
| |
| void testServerAddress() |
| { |
| QCOMPARE(QModbusTcpServer().serverAddress(), 0xff); |
| #if QT_CONFIG(modbus_serialport) |
| QCOMPARE(QModbusRtuSerialSlave().serverAddress(), 1); |
| #endif |
| } |
| |
| void testProcessRequestReadWriteSingleMultipleCoils() |
| { |
| // request write Coil 173, address: 0x00ac -> 172, value: 0xff00 -> ON |
| QModbusRequest request = QModbusRequest(QModbusRequest::WriteSingleCoil, |
| QByteArray::fromHex("00acff00")); |
| QModbusResponse response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("00acff00")); |
| |
| // request read Coil 173, address: 0x00ac -> 172, count: 0x0001 -> 1 |
| request = QModbusRequest(QModbusRequest::ReadCoils, QByteArray::fromHex("00ac0001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, byte count: 0x01 -> 1, status: 0x01 -> 0000 0001 |
| QCOMPARE(response.data(), QByteArray::fromHex("0101")); |
| |
| // request write 10 coils starting at coil 20, address: 0x0013 -> 19, count: 0x000a -> 10, |
| // payload bytes: 0x02 -> 2, values: 0xcd -> 1100 1101, 0x01 -> 0000 0001 |
| request = QModbusRequest(QModbusRequest::WriteMultipleCoils, |
| QByteArray::fromHex("0013000a02cd01")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request's first 4 bytes |
| QCOMPARE(response.data(), QByteArray::fromHex("0013000a")); |
| |
| // request read 10 coils starting at coil 20, address: 0x0013 -> 19, count: 0x000a -> 10 |
| request = QModbusRequest(QModbusRequest::ReadCoils, QByteArray::fromHex("0013000a")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, byte count: 0x02 -> 1, status: 0xcd -> 1100 1101, 0x01 -> 0000 0001 |
| QCOMPARE(response.data(), QByteArray::fromHex("02cd01")); |
| |
| // request write 19 coils starting at coil 20, address: 0x0013 -> 19, count: 0x0013 -> 19, |
| // payload bytes: 0x03 -> 3, values: 0xcd -> 1100 1101, 0x6b -> 0110 1011, 0x05 -> 0000 0101 |
| request = QModbusRequest(QModbusRequest::WriteMultipleCoils, |
| QByteArray::fromHex("0013001303cd6b05")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request's first 4 bytes |
| QCOMPARE(response.data(), QByteArray::fromHex("00130013")); |
| |
| // request read 19 coils starting at coil 20, address: 0x0013 -> 19, count: 0x0013 -> 19 |
| request = QModbusRequest(QModbusRequest::ReadCoils, QByteArray::fromHex("00130013")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, byte count: 0x03 -> 3 |
| // status: 0xcd -> 1100 1101, 0x6b -> 0110 1011, 0x05 -> 0000 0101 |
| QCOMPARE(response.data(), QByteArray::fromHex("03cd6b05")); |
| |
| // request write 10 coils, starting at coil 0, address: 0x0000 -> 0, count: 0x000a -> 10 |
| // payload bytes: 0x02 -> 2, values: 0xcd -> 1100 1101, 0x02 -> 0000 0010 |
| request = QModbusRequest(QModbusRequest::WriteMultipleCoils, |
| QByteArray::fromHex("0000000a02cd02")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request's first 4 bytes |
| QCOMPARE(response.data(), QByteArray::fromHex("0000000a")); |
| |
| // request read 10 coils starting at coil 0, address: 0x0000 -> 0, count: 0x000a -> 10 |
| request = QModbusRequest(QModbusRequest::ReadCoils, QByteArray::fromHex("0000000a")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, byte count: 0x02 -> 2, status: 0xcd -> 1100 1101, 0x02 -> 0000 0020 |
| QCOMPARE(response.data(), QByteArray::fromHex("02cd02")); |
| } |
| |
| void testProcessReadDiscreteInputsRequest() |
| { |
| server.setData(QModbusDataUnit::DiscreteInputs, 172, true); |
| // request read DiscreteInput 173, address: 0x00ac -> 172, count: 0x0001 -> 1 |
| QModbusRequest request = QModbusRequest(QModbusRequest::ReadDiscreteInputs, |
| QByteArray::fromHex("00ac0001")); |
| QModbusResponse response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, byte count: 0x01 -> 1, status: 0x01 -> 0000 0001 |
| QCOMPARE(response.data(), QByteArray::fromHex("0101")); |
| |
| server.setData(QModbusDataUnit::DiscreteInputs, 19, quint16(0x0010)); |
| // request read 10 inputs starting at input 20, address: 0x0013 -> 19, count: 0x000a -> 10 |
| request = QModbusRequest(QModbusRequest::ReadDiscreteInputs, QByteArray::fromHex("0013000a")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, byte count: 0x02 -> 1, status: 0x00 -> 0000 0000, 0x10 -> 0001 0000 |
| QCOMPARE(response.data(), QByteArray::fromHex("020100")); |
| |
| // request read 10 inputs starting at input 501 |
| request = QModbusRequest(QModbusRequest::ReadDiscreteInputs, QByteArray::fromHex("01f5000a")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("02")); |
| |
| // request read 2001 inputs starting at input 0 |
| request = QModbusRequest(QModbusRequest::ReadDiscreteInputs, QByteArray::fromHex("000007d1")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // request read 0 inputs starting at input 0 |
| request = QModbusRequest(QModbusRequest::ReadDiscreteInputs, QByteArray::fromHex("00000000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| } |
| |
| void testProcessReadHoldingRegistersRequest() |
| { |
| server.setData(QModbusDataUnit::HoldingRegisters, 172, 1234u); |
| server.setData(QModbusDataUnit::HoldingRegisters, 173, 4321u); |
| |
| // request read holding registers 173, address: 0x00ac -> 172, count: 0x0001 -> 1 |
| QModbusRequest request = QModbusRequest(QModbusRequest::ReadHoldingRegisters, |
| QByteArray::fromHex("00ac0001")); |
| QModbusResponse response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, byte count: 0x02 -> 2, value: 1234u -> 04d2 |
| QCOMPARE(response.data(), QByteArray::fromHex("0204d2")); |
| |
| // request read 2 registers starting at 172 |
| request = QModbusRequest(QModbusRequest::ReadHoldingRegisters, |
| QByteArray::fromHex("00ac0002")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, byte count: 0x04 -> 4, status: 1234u = 04d2, 4321u =10e1 |
| QCOMPARE(response.data(), QByteArray::fromHex("0404d210e1")); |
| |
| // request read 10 registers starting at offset 501 |
| request = QModbusRequest(QModbusRequest::ReadHoldingRegisters, |
| QByteArray::fromHex("01f5000a")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("02")); |
| |
| // request read 126 registers starting at offset 0 |
| request = QModbusRequest(QModbusRequest::ReadHoldingRegisters, QByteArray::fromHex("0000007e")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // request read 0 registers starting at offset 0 |
| request = QModbusRequest(QModbusRequest::ReadHoldingRegisters, QByteArray::fromHex("00000000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| } |
| |
| void testProcessReadInputRegistersRequest() |
| { |
| server.setData(QModbusDataUnit::InputRegisters, 172, 1234u); |
| server.setData(QModbusDataUnit::InputRegisters, 173, 4321u); |
| |
| // request read input registers 173, address: 0x00ac -> 172, count: 0x0001 -> 1 |
| QModbusRequest request = QModbusRequest(QModbusRequest::ReadInputRegisters, |
| QByteArray::fromHex("00ac0001")); |
| QModbusResponse response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, byte count: 0x02 -> 2, value: 1234u -> 04d2 |
| QCOMPARE(response.data(), QByteArray::fromHex("0204d2")); |
| |
| // request read 2 registers starting at 172 |
| request = QModbusRequest(QModbusRequest::ReadInputRegisters, |
| QByteArray::fromHex("00ac0002")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, byte count: 0x04 -> 4, status: 1234u = 04d2, 4321u =10e1 |
| QCOMPARE(response.data(), QByteArray::fromHex("0404d210e1")); |
| |
| // request read 10 registers starting at offset 501 |
| request = QModbusRequest(QModbusRequest::ReadInputRegisters, |
| QByteArray::fromHex("01f5000a")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("02")); |
| |
| // request read 1 register at offset 0 with corrupt message (+1 byte) |
| request = QModbusRequest(QModbusRequest::ReadInputRegisters, |
| QByteArray::fromHex("0000000100")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // request read 126 registers starting at offset 0 |
| request = QModbusRequest(QModbusRequest::ReadInputRegisters, QByteArray::fromHex("0000007e")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // request read 0 registers starting at offset 0 |
| request = QModbusRequest(QModbusRequest::ReadInputRegisters, QByteArray::fromHex("00000000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| } |
| |
| void testProcessWriteSingleRegisterRequest() |
| { |
| // request write register 173, address: 0x00ac -> 172, value: 0x00ff |
| QModbusRequest request = QModbusRequest(QModbusRequest::WriteSingleRegister, |
| QByteArray::fromHex("00ac00ff")); |
| QModbusResponse response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("00ac00ff")); |
| |
| // request write register at offset 501 |
| request = QModbusRequest(QModbusRequest::WriteSingleRegister, |
| QByteArray::fromHex("01f500ff")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("02")); |
| |
| // request write register at offset 0 value only 1 byte |
| request = QModbusRequest(QModbusRequest::WriteSingleRegister, |
| QByteArray::fromHex("00007d")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| } |
| |
| void testProcessReadExceptionStatus() |
| { |
| // simulate Modicon 484 (start at coil 257 up to 264) |
| server.setValue(QModbusServer::ExceptionStatusOffset, 256u); |
| // set the exception status byte to 0000 0011 simulating two bits set |
| // request write Coil 257, address: 0x0100 -> 256, value: 0xff00 -> ON |
| QModbusRequest request = QModbusRequest(QModbusRequest::WriteSingleCoil, |
| QByteArray::fromHex("0100ff00")); |
| QModbusResponse response = server.processRequest(request); |
| // request write Coil 258, address: 0x0101 -> 257, value: 0xff00 -> ON |
| request = QModbusRequest(QModbusRequest::WriteSingleCoil, |
| QByteArray::fromHex("0101ff00")); |
| response = server.processRequest(request); |
| |
| request = QModbusRequest(QModbusRequest::ReadExceptionStatus); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // invalid request test |
| request = QModbusRequest(QModbusRequest::ReadExceptionStatus, |
| QByteArray::fromHex("007d")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| } |
| |
| void testProcessDiagnosticsRequest() |
| { |
| // subfunction 00 |
| QModbusRequest request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000000ffabcd")); |
| QModbusResponse response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("000000ffabcd")); |
| |
| //subfunction 01 |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("00010000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("00010000")); |
| |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("0001ff00")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("0001ff00")); |
| |
| // invalidate |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("0001ff01")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // subfunction 02 |
| // validate |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("00020000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("00020000")); |
| |
| // invalidate |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("00020001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("0002000001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // subfunction 03 |
| // validate |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("00030a00")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("00030a00")); |
| |
| // invalidate |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("00030a01")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("00030a0101")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // subfunction 04 |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("00040000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isValid(), false); |
| QCOMPARE(response.isException(), false); |
| QCOMPARE(response.data(), QByteArray()); |
| QCOMPARE(response.functionCode(), QModbusResponse::Invalid); |
| |
| // invalidate |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("00040001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("0004000001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // subfunction 10: counter value is 0 |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000a0000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("000a0000")); |
| |
| // invalidate |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000a0001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000a000001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // subfunction 11: counter value is 0 |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000b0000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("000b0000")); |
| |
| // invalidate |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000b0001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000b000001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // subfunction 12: counter value is 0 |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000c0000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("000c0000")); |
| |
| // invalidate |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000c0001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000c000001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // subfunction 13: counter value is 0 |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000d0000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("000d0000")); |
| |
| // invalidate |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000d0001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000d000001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // subfunction 14: counter value is 0 |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000e0000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("000e0000")); |
| |
| // invalidate |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000e0001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000e000001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // subfunction 15: counter value is 0 |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000f0000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("000f0000")); |
| |
| // invalidate |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000f0001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("000f000001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // subfunction 16: counter value is 0 |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("00100000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("00100000")); |
| |
| // invalidate |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("00100001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("0010000001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // subfunction 17: counter value is 0 |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("00110000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("00110000")); |
| |
| // invalidate |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("00110001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("0011000001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // subfunction 18: counter value is 0 |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("00120000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("00120000")); |
| |
| // invalidate |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("00120001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("0012000001")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| |
| // subfunction > 4 < 10 |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("0005ff01")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("01")); |
| |
| // subfunction > 18 |
| request = QModbusRequest(QModbusRequest::Diagnostics, |
| QByteArray::fromHex("00130000")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("01")); |
| } |
| |
| void testProcessGetCommEventCounter() |
| { |
| TestServer local; // Used later to control the correct event amount. |
| |
| QModbusRequest request = QModbusRequest(QModbusRequest::GetCommEventCounter); |
| QModbusResponse response = local.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| QCOMPARE(response.data(), QByteArray::fromHex("00000000")); |
| |
| // TODO: Add more tests once event handling is implemented. |
| |
| request = QModbusRequest(QModbusRequest::GetCommEventCounter, quint8(10)); |
| response = local.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| |
| } |
| |
| void testProcessGetCommEventLogRequest() |
| { |
| TestServer local; // Used later to control the correct event amount. |
| |
| QModbusRequest request = QModbusRequest(QModbusRequest::GetCommEventLog); |
| QModbusResponse response = local.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| QCOMPARE(response.data(), QByteArray::fromHex("06000000000000")); |
| |
| // TODO: Add more tests once event handling is implemented. |
| |
| request = QModbusRequest(QModbusRequest::GetCommEventLog, quint8(10)); |
| response = local.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| } |
| |
| void testProcessWriteMultipleRegistersRequest() |
| { |
| // request write at register 173, address: 0x00ac -> 172, value: 0x00ff 0x1234 |
| QModbusRequest request = QModbusRequest(QModbusRequest::WriteMultipleRegisters, |
| QByteArray::fromHex("00ac00020400ff1234")); |
| QModbusResponse response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("00ac0002")); |
| |
| // request write register at offset 501 |
| request = QModbusRequest(QModbusRequest::WriteMultipleRegisters, |
| QByteArray::fromHex("01f500010200ff")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("02")); |
| |
| // request write 1 register at offset 0 value only 1 byte |
| request = QModbusRequest(QModbusRequest::WriteMultipleRegisters, |
| QByteArray::fromHex("000000010200")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| } |
| |
| void testReportServerId() |
| { |
| QModbusRequest request = QModbusRequest(QModbusRequest::ReportServerId); |
| QModbusResponse response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| QCOMPARE(response.functionCode(), QModbusRequest::ReportServerId); |
| |
| const QByteArray additionalData = "Qt Modbus Server"; |
| QCOMPARE(server.value(QModbusServer::ServerIdentifier).value<quint8>(), quint8(0x0a)); |
| QCOMPARE(server.value(QModbusServer::RunIndicatorStatus).value<quint8>(), quint8(0xff)); |
| QCOMPARE(server.value(QModbusServer::AdditionalData).toByteArray(), additionalData); |
| |
| QByteArray data = QByteArray::fromHex("0aff") + additionalData; |
| data.prepend(quint8(data.size())); |
| QCOMPARE(response.data(), data); |
| |
| request = QModbusRequest(QModbusRequest::ReportServerId, data); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.exceptionCode(), QModbusPdu::IllegalDataValue); |
| |
| server.setValue(QModbusServer::ServerIdentifier, quint8(0xff)); |
| QCOMPARE(server.setValue(QModbusServer::ServerIdentifier, additionalData), false); |
| QCOMPARE(server.value(QModbusServer::ServerIdentifier).value<quint8>(), quint8(0xff)); |
| |
| server.setValue(QModbusServer::RunIndicatorStatus, quint8(0x00)); |
| QCOMPARE(server.setValue(QModbusServer::RunIndicatorStatus, quint8(0xab)), false); |
| QCOMPARE(server.value(QModbusServer::RunIndicatorStatus).value<quint8>(), quint8(0x00)); |
| |
| server.setValue(QModbusServer::AdditionalData, QByteArray("TestData")); |
| QCOMPARE(server.setValue(QModbusServer::AdditionalData, QStringList()), false); |
| QCOMPARE(server.value(QModbusServer::AdditionalData).toByteArray(), QByteArray("TestData")); |
| } |
| |
| void testMaskWriteRegister() |
| { |
| // preset register 172 with value 18 (0x0012h) |
| server.setData(QModbusDataUnit::HoldingRegisters, 172, 18u); |
| //mask request register 172 with andMask: 242 (0x00f2) |
| // orMask: 37 (0x0025) |
| // result: 23 (0x0017) |
| QModbusRequest request = QModbusRequest(QModbusRequest::MaskWriteRegister, |
| QByteArray::fromHex("00ac00f20025")); |
| QModbusResponse response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("00ac00f20025")); |
| // validate contents after masking |
| quint16 data; |
| QVERIFY(server.data(QModbusDataUnit::HoldingRegisters, 172, &data)); |
| QCOMPARE(data, quint16(23)); |
| |
| // invalidate use register 501: |
| request = QModbusRequest(QModbusRequest::MaskWriteRegister, |
| QByteArray::fromHex("01f500f20025")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("02")); |
| // invalidate with one bytes less data: |
| request = QModbusRequest(QModbusRequest::MaskWriteRegister, |
| QByteArray::fromHex("00ac00f200")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| // invalidate with one bytes more data: |
| request = QModbusRequest(QModbusRequest::MaskWriteRegister, |
| QByteArray::fromHex("00ac00f2002500")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| } |
| |
| void testProcessReadWriteMultipleRegistersRequest() |
| { |
| server.setData(QModbusDataUnit::HoldingRegisters, 172, 1234u); |
| server.setData(QModbusDataUnit::HoldingRegisters, 173, 1235u); |
| server.setData(QModbusDataUnit::HoldingRegisters, 174, 1236u); |
| |
| // request read 3 registers at register 173, address: 0x00ac -> 172, |
| // write 3 registers at register 173: 1237u, 1238u, 1239u |
| QModbusRequest request = QModbusRequest(QModbusRequest::ReadWriteMultipleRegisters, |
| QByteArray::fromHex("00ac000300ac00030604d504d604d7")); |
| QModbusResponse response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| // response, equals request |
| QCOMPARE(response.data(), QByteArray::fromHex("0604d504d604d7")); |
| |
| // request write register at offset 501 |
| request = QModbusRequest(QModbusRequest::ReadWriteMultipleRegisters, |
| QByteArray::fromHex("00ac000301f500010200ff")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("02")); |
| |
| // request write 3 registers at offset 173 value only 5 bytes |
| request = QModbusRequest(QModbusRequest::ReadWriteMultipleRegisters, |
| QByteArray::fromHex("00ac000300ac00030604d504d604")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| } |
| |
| void testProcessReadFifoQueue() |
| { |
| // prepare a fifo with two object values, pointer address is 172 with value 2 items |
| server.setData(QModbusDataUnit::HoldingRegisters, 172, 2u); |
| server.setData(QModbusDataUnit::HoldingRegisters, 173, 1235u); |
| server.setData(QModbusDataUnit::HoldingRegisters, 174, 1236u); |
| // request read fifo queue at fifo pointer address 172 |
| QModbusRequest request = QModbusRequest(QModbusRequest::ReadFifoQueue, |
| QByteArray::fromHex("00ac")); |
| QModbusResponse response = server.processRequest(request); |
| QCOMPARE(response.isException(), false); |
| QCOMPARE(response.data(), QByteArray::fromHex("0006000204d304d4")); |
| // invalidate tests |
| // invalid offset address (501) |
| request = QModbusRequest(QModbusRequest::ReadFifoQueue, |
| QByteArray::fromHex("01f5")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("02")); |
| // invalid fifo count > 31 |
| server.setData(QModbusDataUnit::HoldingRegisters, 172, 32u); |
| request = QModbusRequest(QModbusRequest::ReadFifoQueue, |
| QByteArray::fromHex("00ac")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("03")); |
| // invalid fifo data address beyond 500, fifo values 3 (500-502) |
| server.setData(QModbusDataUnit::HoldingRegisters, 499, 3u); |
| request = QModbusRequest(QModbusRequest::ReadFifoQueue, |
| QByteArray::fromHex("01f3")); |
| response = server.processRequest(request); |
| QCOMPARE(response.isException(), true); |
| QCOMPARE(response.data(), QByteArray::fromHex("02")); |
| } |
| |
| void tst_dataCalls_data() |
| { |
| QTest::addColumn<QModbusDataUnit::RegisterType>("registerType"); |
| |
| QTest::newRow("InitCoils") << QModbusDataUnit::Coils; |
| QTest::newRow("InitDiscreteInputs") << QModbusDataUnit::DiscreteInputs; |
| QTest::newRow("InitInputRegisters") << QModbusDataUnit::InputRegisters; |
| QTest::newRow("InitHoldingRegisters") << QModbusDataUnit::HoldingRegisters; |
| QTest::newRow("InitInvalid") << QModbusDataUnit::Invalid; |
| } |
| |
| void tst_dataCalls() |
| { |
| QFETCH(QModbusDataUnit::RegisterType, registerType); |
| // basic assumption, all registers have start address 0 and size 500 |
| |
| const bool validDataUnit = (registerType != QModbusDataUnit::Invalid); |
| //test initialization of 0 |
| if (validDataUnit) { |
| for (int i = 0; i < MAP_RANGE; i++) { |
| quint16 data = 123; |
| QVERIFY(server.data(registerType, i, &data)); |
| QCOMPARE(data, quint16(0)); |
| } |
| } else { |
| quint16 data = 0; |
| QVERIFY(!server.data(registerType, 0 , &data)); |
| } |
| |
| quint16 data = 0; |
| QSignalSpy writtenSpy( |
| &server, SIGNAL(dataWritten(QModbusDataUnit::RegisterType,int,int))); |
| QVERIFY(writtenSpy.isEmpty()); |
| |
| QVERIFY(!server.data(registerType, MAP_RANGE+1, &data)); // out of range |
| QCOMPARE(data, quint16(0)); |
| QVERIFY(!server.data(registerType, 1, 0)); // invalid data pointer |
| QCOMPARE(data, quint16(0)); |
| |
| QCOMPARE(server.setData(registerType, 1, 444), validDataUnit); |
| QCOMPARE(server.data(registerType, 1, &data), validDataUnit); |
| if (validDataUnit) { |
| QCOMPARE(data, quint16(444)); |
| QTRY_COMPARE(writtenSpy.count(), 1); |
| QList<QVariant> signalData = writtenSpy.at(0); |
| QCOMPARE(signalData.count(), 3); |
| QCOMPARE(signalData.at(0).value<QModbusDataUnit::RegisterType>(), registerType); |
| QCOMPARE(signalData.at(1).toInt(), 1); |
| QCOMPARE(signalData.at(2).toInt(), 1); |
| } else { |
| QCOMPARE(data, quint16(0)); |
| QTRY_VERIFY(writtenSpy.isEmpty()); |
| } |
| |
| //write 444 again but this time no dataWritten since no value change |
| data = 0; |
| writtenSpy.clear(); |
| QCOMPARE(server.setData(registerType, 1, 444), validDataUnit); |
| QCOMPARE(server.data(registerType, 1, &data), validDataUnit); |
| if (validDataUnit) |
| QCOMPARE(data, quint16(444)); |
| else |
| QCOMPARE(data, quint16(0)); |
| QTRY_VERIFY(writtenSpy.isEmpty()); // |
| |
| QVERIFY(!server.data(registerType, 1, 0)); // out of range although value set |
| QVERIFY(!server.setData(registerType, -1, 1)); |
| QVERIFY(!server.setData(registerType, MAP_RANGE+1, 1)); |
| QTRY_VERIFY(writtenSpy.isEmpty()); |
| |
| //testing server.setData(ModbusDataUnit&) |
| |
| const QVector<quint16> valueVector = { 1, 1, 1, 1, 1}; |
| const QVector<quint16> zeroVector = { 0, 0, 0, 0, 0}; |
| QModbusDataUnit rangeUnit(registerType, 7, valueVector); |
| QCOMPARE(rangeUnit.valueCount(), 5u); |
| QCOMPARE(rangeUnit.values().count(), 5); |
| QCOMPARE(rangeUnit.startAddress(), 7); |
| QVERIFY(rangeUnit.values() == valueVector); |
| QVERIFY(rangeUnit.registerType() == registerType); |
| |
| writtenSpy.clear(); |
| QVERIFY(server.setData(rangeUnit) == validDataUnit); |
| if (validDataUnit) { |
| for (int i = rangeUnit.startAddress(); |
| i < rangeUnit.startAddress() + int(rangeUnit.valueCount()); i++) { |
| quint16 readData = 0; |
| QVERIFY(server.data(registerType, i, &readData)); |
| QCOMPARE(readData, valueVector.at(i-rangeUnit.startAddress())); |
| } |
| |
| QTRY_COMPARE(writtenSpy.count(), 1); |
| QList<QVariant> signalData = writtenSpy.at(0); |
| QCOMPARE(signalData.count(), 3); |
| QCOMPARE(signalData.at(0).value<QModbusDataUnit::RegisterType>(), registerType); |
| QCOMPARE(signalData.at(1).toInt(), rangeUnit.startAddress()); |
| QCOMPARE(signalData.at(2).toUInt(), rangeUnit.valueCount()); |
| } |
| |
| // no writtenData() signal when writing same rangeUnit again |
| writtenSpy.clear(); |
| QVERIFY(server.setData(rangeUnit) == validDataUnit); |
| QTRY_VERIFY(writtenSpy.isEmpty()); |
| |
| //never fits anywhere |
| QModbusDataUnit oversizeUnit(registerType, 0, MAP_RANGE*2); |
| QCOMPARE(oversizeUnit.valueCount(), uint(MAP_RANGE*2)); |
| QCOMPARE(oversizeUnit.values().count(), MAP_RANGE*2); |
| QCOMPARE(oversizeUnit.startAddress(), 0); |
| QCOMPARE(oversizeUnit.registerType(), registerType); |
| |
| //completely outside of valid range |
| writtenSpy.clear(); |
| rangeUnit.setStartAddress(MAP_RANGE + 1 ); |
| QVERIFY(!server.setData(rangeUnit)); |
| |
| //slightly outside of valid range |
| rangeUnit.setStartAddress(MAP_RANGE - 2); |
| QVERIFY(!server.setData(rangeUnit)); |
| |
| //slightly outside of valid range in the bottom |
| rangeUnit.setStartAddress(-1); |
| QVERIFY(!server.setData(rangeUnit)); |
| |
| //input data unit doesn't fit |
| QVERIFY(!server.setData(oversizeUnit)); |
| oversizeUnit.setStartAddress(-1); |
| QVERIFY(!server.setData(oversizeUnit)); |
| oversizeUnit.setStartAddress(MAP_RANGE+1); |
| QVERIFY(!server.setData(oversizeUnit)); |
| oversizeUnit.setStartAddress(MAP_RANGE-2); |
| QVERIFY(!server.setData(oversizeUnit)); |
| QTRY_VERIFY(writtenSpy.isEmpty()); |
| |
| //testing server.data(QModbusDataUnit *) |
| QModbusDataUnit requestUnit(registerType, 7, 5); |
| QCOMPARE(requestUnit.valueCount(), 5u); |
| QCOMPARE(requestUnit.values().count(), 5); |
| QCOMPARE(requestUnit.startAddress(), 7); |
| QVERIFY(requestUnit.registerType() == registerType); |
| QVERIFY(requestUnit.values() != valueVector); |
| |
| QVERIFY(server.data(&requestUnit) == validDataUnit); |
| if (validDataUnit) { |
| QVERIFY(requestUnit.values() == valueVector); |
| QCOMPARE(requestUnit.valueCount(), 5u); |
| QCOMPARE(requestUnit.values().count(), 5); |
| QCOMPARE(requestUnit.startAddress(), 7); |
| } |
| |
| requestUnit.setValues(zeroVector); |
| QVERIFY(requestUnit.values() != valueVector); |
| QVERIFY(requestUnit.values() == zeroVector); |
| |
| requestUnit.setStartAddress(MAP_RANGE + 1); |
| QVERIFY(!server.data(&requestUnit)); |
| requestUnit.setStartAddress(MAP_RANGE - 2); |
| QVERIFY(!server.data(&requestUnit)); |
| |
| // ask for entire map |
| requestUnit.setStartAddress(-1); |
| QVERIFY(server.data(&requestUnit) == validDataUnit); |
| if (validDataUnit) { |
| QCOMPARE(requestUnit.valueCount(), uint(MAP_RANGE)); |
| QCOMPARE(requestUnit.values().count(), MAP_RANGE); |
| } |
| |
| oversizeUnit.setStartAddress(0); |
| QVERIFY(!server.data(&oversizeUnit)); |
| oversizeUnit.setStartAddress(MAP_RANGE+1); |
| QVERIFY(!server.data(&oversizeUnit)); |
| oversizeUnit.setStartAddress(MAP_RANGE-2); |
| QVERIFY(!server.data(&oversizeUnit)); |
| |
| oversizeUnit.setStartAddress(-1); |
| QVERIFY(server.data(&oversizeUnit) == validDataUnit); |
| if (validDataUnit) { |
| QCOMPARE(oversizeUnit.valueCount(), uint(MAP_RANGE)); |
| QCOMPARE(oversizeUnit.values().count(), MAP_RANGE); |
| } |
| } |
| |
| void tst_dataCallsShiftedIndex() |
| { |
| TestServer overlapIndex; |
| const quint16 dataCount = 4; |
| |
| QModbusDataUnitMap map; |
| map.insert(QModbusDataUnit::HoldingRegisters, {QModbusDataUnit::HoldingRegisters, 3, dataCount}); |
| server.setMap(map); |
| QCOMPARE(map.value(QModbusDataUnit::HoldingRegisters).valueCount(), dataCount); |
| |
| QVERIFY(server.setData(QModbusDataUnit::HoldingRegisters, 3, 0xaaaa)); |
| QVERIFY(server.setData(QModbusDataUnit::HoldingRegisters, 4, 0xbbbb)); |
| QVERIFY(server.setData(QModbusDataUnit::HoldingRegisters, 5, 0xcccc)); |
| QVERIFY(server.setData(QModbusDataUnit::HoldingRegisters, 6, 0xdddd)); |
| |
| // ********** Test individual access ********** // |
| quint16 data = 0; |
| QVERIFY(server.data(QModbusDataUnit::HoldingRegisters, 3, &data)); |
| QCOMPARE(data, 0xaaaa); |
| QVERIFY(server.data(QModbusDataUnit::HoldingRegisters, 4, &data)); |
| QCOMPARE(data, 0xbbbb); |
| QVERIFY(server.data(QModbusDataUnit::HoldingRegisters, 5, &data)); |
| QCOMPARE(data, 0xcccc); |
| QVERIFY(server.data(QModbusDataUnit::HoldingRegisters, 6, &data)); |
| QCOMPARE(data, 0xdddd); |
| |
| |
| // block write at start |
| QModbusDataUnit unit(QModbusDataUnit::HoldingRegisters, 3, 3); |
| for (int i = 0; i < 3; i++) |
| unit.setValue(i, quint16(0x1111 + i)); |
| QVERIFY(server.setData(unit)); |
| |
| QModbusDataUnit results(QModbusDataUnit::HoldingRegisters, 3, 3); |
| QVERIFY(server.data(&results)); |
| QCOMPARE(results.values(), QVector<quint16>({0x1111, 0x1112, 0x1113})); |
| |
| //i block write at end |
| unit.setStartAddress(4); |
| results.setStartAddress(4); |
| unit.setValues({0x1, 0x2, 0x3}); |
| QVERIFY(server.setData(unit)); |
| QVERIFY(server.data(&results)); |
| QCOMPARE(results.values(), QVector<quint16>({0x1, 0x2, 0x3})); |
| |
| |
| unit.setStartAddress(2); // overlap in front |
| QVERIFY(!server.setData(unit)); |
| unit.setStartAddress(5); // overlap at end |
| QVERIFY(!server.setData(unit)); |
| |
| data = 0; |
| QVERIFY(!server.data(QModbusDataUnit::HoldingRegisters, 7, &data)); |
| QCOMPARE(data, 0); |
| QVERIFY(!server.data(QModbusDataUnit::HoldingRegisters, 2, &data)); |
| QCOMPARE(data, 0); |
| |
| QVERIFY(!server.setData(QModbusDataUnit::HoldingRegisters, 7, 0xabcd)); |
| QVERIFY(!server.setData(QModbusDataUnit::HoldingRegisters, 2, 0xabcd)); |
| |
| QVERIFY(!server.data(QModbusDataUnit::HoldingRegisters, 7, &data)); |
| QCOMPARE(data, 0); |
| QVERIFY(!server.data(QModbusDataUnit::HoldingRegisters, 2, &data)); |
| QCOMPARE(data, 0); |
| } |
| |
| void tst_serverAddress() |
| { |
| server.setServerAddress(56); |
| QCOMPARE(server.serverAddress(), 56); |
| server.setServerAddress(1); |
| QCOMPARE(server.serverAddress(), 1); |
| } |
| |
| void tst_diagnosticRegister() |
| { |
| server.setValue(QModbusServer::DiagnosticRegister, 56u); |
| QCOMPARE(server.value(QModbusServer::DiagnosticRegister).value<quint16>(), quint16(56)); |
| server.setValue(QModbusServer::DiagnosticRegister, 1u); |
| QCOMPARE(server.value(QModbusServer::DiagnosticRegister).value<quint16>(), quint16(1)); |
| } |
| |
| void tst_exceptionStatusOffset() |
| { |
| server.setValue(QModbusServer::ExceptionStatusOffset, 256u); |
| QCOMPARE(server.value(QModbusServer::ExceptionStatusOffset).value<quint16>(), quint16(256)); |
| server.setValue(QModbusServer::ExceptionStatusOffset, 0x0000); |
| QCOMPARE(server.value(QModbusServer::ExceptionStatusOffset).value<quint16>(), quint16(0)); |
| } |
| |
| void tst_readWriteDataInheritance() |
| { |
| class DebugHandler |
| { |
| public: |
| DebugHandler(QtMessageHandler newMessageHandler) |
| : oldMessageHandler(qInstallMessageHandler(newMessageHandler)) {} |
| ~DebugHandler() { |
| qInstallMessageHandler(oldMessageHandler); |
| } |
| private: |
| QtMessageHandler oldMessageHandler; |
| }; |
| |
| class InheritanceTestServer : public QModbusServer |
| { |
| public: |
| void close() override {} |
| bool open() override { return true; } |
| |
| bool readData(QModbusDataUnit *) const override { |
| qDebug() << "QModbusServer::data() call did end in the expected OVERRIDE."; |
| return false; |
| } |
| bool writeData(const QModbusDataUnit &) override { |
| qDebug() << "QModbusServer::setData() call did end in the expected OVERRIDE."; |
| return false; |
| } |
| }; |
| |
| InheritanceTestServer s; |
| DebugHandler mhs(myMessageHandler); |
| { |
| QModbusDataUnit unit; |
| s.data(&unit); |
| } |
| QCOMPARE(s_msg, QString("QModbusServer::data() call did end in the expected OVERRIDE.")); |
| { |
| s.data(QModbusDataUnit::Coils, 0u, nullptr); |
| } |
| QCOMPARE(s_msg, QString("QModbusServer::data() call did end in the expected OVERRIDE.")); |
| { |
| s.setData(QModbusDataUnit()); |
| } |
| QCOMPARE(s_msg, QString("QModbusServer::setData() call did end in the expected OVERRIDE.")); |
| { |
| s.setData(QModbusDataUnit::Coils, 0u, 0u); |
| } |
| QCOMPARE(s_msg, QString("QModbusServer::setData() call did end in the expected OVERRIDE.")); |
| } |
| |
| void testReadWriteDataMissingOrInvalidRegister() |
| { |
| TestServer local; |
| local.setMap({ { QModbusDataUnit::Invalid, QModbusDataUnit() }, |
| { QModbusDataUnit::Coils, QModbusDataUnit(QModbusDataUnit::Coils) }, |
| { QModbusDataUnit::DiscreteInputs, QModbusDataUnit(QModbusDataUnit::DiscreteInputs) }}); |
| |
| QModbusDataUnit invalid; |
| QCOMPARE(local.data(&invalid), false); |
| QCOMPARE(local.setData(invalid), false); |
| |
| QModbusDataUnit missing(QModbusDataUnit::HoldingRegisters); |
| QCOMPARE(local.data(&missing), false); |
| QCOMPARE(local.setData(missing), false); |
| } |
| |
| void testIllegalTcpFunctionCodes() |
| { |
| class ModbusTcpServer : public QModbusTcpServer |
| { |
| public: |
| QModbusResponse processRequest(const QModbusPdu &request) override { |
| return QModbusTcpServer::processRequest(request); |
| } |
| |
| }; |
| ModbusTcpServer local; |
| |
| QModbusRequest request(QModbusRequest::ReadExceptionStatus); |
| QCOMPARE(local.processRequest(request).exceptionCode(), QModbusPdu::IllegalFunction); |
| |
| request = QModbusRequest(QModbusRequest::Diagnostics); |
| QCOMPARE(local.processRequest(request).exceptionCode(), QModbusPdu::IllegalFunction); |
| |
| request = QModbusRequest(QModbusRequest::GetCommEventCounter); |
| QCOMPARE(local.processRequest(request).exceptionCode(), QModbusPdu::IllegalFunction); |
| |
| request = QModbusRequest(QModbusRequest::GetCommEventLog); |
| QCOMPARE(local.processRequest(request).exceptionCode(), QModbusPdu::IllegalFunction); |
| |
| request = QModbusRequest(QModbusRequest::ReportServerId); |
| QCOMPARE(local.processRequest(request).exceptionCode(), QModbusPdu::IllegalFunction); |
| } |
| |
| void testQModbusServerOptions() |
| { |
| // TODO: Add a local class implementation to test value()/setValue with a different backing |
| // store. That's not only related to AsciiInputDelimiter, rather to all enum values there. |
| |
| QCOMPARE(server.value(QModbusServer::AsciiInputDelimiter).toInt(), int('\n')); |
| QCOMPARE(server.setValue(QModbusServer::AsciiInputDelimiter, "Test"), false); |
| QCOMPARE(server.setValue(QModbusServer::AsciiInputDelimiter, '@'), true); |
| QCOMPARE(server.value(QModbusServer::AsciiInputDelimiter).toInt(), int('@')); |
| |
| QVERIFY(server.setValue(QModbusServer::AsciiInputDelimiter, 'j')); |
| QCOMPARE(server.value(QModbusServer::AsciiInputDelimiter).toInt(), int('j')); |
| QVERIFY(server.setValue(QModbusServer::AsciiInputDelimiter, 0x6a)); |
| QCOMPARE(server.value(QModbusServer::AsciiInputDelimiter).toInt(), int('j')); |
| QVERIFY(server.setValue(QModbusServer::AsciiInputDelimiter, 0x6a)); |
| QCOMPARE(server.value(QModbusServer::AsciiInputDelimiter).toInt(), int('j')); |
| QVERIFY(!server.setValue(QModbusServer::AsciiInputDelimiter, 0x100)); |
| QCOMPARE(server.value(QModbusServer::AsciiInputDelimiter).toInt(), int('j')); |
| QVERIFY(!server.setValue(QModbusServer::AsciiInputDelimiter, -1)); |
| |
| TestServer local; |
| QCOMPARE(local.value(QModbusServer::ListenOnlyMode).toBool(), false); |
| QCOMPARE(local.setValue(QModbusServer::ListenOnlyMode, "Test"), false); |
| QCOMPARE(local.setValue(QModbusServer::ListenOnlyMode, true), true); |
| QCOMPARE(local.value(QModbusServer::ListenOnlyMode).toBool(), true); |
| } |
| |
| void testClearOverrunCounterAndFlag() |
| { |
| TestServer server; |
| server.setValue(QModbusServer::DiagnosticRegister, 0xffff); |
| server.processRequest(QModbusRequest(QModbusRequest::Diagnostics, quint16(0x0014), quint16(0))); |
| QCOMPARE(server.value(QModbusServer::DiagnosticRegister).value<quint16>(), quint16(0xfffe)); |
| } |
| |
| void testProcessEncapsulatedInterfaceTransportRequest() |
| { |
| QModbusDeviceIdentification objectPool; |
| QCOMPARE(objectPool.insert(QModbusDeviceIdentification::VendorNameObjectId, |
| "Company identification"), true); |
| QCOMPARE(objectPool.isValid(), false); |
| QCOMPARE(objectPool.insert(QModbusDeviceIdentification::ProductCodeObjectId, |
| "Product code"), true); |
| QCOMPARE(objectPool.isValid(), false); |
| QCOMPARE(objectPool.insert(QModbusDeviceIdentification::MajorMinorRevisionObjectId, |
| "V2.11"), true); |
| QCOMPARE(objectPool.isValid(), true); |
| |
| QCOMPARE(server.setValue(QModbusServer::DeviceIdentification, |
| QVariant::fromValue<QModbusDeviceIdentification>(objectPool)), true); |
| |
| auto response = server |
| .processRequest(QModbusRequest(QModbusRequest::EncapsulatedInterfaceTransport, |
| QByteArray::fromHex("0e0100"))); |
| QCOMPARE(response.data(), QByteArray::fromHex("0e01010000030016") |
| + "Company identification" + QByteArray::fromHex("010c") + "Product code" |
| + QByteArray::fromHex("0205") + "V2.11"); |
| |
| QCOMPARE(objectPool.insert(QModbusDeviceIdentification::ProductCodeObjectId, QByteArray( |
| "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" |
| "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" |
| "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")), true); |
| QCOMPARE(objectPool.isValid(), true); |
| |
| QCOMPARE(server.setValue(QModbusServer::DeviceIdentification, |
| QVariant::fromValue<QModbusDeviceIdentification>(objectPool)), true); |
| |
| response = server.processRequest(QModbusRequest(QModbusPdu::EncapsulatedInterfaceTransport, |
| QByteArray::fromHex("0e0100"))); |
| QCOMPARE(response.data(), QByteArray::fromHex("0e0101ff02020016") |
| + "Company identification" + QByteArray::fromHex("01dc") + QByteArray( |
| "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" |
| "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" |
| "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")); |
| |
| response = server.processRequest(QModbusRequest(QModbusPdu::EncapsulatedInterfaceTransport, |
| QByteArray::fromHex("0e0102"))); |
| QCOMPARE(response.data(), QByteArray::fromHex("0e01010000010205") + "V2.11"); |
| } |
| }; |
| |
| QTEST_MAIN(tst_QModbusServer) |
| |
| #include "tst_qmodbusserver.moc" |