1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtSerialBus module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include <QtSerialBus/qmodbusclient.h>
38#include <private/qmodbusclient_p.h>
39#include <private/qmodbus_symbols_p.h>
40
41#include <QtTest/QtTest>
42
43class TestClient : public QModbusClient
44{
45 Q_OBJECT
46 class TestClientPrivate : public QModbusClientPrivate
47 {
48 Q_DECLARE_PUBLIC(TestClient)
49
50 public:
51 bool isOpen() const override { return m_open; }
52
53 private:
54 bool m_open = false;
55 };
56
57public:
58 TestClient()
59 : QModbusClient(*new TestClientPrivate)
60 {}
61 bool open() override {
62 d_func()->m_open = true;
63 setState(QModbusDevice::ConnectedState);
64 return true;
65 }
66 void close() override {
67 d_func()->m_open = true;
68 setState(QModbusDevice::UnconnectedState);
69 }
70 bool processResponse(const QModbusResponse &response, QModbusDataUnit *data) override
71 {
72 return QModbusClient::processResponse(response, data);
73 }
74 Q_DECLARE_PRIVATE(TestClient)
75};
76
77class tst_QModbusClient : public QObject
78{
79 Q_OBJECT
80
81private slots:
82 void testTimeout()
83 {
84 TestClient client;
85 QCOMPARE(client.timeout(), 1000); //default value test
86
87 QSignalSpy spy(&client, SIGNAL(timeoutChanged(int)));
88 client.setTimeout(50);
89 QCOMPARE(client.timeout(), 50);
90 QCOMPARE(spy.isEmpty(), false); // we expect the signal
91
92 spy.clear();
93 client.setTimeout(9); // everything below 10 fails
94 QVERIFY(client.timeout() >= 10);
95 QCOMPARE(spy.isEmpty(), true); // and the signal should not fire
96 }
97
98 void testNumberOfRetries()
99 {
100 TestClient client;
101 QCOMPARE(client.numberOfRetries(), 3);
102
103 client.setNumberOfRetries(-1); // ignore everything below 0
104 QCOMPARE(client.numberOfRetries(), 3);
105
106 client.setNumberOfRetries(1);
107 QCOMPARE(client.numberOfRetries(), 1);
108 }
109
110 void testProcessReadWriteSingleMultipleCoilsResponse()
111 {
112 TestClient client;
113
114 QModbusDataUnit unit(QModbusDataUnit::Coils, 100, 24);
115 QModbusResponse response = QModbusResponse(QModbusResponse::ReadCoils,
116 QByteArray::fromHex(hexEncoded: "03cd6b05"));
117 QCOMPARE(client.processResponse(response, &unit), true);
118
119 QCOMPARE(unit.isValid(), true);
120 QCOMPARE(unit.valueCount(), 24u);
121 QCOMPARE(unit.startAddress(), 100);
122 QCOMPARE(unit.values(),
123 QVector<quint16>({ 1,0,1,1,0,0,1,1, 1,1,0,1,0,1,1,0, 1,0,1,0,0,0,0,0 }));
124 QCOMPARE(unit.registerType(), QModbusDataUnit::Coils);
125
126 unit = QModbusDataUnit();
127 response = QModbusResponse(QModbusResponse::WriteSingleCoil,
128 QByteArray::fromHex(hexEncoded: "00acff00"));
129 QCOMPARE(client.processResponse(response, &unit), true);
130
131 QCOMPARE(unit.isValid(), true);
132 QCOMPARE(unit.valueCount(), 1u);
133 QCOMPARE(unit.startAddress(), 172);
134 QCOMPARE(unit.values(), QVector<quint16>() << Coil::On);
135 QCOMPARE(unit.registerType(), QModbusDataUnit::Coils);
136
137 unit = QModbusDataUnit();
138 response = QModbusResponse(QModbusResponse::WriteMultipleCoils,
139 QByteArray::fromHex(hexEncoded: "0013000a"));
140 QCOMPARE(client.processResponse(response, &unit), true);
141
142 QCOMPARE(unit.isValid(), true);
143 QCOMPARE(unit.valueCount(), 10u);
144 QCOMPARE(unit.startAddress(), 19);
145 QCOMPARE(unit.values(), QVector<quint16>());
146 QCOMPARE(unit.registerType(), QModbusDataUnit::Coils);
147 }
148
149 void testProcessReadDiscreteInputsResponse()
150 {
151 TestClient client;
152
153 QModbusDataUnit unit(QModbusDataUnit::DiscreteInputs, 100, 24);
154 QModbusResponse response = QModbusResponse(QModbusResponse::ReadDiscreteInputs,
155 QByteArray::fromHex(hexEncoded: "03cd6b05"));
156 QCOMPARE(client.processResponse(response, &unit), true);
157
158 QCOMPARE(unit.isValid(), true);
159 QCOMPARE(unit.valueCount(), 24u);
160 QCOMPARE(unit.startAddress(), 100);
161 QCOMPARE(unit.values(),
162 QVector<quint16>({ 1,0,1,1,0,0,1,1, 1,1,0,1,0,1,1,0, 1,0,1,0,0,0,0,0 }));
163 QCOMPARE(unit.registerType(), QModbusDataUnit::DiscreteInputs);
164
165 response.setFunctionCode(QModbusPdu::FunctionCode(0x82));
166 QCOMPARE(client.processResponse(response, &unit), false);
167
168 response.setFunctionCode(QModbusResponse::ReadDiscreteInputs);
169 response.setData(QByteArray::fromHex(hexEncoded: "05"));
170 QCOMPARE(client.processResponse(response, &unit), false);
171
172 response.setData(QByteArray::fromHex(hexEncoded: "03cd6b"));
173 QCOMPARE(client.processResponse(response, &unit), false);
174
175 response.setData(QByteArray::fromHex(hexEncoded: "03cd6b0517"));
176 QCOMPARE(client.processResponse(response, &unit), false);
177 }
178
179 void testProcessReadHoldingRegistersResponse()
180 {
181 TestClient client;
182
183 QModbusDataUnit unit;
184 unit.setStartAddress(100);
185 QModbusResponse response = QModbusResponse(QModbusResponse::ReadHoldingRegisters,
186 QByteArray::fromHex(hexEncoded: "04cd6b057f"));
187 QCOMPARE(client.processResponse(response, &unit), true);
188
189 QCOMPARE(unit.isValid(), true);
190 QCOMPARE(unit.valueCount(), 2u);
191 QCOMPARE(unit.startAddress(), 100);
192 QCOMPARE(unit.values(), QVector<quint16>({ 52587, 1407 }));
193 QCOMPARE(unit.registerType(), QModbusDataUnit::HoldingRegisters);
194
195 response.setFunctionCode(QModbusPdu::FunctionCode(0x83));
196 QCOMPARE(client.processResponse(response, &unit), false);
197
198 response.setFunctionCode(QModbusResponse::ReadHoldingRegisters);
199 response.setData(QByteArray::fromHex(hexEncoded: "05"));
200 QCOMPARE(client.processResponse(response, &unit), false);
201
202 response.setData(QByteArray::fromHex(hexEncoded: "04cd6b"));
203 QCOMPARE(client.processResponse(response, &unit), false);
204
205 response.setData(QByteArray::fromHex(hexEncoded: "04cd6b051755"));
206 QCOMPARE(client.processResponse(response, &unit), false);
207 }
208
209 void testProcessReadInputRegistersResponse()
210 {
211 TestClient client;
212
213 QModbusDataUnit unit;
214 unit.setStartAddress(100);
215 QModbusResponse response = QModbusResponse(QModbusResponse::ReadInputRegisters,
216 QByteArray::fromHex(hexEncoded: "04cd6b057f"));
217 QCOMPARE(client.processResponse(response, &unit), true);
218
219 QCOMPARE(unit.isValid(), true);
220 QCOMPARE(unit.valueCount(), 2u);
221 QCOMPARE(unit.startAddress(), 100);
222 QCOMPARE(unit.values(), QVector<quint16>({ 52587, 1407 }));
223 QCOMPARE(unit.registerType(), QModbusDataUnit::InputRegisters);
224
225 response.setFunctionCode(QModbusPdu::FunctionCode(0x84));
226 QCOMPARE(client.processResponse(response, &unit), false);
227
228 response.setFunctionCode(QModbusResponse::ReadInputRegisters);
229 response.setData(QByteArray::fromHex(hexEncoded: "05"));
230 QCOMPARE(client.processResponse(response, &unit), false);
231
232 response.setData(QByteArray::fromHex(hexEncoded: "04cd6b"));
233 QCOMPARE(client.processResponse(response, &unit), false);
234
235 response.setData(QByteArray::fromHex(hexEncoded: "04cd6b051755"));
236 QCOMPARE(client.processResponse(response, &unit), false);
237 }
238
239 void testProcessWriteSingleRegisterResponse()
240 {
241 TestClient client;
242
243 QModbusDataUnit unit;
244 QModbusResponse response = QModbusResponse(QModbusResponse::WriteSingleRegister,
245 QByteArray::fromHex(hexEncoded: "04cd6b05"));
246 QCOMPARE(client.processResponse(response, &unit), true);
247
248 QCOMPARE(unit.isValid(), true);
249 QCOMPARE(unit.valueCount(), 1u);
250 QCOMPARE(unit.startAddress(), 1229);
251 QCOMPARE(unit.values(), QVector<quint16>(1, 27397u));
252 QCOMPARE(unit.registerType(), QModbusDataUnit::HoldingRegisters);
253
254 response.setFunctionCode(QModbusPdu::FunctionCode(0x86));
255 QCOMPARE(client.processResponse(response, &unit), false);
256
257 response.setFunctionCode(QModbusResponse::WriteSingleRegister);
258 response.setData(QByteArray::fromHex(hexEncoded: "05"));
259 QCOMPARE(client.processResponse(response, &unit), false);
260
261 response.setData(QByteArray::fromHex(hexEncoded: "04cd6b051755"));
262 QCOMPARE(client.processResponse(response, &unit), false);
263 }
264
265 void testProcessWriteMultipleRegistersResponse()
266 {
267 TestClient client;
268
269 QModbusDataUnit unit;
270 QModbusResponse response = QModbusResponse(QModbusResponse::WriteMultipleRegisters,
271 QByteArray::fromHex(hexEncoded: "03cd007b"));
272 QCOMPARE(client.processResponse(response, &unit), true);
273
274 QCOMPARE(unit.isValid(), true);
275 QCOMPARE(unit.valueCount(), 123u);
276 QCOMPARE(unit.startAddress(), 973);
277 QCOMPARE(unit.registerType(), QModbusDataUnit::HoldingRegisters);
278
279 response.setFunctionCode(QModbusPdu::FunctionCode(0x90));
280 QCOMPARE(client.processResponse(response, &unit), false);
281
282 response.setFunctionCode(QModbusResponse::WriteMultipleRegisters);
283 response.setData(QByteArray::fromHex(hexEncoded: "05"));
284 QCOMPARE(client.processResponse(response, &unit), false);
285
286 response.setData(QByteArray::fromHex(hexEncoded: "03cd6b"));
287 QCOMPARE(client.processResponse(response, &unit), false);
288
289 response.setData(QByteArray::fromHex(hexEncoded: "03cd6b0517"));
290 QCOMPARE(client.processResponse(response, &unit), false);
291
292 response.setData(QByteArray::fromHex(hexEncoded: "03cd0000"));
293 QCOMPARE(client.processResponse(response, &unit), false);
294
295 response.setData(QByteArray::fromHex(hexEncoded: "03cd007c"));
296 QCOMPARE(client.processResponse(response, &unit), false);
297 }
298
299 void testProcessReadWriteMultipleRegistersResponse()
300 {
301 TestClient client;
302
303 QModbusDataUnit unit;
304 unit.setStartAddress(100);
305 QModbusResponse response = QModbusResponse(QModbusResponse::ReadWriteMultipleRegisters,
306 QByteArray::fromHex(hexEncoded: "04cd6b057f"));
307 QCOMPARE(client.processResponse(response, &unit), true);
308
309 QCOMPARE(unit.isValid(), true);
310 QCOMPARE(unit.valueCount(), 2u);
311 QCOMPARE(unit.values(), QVector<quint16>({ 52587, 1407 }));
312 QCOMPARE(unit.registerType(), QModbusDataUnit::HoldingRegisters);
313
314 response.setFunctionCode(QModbusPdu::FunctionCode(0x97));
315 QCOMPARE(client.processResponse(response, &unit), false);
316
317 response.setFunctionCode(QModbusResponse::ReadWriteMultipleRegisters);
318 response.setData(QByteArray::fromHex(hexEncoded: "05"));
319 QCOMPARE(client.processResponse(response, &unit), false);
320
321 response.setData(QByteArray::fromHex(hexEncoded: "04cd6b"));
322 QCOMPARE(client.processResponse(response, &unit), false);
323
324 response.setData(QByteArray::fromHex(hexEncoded: "04cd6b051755"));
325 QCOMPARE(client.processResponse(response, &unit), false);
326 }
327
328 void testPrivateCreateReadRequest_data()
329 {
330 QTest::addColumn<QModbusDataUnit::RegisterType>(name: "rc");
331 QTest::addColumn<int>(name: "address");
332 QTest::addColumn<int>(name: "count");
333 QTest::addColumn<QModbusPdu::FunctionCode>(name: "fc");
334 QTest::addColumn<QByteArray>(name: "data");
335 QTest::addColumn<bool>(name: "isValid");
336
337 QTest::newRow(dataTag: "QModbusDataUnit::Invalid") << QModbusDataUnit::Invalid << 19 << 19
338 << QModbusRequest::Invalid << QByteArray() << false;
339 QTest::newRow(dataTag: "QModbusDataUnit::Coils") << QModbusDataUnit::Coils << 19 << 19
340 << QModbusRequest::ReadCoils << QByteArray::fromHex(hexEncoded: "00130013") << true;
341 QTest::newRow(dataTag: "QModbusDataUnit::DiscreteInputs") << QModbusDataUnit::DiscreteInputs << 19
342 << 19 << QModbusRequest::ReadDiscreteInputs << QByteArray::fromHex(hexEncoded: "00130013") << true;
343 QTest::newRow(dataTag: "QModbusDataUnit::InputRegisters") << QModbusDataUnit::InputRegisters << 19
344 << 19 << QModbusRequest::ReadInputRegisters << QByteArray::fromHex(hexEncoded: "00130013") << true;
345 QTest::newRow(dataTag: "QModbusDataUnit::HoldingRegisters") << QModbusDataUnit::HoldingRegisters
346 << 19 << 19 << QModbusRequest::ReadHoldingRegisters << QByteArray::fromHex(hexEncoded: "00130013")
347 << true;
348 }
349
350 void testPrivateCreateReadRequest()
351 {
352 QFETCH(QModbusDataUnit::RegisterType, rc);
353 QFETCH(int, address);
354 QFETCH(int, count);
355
356 TestClient client;
357 QModbusDataUnit read(rc, address, count);
358 QModbusRequest request = client.d_func()->createReadRequest(data: read);
359 QTEST(request.functionCode(), "fc");
360 QTEST(request.data(), "data");
361 QTEST(request.isValid(), "isValid");
362 }
363
364 void testPrivateCreateWriteRequest_data()
365 {
366 QTest::addColumn<QModbusDataUnit::RegisterType>(name: "rc");
367 QTest::addColumn<int>(name: "address");
368 QTest::addColumn<QVector<quint16>>(name: "values");
369 QTest::addColumn<QModbusPdu::FunctionCode>(name: "fc");
370 QTest::addColumn<QByteArray>(name: "data");
371 QTest::addColumn<bool>(name: "isValid");
372
373 QTest::newRow(dataTag: "QModbusDataUnit::Invalid") << QModbusDataUnit::Invalid << 19
374 << (QVector<quint16>() << 1) << QModbusRequest::Invalid << QByteArray() << false;
375 QTest::newRow(dataTag: "QModbusDataUnit::DiscreteInputs") << QModbusDataUnit::DiscreteInputs << 19
376 << (QVector<quint16>() << 1) << QModbusRequest::Invalid << QByteArray() << false;
377 QTest::newRow(dataTag: "QModbusDataUnit::InputRegisters") << QModbusDataUnit::InputRegisters << 19
378 << (QVector<quint16>() << 1) << QModbusRequest::Invalid << QByteArray() << false;
379
380 QTest::newRow(dataTag: "QModbusDataUnit::Coils{Single}") << QModbusDataUnit::Coils << 172
381 << (QVector<quint16>() << 1) << QModbusRequest::WriteSingleCoil
382 << QByteArray::fromHex(hexEncoded: "00acff00") << true;
383 QTest::newRow(dataTag: "QModbusDataUnit::Coils{Multiple}") << QModbusDataUnit::Coils << 19
384 << (QVector<quint16>({ 1,0,1,1,0,0,1,1, 1,0 /* 6 times padding */ }))
385 << QModbusRequest::WriteMultipleCoils << QByteArray::fromHex(hexEncoded: "0013000a02cd01")
386 << true;
387 QTest::newRow(dataTag: "QModbusDataUnit::HoldingRegisters{Single}")
388 << QModbusDataUnit::HoldingRegisters << 1229 << (QVector<quint16>() << 27397u)
389 << QModbusRequest::WriteSingleRegister << QByteArray::fromHex(hexEncoded: "04cd6b05") << true;
390 QTest::newRow(dataTag: "QModbusDataUnit::HoldingRegisters{Multiple}")
391 << QModbusDataUnit::HoldingRegisters << 1 << (QVector<quint16>() << 27397u << 27397u)
392 << QModbusRequest::WriteMultipleRegisters << QByteArray::fromHex(hexEncoded: "00010002046b056b05")
393 << true;
394 }
395
396 void testPrivateCreateWriteRequest()
397 {
398 QFETCH(QModbusDataUnit::RegisterType, rc);
399 QFETCH(int, address);
400 QFETCH(QVector<quint16>, values);
401
402 TestClient client;
403 QModbusDataUnit write(rc, address, values);
404 QModbusRequest request = client.d_func()->createWriteRequest(data: write);
405 QTEST(request.functionCode(), "fc");
406 QTEST(request.data(), "data");
407 QTEST(request.isValid(), "isValid");
408 }
409
410 void testPrivateCreateRWRequest_data()
411 {
412 QTest::addColumn<QModbusDataUnit::RegisterType>(name: "rc");
413 QTest::addColumn<int>(name: "address");
414 QTest::addColumn<QVector<quint16>>(name: "values");
415 QTest::addColumn<QModbusPdu::FunctionCode>(name: "fc");
416 QTest::addColumn<QByteArray>(name: "data");
417 QTest::addColumn<bool>(name: "isValid");
418
419 QTest::newRow(dataTag: "QModbusDataUnit::Invalid") << QModbusDataUnit::Invalid << 19
420 << (QVector<quint16>() << 1) << QModbusRequest::Invalid << QByteArray() << false;
421 QTest::newRow(dataTag: "QModbusDataUnit::Coils") << QModbusDataUnit::Invalid << 172
422 << (QVector<quint16>() << 1) << QModbusRequest::Invalid << QByteArray() << false;
423 QTest::newRow(dataTag: "QModbusDataUnit::DiscreteInputs") << QModbusDataUnit::DiscreteInputs << 19
424 << (QVector<quint16>() << 1) << QModbusRequest::Invalid << QByteArray() << false;
425 QTest::newRow(dataTag: "QModbusDataUnit::InputRegisters") << QModbusDataUnit::InputRegisters << 19
426 << (QVector<quint16>() << 1) << QModbusRequest::Invalid << QByteArray() << false;
427 QTest::newRow(dataTag: "QModbusDataUnit::HoldingRegisters{Read|Write}")
428 << QModbusDataUnit::HoldingRegisters << 1 << (QVector<quint16>() << 27397u << 27397u)
429 << QModbusRequest::ReadWriteMultipleRegisters
430 << QByteArray::fromHex(hexEncoded: "0001000200010002046b056b05") << true;
431 }
432
433 void testPrivateCreateRWRequest()
434 {
435 QFETCH(QModbusDataUnit::RegisterType, rc);
436 QFETCH(int, address);
437 QFETCH(QVector<quint16>, values);
438
439 TestClient client;
440 QModbusDataUnit read(rc, address, values.count());
441 QModbusDataUnit write(rc, address, values);
442 QModbusRequest request = client.d_func()->createRWRequest(read, write);
443 QTEST(request.functionCode(), "fc");
444 QTEST(request.data(), "data");
445 QTEST(request.isValid(), "isValid");
446 }
447
448 void testPrivateSendRequest()
449 {
450 TestClient client;
451 QModbusDataUnit unit;
452 QModbusReply *reply = nullptr;
453
454 QTest::ignoreMessage(type: QtWarningMsg, message: "(Client) Device is not connected");
455 QCOMPARE(client.d_func()->sendRequest(QModbusRequest(), 1, &unit), reply);
456 QCOMPARE(client.error(), QModbusDevice::ConnectionError);
457 QCOMPARE(client.errorString(), QString("Device not connected."));
458
459 QTest::ignoreMessage(type: QtWarningMsg, message: "(Client) Device is not connected");
460 QCOMPARE(client.d_func()->sendRequest(QModbusRequest(), 1, nullptr), reply);
461 QCOMPARE(client.error(), QModbusDevice::ConnectionError);
462 QCOMPARE(client.errorString(), QString("Device not connected."));
463
464 QCOMPARE(client.connectDevice(), true);
465
466 QTest::ignoreMessage(type: QtWarningMsg, message: "(Client) Refuse to send invalid request.");
467 QCOMPARE(client.d_func()->sendRequest(QModbusRequest(), 1, &unit), reply);
468 QCOMPARE(client.error(), QModbusDevice::ProtocolError);
469 QCOMPARE(client.errorString(), QString("Invalid Modbus request."));
470
471 QTest::ignoreMessage(type: QtWarningMsg, message: "(Client) Refuse to send invalid request.");
472 QCOMPARE(client.d_func()->sendRequest(QModbusRequest(), 1, nullptr), reply);
473 QCOMPARE(client.error(), QModbusDevice::ProtocolError);
474 QCOMPARE(client.errorString(), QString("Invalid Modbus request."));
475
476 QModbusRequest request(QModbusRequest::Diagnostics);
477 // The default implementation is empty and returns a null pointer.
478 QCOMPARE(client.d_func()->sendRequest(request, 1, &unit), reply);
479 QCOMPARE(client.d_func()->sendRequest(request, 1, nullptr), reply);
480 }
481};
482
483QTEST_MAIN(tst_QModbusClient)
484
485#include "tst_qmodbusclient.moc"
486

source code of qtserialbus/tests/auto/qmodbusclient/tst_qmodbusclient.cpp