• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wirenboard / wb-mqtt-serial / 1

08 Jul 2025 01:20PM UTC coverage: 73.854% (+1.0%) from 72.836%
1

Pull #963

github

39d9bc
KraPete
Bump version
Pull Request #963: Bump version

6444 of 9057 branches covered (71.15%)

12341 of 16710 relevant lines covered (73.85%)

305.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.15
/test/modbus_test.cpp
1
#include "devices/modbus_device.h"
2
#include "fake_serial_port.h"
3
#include "modbus_common.h"
4
#include "modbus_expectations.h"
5

6
#include <wblib/control.h>
7

8
using namespace std;
9

10
class TModbusTest: public TSerialDeviceTest, public TModbusExpectations
11
{
12
    typedef shared_ptr<TModbusDevice> PModbusDevice;
13

14
protected:
15
    void SetUp();
16
    set<int> VerifyQuery(PRegister reg = PRegister());
×
17

18
    virtual TModbusDeviceConfig GetDeviceConfig() const;
19

20
    PModbusDevice ModbusDev;
21

22
    PRegister ModbusCoil0;
23
    PRegister ModbusCoil1;
24
    PRegister ModbusDiscrete;
25
    PRegister ModbusHolding;
26
    PRegister ModbusInput;
27

28
    PRegister ModbusHoldingS64;
29

30
    PRegister ModbusHoldingU64Single;
31
    PRegister ModbusHoldingU16Single;
32
    PRegister ModbusHoldingU64Multi;
33
    PRegister ModbusHoldingU16Multi;
34

35
    PRegister ModbusHoldingU16WithAddressWrite;
36
    PRegister ModbusHoldingU16WithWriteBitOffset;
37

38
    PRegister ModbusHoldingStringRead;
39
    PRegister ModbusHoldingStringWrite;
40

41
    PRegister ModbusHoldingString8Read;
42
    PRegister ModbusHoldingString8Write;
43
};
44

45
TModbusDeviceConfig TModbusTest::GetDeviceConfig() const
26✔
46
{
47
    TModbusDeviceConfig config;
26✔
48
    config.CommonConfig = std::make_shared<TDeviceConfig>("modbus", std::to_string(0x01), "modbus");
26✔
49
    config.CommonConfig->MaxReadRegisters = 10;
26✔
50
    return config;
26✔
51
}
52

53
void TModbusTest::SetUp()
24✔
54
{
55
    SelectModbusType(MODBUS_RTU);
24✔
56
    TSerialDeviceTest::SetUp();
24✔
57

58
    auto modbusRtuTraits = std::make_unique<Modbus::TModbusRTUTraits>();
48✔
59

60
    ModbusDev = std::make_shared<TModbusDevice>(std::move(modbusRtuTraits),
48✔
61
                                                GetDeviceConfig(),
48✔
62
                                                DeviceFactory.GetProtocol("modbus"));
48✔
63
    ModbusCoil0 = ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_COIL, 0, U8));
24✔
64
    ModbusCoil1 = ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_COIL, 1, U8));
24✔
65
    ModbusDiscrete = ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_DISCRETE, 20, U8));
24✔
66
    ModbusHolding = ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_HOLDING, 70, U16));
24✔
67
    ModbusInput = ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_INPUT, 40, U16));
24✔
68
    ModbusHoldingS64 = ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_HOLDING, 30, S64));
24✔
69

70
    ModbusHoldingU64Single = ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_HOLDING_SINGLE, 90, U64));
24✔
71
    ModbusHoldingU16Single = ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_HOLDING_SINGLE, 94, U16));
24✔
72
    ModbusHoldingU64Multi = ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_HOLDING_MULTI, 95, U64));
24✔
73
    ModbusHoldingU16Multi = ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_HOLDING_MULTI, 99, U16));
24✔
74

75
    TRegisterDesc regAddrDesc;
24✔
76
    regAddrDesc.Address = std::make_shared<TUint32RegisterAddress>(110);
24✔
77
    regAddrDesc.WriteAddress = std::make_shared<TUint32RegisterAddress>(115);
24✔
78

79
    ModbusHoldingU16WithAddressWrite =
80
        ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_HOLDING, regAddrDesc, RegisterFormat::U16));
24✔
81

82
    regAddrDesc.Address = std::make_shared<TUint32RegisterAddress>(111);
24✔
83
    regAddrDesc.WriteAddress = std::make_shared<TUint32RegisterAddress>(116);
24✔
84
    regAddrDesc.DataWidth = 3;
24✔
85
    regAddrDesc.DataOffset = 2;
24✔
86

87
    ModbusHoldingU16WithWriteBitOffset =
88
        ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_HOLDING, regAddrDesc, RegisterFormat::U16));
24✔
89

90
    TRegisterDesc regStringDesc;
24✔
91
    regStringDesc.Address = std::make_shared<TUint32RegisterAddress>(120);
24✔
92
    regStringDesc.DataWidth = 16 * sizeof(char) * 8;
24✔
93
    ModbusHoldingStringRead =
94
        ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_HOLDING, regStringDesc, String));
24✔
95
    ModbusHoldingStringWrite =
96
        ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_HOLDING_MULTI, regStringDesc, String));
24✔
97

98
    TRegisterDesc regString8Desc;
24✔
99
    regString8Desc.Address = std::make_shared<TUint32RegisterAddress>(142);
24✔
100
    regString8Desc.DataWidth = 8 * sizeof(char) * 8;
24✔
101
    ModbusHoldingString8Read =
102
        ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_HOLDING, regString8Desc, String8));
24✔
103
    ModbusHoldingString8Write =
104
        ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_HOLDING_MULTI, regString8Desc, String8));
24✔
105

106
    SerialPort->Open();
24✔
107
}
24✔
108

109
set<int> TModbusTest::VerifyQuery(PRegister reg)
5✔
110
{
111
    std::list<PRegisterRange> ranges;
10✔
112

113
    if (!reg) {
5✔
114
        {
115
            auto r = ModbusDev->CreateRegisterRange();
4✔
116
            r->Add(*SerialPort, ModbusCoil0, std::chrono::milliseconds::max());
2✔
117
            r->Add(*SerialPort, ModbusCoil1, std::chrono::milliseconds::max());
2✔
118
            ranges.push_back(r);
2✔
119
        }
120
        {
121
            auto r = ModbusDev->CreateRegisterRange();
4✔
122
            r->Add(*SerialPort, ModbusDiscrete, std::chrono::milliseconds::max());
2✔
123
            ranges.push_back(r);
2✔
124
        }
125
        {
126
            auto r = ModbusDev->CreateRegisterRange();
4✔
127
            r->Add(*SerialPort, ModbusHolding, std::chrono::milliseconds::max());
2✔
128
            ranges.push_back(r);
2✔
129
        }
130
        {
131
            auto r = ModbusDev->CreateRegisterRange();
4✔
132
            r->Add(*SerialPort, ModbusInput, std::chrono::milliseconds::max());
2✔
133
            ranges.push_back(r);
2✔
134
        }
135
        {
136
            auto r = ModbusDev->CreateRegisterRange();
4✔
137
            r->Add(*SerialPort, ModbusHoldingS64, std::chrono::milliseconds::max());
2✔
138
            ranges.push_back(r);
2✔
139
        }
140
    } else {
141
        auto r = ModbusDev->CreateRegisterRange();
6✔
142
        r->Add(*SerialPort, reg, std::chrono::milliseconds::max());
3✔
143
        ranges.push_back(r);
3✔
144
    }
145
    set<int> readAddresses;
10✔
146
    set<int> errorRegisters;
5✔
147
    map<int, TRegisterValue> registerValues;
10✔
148

149
    for (auto range: ranges) {
18✔
150
        ModbusDev->ReadRegisterRange(*SerialPort, range);
13✔
151
        for (auto& reg: range->RegisterList()) {
28✔
152
            auto addr = GetUint32RegisterAddress(reg->GetConfig()->GetAddress());
15✔
153
            readAddresses.insert(addr);
15✔
154
            if (reg->GetErrorState().test(TRegister::TError::ReadError)) {
15✔
155
                errorRegisters.insert(addr);
6✔
156
            } else {
157
                registerValues[addr] = reg->GetValue();
9✔
158
            }
159
        }
160
    }
161

162
    for (auto registerValue: registerValues) {
14✔
163
        auto address = registerValue.first;
9✔
164
        auto value = to_string(registerValue.second.Get<uint64_t>());
18✔
165

166
        switch (address) {
9✔
167
            case 0:
1✔
168
                EXPECT_EQ(to_string(0x0), value);
2✔
169
                break;
1✔
170
            case 1:
1✔
171
                EXPECT_EQ(to_string(0x1), value);
2✔
172
                break;
1✔
173
            case 20:
1✔
174
                EXPECT_EQ(to_string(0x1), value);
2✔
175
                break;
1✔
176
            case 30:
2✔
177
                EXPECT_EQ(to_string(0x0102030405060708), value);
4✔
178
                break;
2✔
179
            case 40:
2✔
180
                EXPECT_EQ(to_string(0x66), value);
4✔
181
                break;
2✔
182
            case 70:
2✔
183
                EXPECT_EQ(to_string(0x15), value);
4✔
184
                break;
2✔
185
            default:
×
186
                throw runtime_error("register with wrong address " + to_string(address) + " in range");
×
187
        }
188
    }
189

190
    return errorRegisters;
10✔
191
}
192

193
TEST_F(TModbusTest, WriteOnlyRegisters)
8✔
194
{
195
    auto traits = std::make_unique<Modbus::TModbusRTUTraits>();
4✔
196
    auto device =
197
        std::make_shared<TModbusDevice>(std::move(traits), GetDeviceConfig(), DeviceFactory.GetProtocol("modbus"));
4✔
198

199
    TRegisterDesc coilDesc;
2✔
200
    coilDesc.WriteAddress = std::make_shared<TUint32RegisterAddress>(0);
2✔
201

202
    auto coilConfig = TRegisterConfig::Create(Modbus::REG_COIL, coilDesc);
6✔
203
    coilConfig->AccessType = TRegisterConfig::EAccessType::WRITE_ONLY;
2✔
204

205
    EnqueueCoilWriteResponse();
2✔
206
    EXPECT_NO_THROW(ModbusDev->WriteRegister(*SerialPort, device->AddRegister(coilConfig), 0x0001));
2✔
207

208
    TRegisterDesc holdingDesc;
2✔
209
    holdingDesc.WriteAddress = std::make_shared<TUint32RegisterAddress>(70);
2✔
210

211
    auto holdingConfig = TRegisterConfig::Create(Modbus::REG_HOLDING, holdingDesc);
6✔
212
    holdingConfig->AccessType = TRegisterConfig::EAccessType::WRITE_ONLY;
2✔
213

214
    EnqueueHoldingWriteU16Response();
2✔
215
    EXPECT_NO_THROW(ModbusDev->WriteRegister(*SerialPort, device->AddRegister(holdingConfig), 0x0F41));
2✔
216

217
    SerialPort->Close();
2✔
218
}
2✔
219

220
TEST_F(TModbusTest, ReadHoldingRegiterWithWriteAddress)
8✔
221
{
222
    EnqueueHoldingReadU16ResponseWithWriteAddress();
2✔
223

224
    auto range = ModbusDev->CreateRegisterRange();
4✔
225
    range->Add(*SerialPort, ModbusHoldingU16WithAddressWrite, std::chrono::milliseconds::max());
2✔
226
    ModbusDev->ReadRegisterRange(*SerialPort, range);
2✔
227
    auto registerList = range->RegisterList();
4✔
228
    EXPECT_EQ(registerList.size(), 1);
2✔
229
    auto reg = registerList.front();
4✔
230
    EXPECT_EQ(GetUint32RegisterAddress(reg->GetConfig()->GetAddress()), 110);
2✔
231
    EXPECT_FALSE(reg->GetErrorState().test(TRegister::TError::ReadError));
2✔
232
    EXPECT_EQ(reg->GetValue(), 0x15);
2✔
233
}
2✔
234

235
TEST_F(TModbusTest, WriteHoldingRegiterWithWriteAddress)
8✔
236
{
237
    EnqueueHoldingWriteU16ResponseWithWriteAddress();
2✔
238
    EXPECT_EQ(GetUint32RegisterAddress(ModbusHoldingU16WithAddressWrite->GetConfig()->GetAddress()), 110);
2✔
239
    EXPECT_EQ(GetUint32RegisterAddress(ModbusHoldingU16WithAddressWrite->GetConfig()->GetWriteAddress()), 115);
2✔
240

241
    EXPECT_NO_THROW(ModbusDev->WriteRegister(*SerialPort, ModbusHoldingU16WithAddressWrite, 0x119C));
2✔
242
}
2✔
243

244
TEST_F(TModbusTest, ReadHoldingRegiterWithOffsetWriteOptions)
8✔
245
{
246
    EnqueueHoldingReadU16ResponseWithOffsetWriteOptions();
2✔
247

248
    auto range = ModbusDev->CreateRegisterRange();
4✔
249
    range->Add(*SerialPort, ModbusHoldingU16WithWriteBitOffset, std::chrono::milliseconds::max());
2✔
250
    ModbusDev->ReadRegisterRange(*SerialPort, range);
2✔
251
    auto registerList = range->RegisterList();
4✔
252
    EXPECT_EQ(registerList.size(), 1);
2✔
253
    auto reg = registerList.front();
4✔
254
    EXPECT_EQ(GetUint32RegisterAddress(reg->GetConfig()->GetAddress()), 111);
2✔
255
    EXPECT_FALSE(reg->GetErrorState().test(TRegister::TError::ReadError));
2✔
256
    EXPECT_EQ(reg->GetValue(), 5);
2✔
257
}
2✔
258

259
TEST_F(TModbusTest, ReadString)
8✔
260
{
261
    const std::vector<std::string> responses = {"2.4.2-rc1", "2.4.2-rc1", "2.4.2-rc1", "2.4.2-rc12345678"};
16✔
262
    EnqueueStringReadResponse(TModbusExpectations::TRAILING_ZEROS);
2✔
263
    EnqueueStringReadResponse(TModbusExpectations::ZERO_AND_TRASH);
2✔
264
    EnqueueStringReadResponse(TModbusExpectations::TRAILING_FF);
2✔
265
    EnqueueStringReadResponse(TModbusExpectations::FULL_OF_CHARS);
2✔
266

267
    for (int i = 0; i < 4; ++i) {
10✔
268
        auto range = ModbusDev->CreateRegisterRange();
16✔
269
        range->Add(*SerialPort, ModbusHoldingStringRead, std::chrono::milliseconds::max());
8✔
270
        ModbusDev->ReadRegisterRange(*SerialPort, range);
8✔
271
        auto registerList = range->RegisterList();
16✔
272
        EXPECT_EQ(registerList.size(), 1);
8✔
273
        auto reg = registerList.front();
16✔
274
        EXPECT_EQ(GetUint32RegisterAddress(reg->GetConfig()->GetAddress()), 120);
8✔
275
        EXPECT_FALSE(reg->GetErrorState().test(TRegister::TError::ReadError));
8✔
276
        EXPECT_EQ(reg->GetValue().Get<std::string>(), responses[i]);
16✔
277
    }
278

279
    SerialPort->Close();
2✔
280
}
2✔
281

282
TEST_F(TModbusTest, WriteString)
8✔
283
{
284
    EnqueueStringWriteResponse();
2✔
285
    ModbusDev->WriteRegister(*SerialPort, ModbusHoldingStringWrite, TRegisterValue("Lateralus"));
2✔
286
    SerialPort->Close();
2✔
287
}
2✔
288

289
TEST_F(TModbusTest, ReadString8)
8✔
290
{
291
    const std::vector<std::string> responses = {"2.4.2-rc1", "2.4.2-rc1", "2.4.2-rc1", "2.4.2-rc12345678"};
16✔
292
    EnqueueString8ReadResponse(TModbusExpectations::TRAILING_ZEROS);
2✔
293
    EnqueueString8ReadResponse(TModbusExpectations::ZERO_AND_TRASH);
2✔
294
    EnqueueString8ReadResponse(TModbusExpectations::TRAILING_FF);
2✔
295
    EnqueueString8ReadResponse(TModbusExpectations::FULL_OF_CHARS);
2✔
296

297
    for (int i = 0; i < 4; ++i) {
10✔
298
        auto range = ModbusDev->CreateRegisterRange();
16✔
299
        range->Add(*SerialPort, ModbusHoldingString8Read, std::chrono::milliseconds::max());
8✔
300
        ModbusDev->ReadRegisterRange(*SerialPort, range);
8✔
301
        auto registerList = range->RegisterList();
16✔
302
        EXPECT_EQ(registerList.size(), 1);
8✔
303
        auto reg = registerList.front();
16✔
304
        EXPECT_EQ(GetUint32RegisterAddress(reg->GetConfig()->GetAddress()), 142);
8✔
305
        EXPECT_FALSE(reg->GetErrorState().test(TRegister::TError::ReadError));
8✔
306
        EXPECT_EQ(reg->GetValue().Get<std::string>(), responses[i]);
16✔
307
    }
308

309
    SerialPort->Close();
2✔
310
}
2✔
311

312
TEST_F(TModbusTest, WriteString8)
8✔
313
{
314
    EnqueueString8WriteResponse();
2✔
315
    ModbusDev->WriteRegister(*SerialPort, ModbusHoldingString8Write, TRegisterValue("Lateralus"));
2✔
316
    SerialPort->Close();
2✔
317
}
2✔
318

319
TEST_F(TModbusTest, WriteHoldingRegiterWithOffsetWriteOptions)
8✔
320
{
321
    EnqueueHoldingWriteU16ResponseWithOffsetWriteOptions();
2✔
322

323
    EXPECT_NO_THROW(ModbusDev->WriteRegister(*SerialPort, ModbusHoldingU16WithWriteBitOffset, 0x119D));
2✔
324
}
2✔
325

326
TEST_F(TModbusTest, WriteOnlyHoldingRegiter)
8✔
327
{
328
    PRegister ModbusHoldingU16WriteOnly;
2✔
329

330
    TRegisterDesc regAddrDesc;
2✔
331
    regAddrDesc.WriteAddress = std::make_shared<TUint32RegisterAddress>(115);
2✔
332

333
    ModbusHoldingU16WriteOnly =
334
        ModbusDev->AddRegister(TRegisterConfig::Create(Modbus::REG_HOLDING, regAddrDesc, RegisterFormat::U16));
2✔
335
    EXPECT_TRUE(ModbusHoldingU16WriteOnly->GetConfig()->AccessType == TRegisterConfig::EAccessType::WRITE_ONLY);
2✔
336
}
2✔
337

338
TEST_F(TModbusTest, WriteOnlyHoldingRegiterNeg)
8✔
339
{
340
    TRegisterDesc regAddrDesc;
2✔
341

342
    EXPECT_THROW(TRegisterConfig::Create(Modbus::REG_HOLDING, regAddrDesc, RegisterFormat::U16),
6✔
343
                 TSerialDeviceException);
344
}
2✔
345

346
TEST_F(TModbusTest, Query)
8✔
347
{
348
    EnqueueCoilReadResponse();
2✔
349
    EnqueueDiscreteReadResponse();
2✔
350
    EnqueueHoldingReadU16Response();
2✔
351
    EnqueueInputReadU16Response();
2✔
352
    EnqueueHoldingReadS64Response();
2✔
353

354
    ASSERT_EQ(0, VerifyQuery().size()); // we don't expect any errors to occur here
2✔
355
    SerialPort->Close();
2✔
356
}
357

358
TEST_F(TModbusTest, HoldingSingleMulti)
8✔
359
{
360
    EnqueueHoldingSingleWriteU16Response();
2✔
361
    EnqueueHoldingSingleWriteU64Response();
2✔
362
    EnqueueHoldingMultiWriteU16Response();
2✔
363
    EnqueueHoldingMultiWriteU64Response();
2✔
364

365
    ModbusDev->WriteRegister(*SerialPort, ModbusHoldingU16Single, 0x0f41);
2✔
366
    ModbusDev->WriteRegister(*SerialPort, ModbusHoldingU64Single, 0x01020304050607);
2✔
367
    ModbusDev->WriteRegister(*SerialPort, ModbusHoldingU16Multi, 0x0123);
2✔
368
    ModbusDev->WriteRegister(*SerialPort, ModbusHoldingU64Multi, 0x0123456789ABCDEF);
2✔
369

370
    SerialPort->Close();
2✔
371
}
2✔
372

373
TEST_F(TModbusTest, Errors)
8✔
374
{
375
    EnqueueCoilReadResponse(1);
2✔
376
    EnqueueDiscreteReadResponse(2);
2✔
377
    EnqueueHoldingReadU16Response();
2✔
378
    EnqueueInputReadU16Response();
2✔
379
    EnqueueHoldingReadS64Response();
2✔
380

381
    set<int> expectedAddresses{0, 1, 20}; // errors in 2 coils and 1 input
2✔
382
    auto errorAddresses = VerifyQuery();
2✔
383

384
    ASSERT_EQ(expectedAddresses, errorAddresses);
2✔
385
    SerialPort->Close();
2✔
386
}
387

388
TEST_F(TModbusTest, CRCError)
8✔
389
{
390
    EnqueueInvalidCRCCoilReadResponse();
2✔
391

392
    auto r = ModbusDev->CreateRegisterRange();
4✔
393
    r->Add(*SerialPort, ModbusCoil0, std::chrono::milliseconds::max());
2✔
394
    ModbusDev->ReadRegisterRange(*SerialPort, r);
2✔
395

396
    SerialPort->Close();
2✔
397
}
2✔
398

399
TEST_F(TModbusTest, WrongResponseDataSize)
8✔
400
{
401
    EnqueueWrongDataSizeReadResponse();
2✔
402

403
    auto r = ModbusDev->CreateRegisterRange();
4✔
404
    r->Add(*SerialPort, ModbusCoil0, std::chrono::milliseconds::max());
2✔
405
    ModbusDev->ReadRegisterRange(*SerialPort, r);
2✔
406

407
    SerialPort->Close();
2✔
408
}
2✔
409

410
TEST_F(TModbusTest, WrongSlaveId)
8✔
411
{
412
    EnqueueWrongSlaveIdCoilReadResponse();
2✔
413

414
    EXPECT_EQ(1, VerifyQuery({ModbusCoil0}).size());
2✔
415

416
    SerialPort->Close();
2✔
417
}
2✔
418

419
TEST_F(TModbusTest, WrongFunctionCode)
8✔
420
{
421
    EnqueueWrongFunctionCodeCoilReadResponse();
2✔
422

423
    EXPECT_EQ(1, VerifyQuery({ModbusCoil0}).size());
2✔
424

425
    SerialPort->Close();
2✔
426
}
2✔
427

428
TEST_F(TModbusTest, WrongFunctionCodeWithException)
8✔
429
{
430
    EnqueueWrongFunctionCodeCoilReadResponse(0x2);
2✔
431

432
    EXPECT_EQ(1, VerifyQuery({ModbusCoil0}).size());
2✔
433

434
    SerialPort->Close();
2✔
435
}
2✔
436

437
TEST_F(TModbusTest, WrongSlaveIdWrite)
8✔
438
{
439
    EnqueueWrongSlaveIdCoilWriteResponse();
2✔
440

441
    try {
442
        ModbusDev->WriteRegister(*SerialPort, ModbusCoil0, 0xFF);
4✔
443
        EXPECT_FALSE(true);
×
444
    } catch (const TSerialDeviceTransientErrorException& e) {
4✔
445
        EXPECT_EQ(string("Serial protocol error: request and response slave id mismatch"), e.what());
2✔
446
    }
447

448
    SerialPort->Close();
2✔
449
}
2✔
450

451
TEST_F(TModbusTest, WrongFunctionCodeWrite)
8✔
452
{
453
    EnqueueWrongFunctionCodeCoilWriteResponse();
2✔
454

455
    try {
456
        ModbusDev->WriteRegister(*SerialPort, ModbusCoil0, 0xFF);
4✔
457
        EXPECT_FALSE(true);
×
458
    } catch (const TSerialDeviceTransientErrorException& e) {
4✔
459
        EXPECT_EQ(string("Serial protocol error: request and response function code mismatch"), e.what());
2✔
460
    }
461

462
    SerialPort->Close();
2✔
463
}
2✔
464

465
TEST_F(TModbusTest, WrongFunctionCodeWithExceptionWrite)
8✔
466
{
467
    EnqueueWrongFunctionCodeCoilWriteResponse(0x2);
2✔
468

469
    try {
470
        ModbusDev->WriteRegister(*SerialPort, ModbusCoil0, 0xFF);
4✔
471
        EXPECT_FALSE(true);
×
472
    } catch (const TSerialDeviceTransientErrorException& e) {
4✔
473
        EXPECT_EQ(string("Serial protocol error: request and response function code mismatch"), e.what());
2✔
474
    }
475

476
    SerialPort->Close();
2✔
477
}
2✔
478

479
TEST_F(TModbusTest, MinReadRegisters)
8✔
480
{
481
    EnqueueHoldingReadU16Min2ReadResponse();
2✔
482

483
    ModbusDev->DeviceConfig()->MinReadRegisters = 2;
2✔
484

485
    auto range = ModbusDev->CreateRegisterRange();
4✔
486
    range->Add(*SerialPort, ModbusHoldingU16WithAddressWrite, std::chrono::milliseconds::max());
2✔
487
    ModbusDev->ReadRegisterRange(*SerialPort, range);
2✔
488
    auto registerList = range->RegisterList();
4✔
489
    EXPECT_EQ(registerList.size(), 1);
2✔
490
    auto reg = registerList.front();
4✔
491
    EXPECT_EQ(GetUint32RegisterAddress(reg->GetConfig()->GetAddress()), 110);
2✔
492
    EXPECT_FALSE(reg->GetErrorState().test(TRegister::TError::ReadError));
2✔
493
    EXPECT_EQ(reg->GetValue(), 0x15);
2✔
494
}
2✔
495

496
TEST_F(TModbusTest, SkipNoiseAtPacketEnd)
8✔
497
{
498
    EnqueueReadResponseWithNoiseAtTheEnd(true);
2✔
499
    EnqueueReadResponseWithNoiseAtTheEnd(false);
2✔
500

501
    auto modbusRtuTraits = std::make_unique<Modbus::TModbusRTUTraits>(true);
4✔
502
    auto deviceConfig = GetDeviceConfig();
4✔
503
    deviceConfig.CommonConfig->FrameTimeout = 0ms;
2✔
504
    auto dev =
505
        std::make_shared<TModbusDevice>(std::move(modbusRtuTraits), deviceConfig, DeviceFactory.GetProtocol("modbus"));
4✔
506

507
    auto range = dev->CreateRegisterRange();
4✔
508
    auto reg = dev->AddRegister(TRegisterConfig::Create(Modbus::REG_HOLDING, 0x272E, U16));
6✔
509
    range->Add(*SerialPort, reg, std::chrono::milliseconds::max());
2✔
510
    // Read with noise
511
    EXPECT_NO_THROW(dev->ReadRegisterRange(*SerialPort, range));
2✔
512
    // Read without noise
513
    EXPECT_NO_THROW(dev->ReadRegisterRange(*SerialPort, range));
2✔
514
}
2✔
515

516
class TModbusIntegrationTest: public TSerialDeviceIntegrationTest, public TModbusExpectations
517
{
518
protected:
519
    enum TestMode
520
    {
521
        TEST_DEFAULT,
522
        TEST_HOLES,
523
        TEST_MAX_READ_REGISTERS,
524
        TEST_MAX_READ_REGISTERS_FIRST_CYCLE,
525
    };
526

527
    void SetUp() override;
528
    void TearDown() override;
529
    const char* ConfigPath() const override
7✔
530
    {
531
        return "configs/config-modbus-test.json";
7✔
532
    }
533
    void ExpectPollQueries(TestMode mode = TEST_DEFAULT);
534
    void InvalidateConfigPoll(TestMode mode = TEST_DEFAULT);
535
};
536

537
void TModbusIntegrationTest::SetUp()
13✔
538
{
539
    SelectModbusType(MODBUS_RTU);
13✔
540
    TSerialDeviceIntegrationTest::SetUp();
13✔
541
    ASSERT_TRUE(!!SerialPort);
13✔
542
}
543

544
void TModbusIntegrationTest::TearDown()
13✔
545
{
546
    SerialPort->Close();
13✔
547
    TSerialDeviceIntegrationTest::TearDown();
13✔
548
}
13✔
549

550
void TModbusIntegrationTest::ExpectPollQueries(TestMode mode)
7✔
551
{
552
    switch (mode) {
7✔
553
        case TEST_HOLES:
1✔
554
            EnqueueHoldingPackHoles10ReadResponse();
1✔
555
            break;
1✔
556
        case TEST_MAX_READ_REGISTERS:
1✔
557
            EnqueueHoldingPackMax3ReadResponse();
1✔
558
            break;
1✔
559
        case TEST_MAX_READ_REGISTERS_FIRST_CYCLE:
5✔
560
        case TEST_DEFAULT:
561
        default:
562
            EnqueueHoldingSeparateReadResponse();
5✔
563
            break;
5✔
564
    }
565
    // test different lengths and register types
566
    EnqueueHoldingReadS64Response();
7✔
567
    EnqueueHoldingReadF32Response();
7✔
568
    EnqueueHoldingReadU16Response();
7✔
569
    EnqueueInputReadU16Response();
7✔
570

571
    switch (mode) {
7✔
572
        case TEST_HOLES:
1✔
573
            EnqueueCoilsPackReadResponse();
1✔
574
            break;
1✔
575
        case TEST_MAX_READ_REGISTERS:
2✔
576
        case TEST_MAX_READ_REGISTERS_FIRST_CYCLE:
577
            EnqueueCoilReadResponse();
2✔
578
            Enqueue10CoilsMax3ReadResponse();
2✔
579
            break;
2✔
580
        case TEST_DEFAULT:
4✔
581
        default:
582
            EnqueueCoilReadResponse();
4✔
583
            Enqueue10CoilsReadResponse();
4✔
584
            break;
4✔
585
    }
586

587
    EnqueueDiscreteReadResponse();
7✔
588

589
    switch (mode) {
7✔
590
        case TEST_HOLES:
1✔
591
            EnqueueHoldingSingleReadResponse();
1✔
592
            EnqueueHoldingMultiReadResponse();
1✔
593
            break;
1✔
594
        case TEST_MAX_READ_REGISTERS:
2✔
595
        case TEST_MAX_READ_REGISTERS_FIRST_CYCLE:
596
            EnqueueHoldingSingleMax3ReadResponse();
2✔
597
            EnqueueHoldingMultiMax3ReadResponse();
2✔
598
            break;
2✔
599
        case TEST_DEFAULT:
4✔
600
        default:
601
            EnqueueHoldingSingleOneByOneReadResponse();
4✔
602
            EnqueueHoldingMultiOneByOneReadResponse();
4✔
603
            break;
4✔
604
    }
605
}
7✔
606

607
void TModbusIntegrationTest::InvalidateConfigPoll(TestMode mode)
3✔
608
{
609
    ExpectPollQueries(mode);
3✔
610
    Note() << "LoopOnce()";
3✔
611
    size_t n = (TEST_MAX_READ_REGISTERS_FIRST_CYCLE == mode) ? 21 : 18;
3✔
612
    for (size_t i = 0; i < n; ++i) {
60✔
613
        SerialDriver->LoopOnce();
57✔
614
    }
615
}
3✔
616

617
TEST_F(TModbusIntegrationTest, Poll)
8✔
618
{
619
    ExpectPollQueries();
2✔
620
    Note() << "LoopOnce()";
2✔
621
    for (auto i = 0; i < 18; ++i) {
38✔
622
        SerialDriver->LoopOnce();
36✔
623
    }
624
}
2✔
625

626
TEST_F(TModbusIntegrationTest, Write)
8✔
627
{
628
    PublishWaitOnValue("/devices/modbus-sample/controls/Coil 0/on", "1");
2✔
629
    PublishWaitOnValue("/devices/modbus-sample/controls/RGB/on", "10;20;30");
2✔
630
    PublishWaitOnValue("/devices/modbus-sample/controls/Holding S64/on", "81985529216486895");
2✔
631
    PublishWaitOnValue("/devices/modbus-sample/controls/Holding U16/on", "3905");
2✔
632
    PublishWaitOnValue("/devices/modbus-sample/controls/Holding U64 Single/on", "283686952306183");
2✔
633
    PublishWaitOnValue("/devices/modbus-sample/controls/Holding U64 Multi/on", "81985529216486895");
2✔
634

635
    EnqueueCoilWriteResponse();
2✔
636
    EnqueueRGBWriteResponse();
2✔
637
    EnqueueHoldingWriteS64Response();
2✔
638
    EnqueueHoldingWriteU16Response();
2✔
639
    EnqueueHoldingSingleWriteU64Response();
2✔
640
    EnqueueHoldingMultiWriteU64Response();
2✔
641

642
    EnqueueHoldingPartialPackReadResponse();
2✔
643
    EnqueueHoldingReadS64Response();
2✔
644
    EnqueueHoldingReadF32Response();
2✔
645
    EnqueueHoldingReadU16Response();
2✔
646
    EnqueueInputReadU16Response();
2✔
647
    EnqueueCoilOneByOneReadResponse();
2✔
648
    Enqueue10CoilsReadResponse();
2✔
649
    EnqueueDiscreteReadResponse();
2✔
650
    EnqueueHoldingSingleOneByOneReadResponse();
2✔
651
    EnqueueHoldingMultiOneByOneReadResponse();
2✔
652

653
    Note() << "LoopOnce()";
2✔
654
    for (auto i = 0; i < 17; ++i) {
36✔
655
        SerialDriver->LoopOnce();
34✔
656
    }
657
}
2✔
658

659
TEST_F(TModbusIntegrationTest, Errors)
8✔
660
{
661
    PublishWaitOnValue("/devices/modbus-sample/controls/Coil 0/on", "1");
2✔
662
    PublishWaitOnValue("/devices/modbus-sample/controls/Holding U16/on", "3905");
2✔
663
    PublishWaitOnValue("/devices/modbus-sample/controls/Holding U64 Single/on", "283686952306183");
2✔
664

665
    EnqueueCoilWriteResponse(0x1);
2✔
666
    EnqueueHoldingWriteU16Response(0x2);
2✔
667
    EnqueueHoldingSingleWriteU64Response(0x2);
2✔
668

669
    EnqueueHoldingSeparateReadResponse(0x3);
2✔
670
    EnqueueHoldingReadS64Response(0x4);
2✔
671
    EnqueueHoldingReadF32Response(0x5);
2✔
672
    EnqueueHoldingReadU16Response(0x6);
2✔
673
    EnqueueInputReadU16Response(0x8);
2✔
674
    EnqueueCoilReadResponse(0xa);
2✔
675
    Enqueue10CoilsReadResponse(0x54); // invalid exception code
2✔
676
    EnqueueDiscreteReadResponse(0xb);
2✔
677
    EnqueueHoldingSingleOneByOneReadResponse(0x2);
2✔
678
    EnqueueHoldingMultiOneByOneReadResponse(0x3);
2✔
679

680
    Note() << "LoopOnce()";
2✔
681
    for (auto i = 0; i < 18; ++i) {
38✔
682
        SerialDriver->LoopOnce();
36✔
683
    }
684
}
2✔
685

686
TEST_F(TModbusIntegrationTest, Holes)
8✔
687
{
688
    SerialPort->SetBaudRate(115200);
2✔
689
    // we check that driver issue long read requests for holding registers 4-18 and all coils
690
    Config->PortConfigs[0]->Devices[0]->Device->DeviceConfig()->MaxRegHole = 10;
2✔
691
    Config->PortConfigs[0]->Devices[0]->Device->DeviceConfig()->MaxBitHole = 80;
2✔
692
    // First cycle, read registers one by one to find unavailable registers
693
    ExpectPollQueries();
2✔
694
    Note() << "LoopOnce()";
2✔
695
    for (size_t i = 0; i < 18; ++i) {
38✔
696
        SerialDriver->LoopOnce();
36✔
697
    }
698
    // Second cycle with holes enabled
699
    ExpectPollQueries(TEST_HOLES);
2✔
700
    Note() << "LoopOnce() [holes enabled]";
2✔
701
    for (auto i = 0; i < 9; ++i) {
20✔
702
        SerialDriver->LoopOnce();
18✔
703
    }
704
}
2✔
705

706
TEST_F(TModbusIntegrationTest, HolesAutoDisable)
8✔
707
{
708
    SerialPort->SetBaudRate(115200);
2✔
709

710
    Config->PortConfigs[0]->Devices[0]->Device->DeviceConfig()->MaxRegHole = 10;
2✔
711
    InvalidateConfigPoll();
2✔
712

713
    EnqueueHoldingPackHoles10ReadResponse(0x3); // this must result in auto-disabling holes feature
2✔
714
    EnqueueHoldingReadS64Response();
2✔
715
    EnqueueHoldingReadF32Response();
2✔
716
    EnqueueHoldingReadU16Response();
2✔
717
    EnqueueInputReadU16Response();
2✔
718
    EnqueueCoilReadResponse();
2✔
719
    Enqueue10CoilsReadResponse();
2✔
720
    EnqueueDiscreteReadResponse();
2✔
721
    EnqueueHoldingSingleReadResponse();
2✔
722
    EnqueueHoldingMultiReadResponse();
2✔
723

724
    Note() << "LoopOnce() [read with holes, error]";
2✔
725
    for (auto i = 0; i < 10; ++i) {
22✔
726
        SerialDriver->LoopOnce();
20✔
727
    }
728

729
    EnqueueHoldingPackReadResponse();
2✔
730
    EnqueueHoldingReadS64Response();
2✔
731
    EnqueueHoldingReadF32Response();
2✔
732
    EnqueueHoldingReadU16Response();
2✔
733
    EnqueueInputReadU16Response();
2✔
734
    EnqueueCoilReadResponse();
2✔
735
    Enqueue10CoilsReadResponse();
2✔
736
    EnqueueDiscreteReadResponse();
2✔
737
    EnqueueHoldingSingleReadResponse();
2✔
738
    EnqueueHoldingMultiReadResponse();
2✔
739

740
    Note() << "LoopOnce() [read without holes]";
2✔
741
    for (auto i = 0; i < 11; ++i) {
24✔
742
        SerialDriver->LoopOnce();
22✔
743
    }
744
}
2✔
745

746
TEST_F(TModbusIntegrationTest, MaxReadRegisters)
8✔
747
{
748
    // Normally registers 4-9 (6 in total) are read or written in a single request.
749
    // By limiting the max_read_registers to 3 we force driver to issue two requests
750
    //    for this register range instead of one
751

752
    Config->PortConfigs[0]->Devices[0]->Device->DeviceConfig()->MaxReadRegisters = 3;
2✔
753
    InvalidateConfigPoll(TEST_MAX_READ_REGISTERS_FIRST_CYCLE);
2✔
754
    ExpectPollQueries(TEST_MAX_READ_REGISTERS);
2✔
755
    Note() << "LoopOnce()";
2✔
756
    for (auto i = 0; i < 17; ++i) {
36✔
757
        SerialDriver->LoopOnce();
34✔
758
    }
759
}
2✔
760

761
TEST_F(TModbusIntegrationTest, GuardInterval)
8✔
762
{
763
    Config->PortConfigs[0]->Devices[0]->Device->DeviceConfig()->RequestDelay = chrono::microseconds(1000);
2✔
764
    InvalidateConfigPoll();
2✔
765
}
2✔
766

767
class TModbusReconnectTest: public TModbusIntegrationTest
768
{
769
public:
770
    const char* ConfigPath() const override
1✔
771
    {
772
        return "configs/config-modbus-reconnect.json";
1✔
773
    }
774
};
775

776
TEST_F(TModbusReconnectTest, Disconnect)
8✔
777
{
778
    EnqueueHoldingSingleWriteU16Response();
2✔
779
    EnqueueHoldingReadU16Response();
2✔
780
    Note() << "LoopOnce() [connected]";
2✔
781
    SerialDriver->LoopOnce();
2✔
782
    EnqueueHoldingReadU16Response();
2✔
783
    Note() << "LoopOnce() [on-line]";
2✔
784
    SerialDriver->LoopOnce();
2✔
785
    this_thread::sleep_for(std::chrono::milliseconds(10));
2✔
786
    EnqueueHoldingReadU16Response(0, true);
2✔
787
    Note() << "LoopOnce() [disconnected]";
2✔
788
    SerialDriver->LoopOnce();
2✔
789
    EnqueueHoldingSingleWriteU16Response();
2✔
790
    EnqueueHoldingReadU16Response();
2✔
791
    Note() << "LoopOnce() [connected2]";
2✔
792
    SerialDriver->LoopOnce();
2✔
793
}
2✔
794

795
class TModbusBitmasksIntegrationTest: public TModbusIntegrationTest
796
{
797
protected:
798
    const char* ConfigPath() const override
2✔
799
    {
800
        return "configs/config-modbus-bitmasks-test.json";
2✔
801
    }
802

803
    void ExpectPollQueries(bool afterWrite = false, bool afterWriteMultiple = false);
804
};
805

806
void TModbusBitmasksIntegrationTest::ExpectPollQueries(bool afterWriteSingle, bool afterWriteMultiple)
2✔
807
{
808
    EnqueueU8Shift0Bits8HoldingReadResponse();
2✔
809
    EnqueueU16Shift8HoldingReadResponse(afterWriteMultiple);
2✔
810
    EnqueueU8Shift0SingleBitHoldingReadResponse(afterWriteSingle);
2✔
811
    EnqueueU8Shift1SingleBitHoldingReadResponse(afterWriteSingle);
2✔
812
    EnqueueU8Shift2SingleBitHoldingReadResponse(afterWriteSingle);
2✔
813
}
2✔
814

815
TEST_F(TModbusBitmasksIntegrationTest, Poll)
8✔
816
{
817
    ExpectPollQueries();
2✔
818
    Note() << "LoopOnce()";
2✔
819
    for (auto i = 0; i < 5; ++i) {
12✔
820
        SerialDriver->LoopOnce();
10✔
821
    }
822
}
2✔
823

824
TEST_F(TModbusBitmasksIntegrationTest, SingleWrite)
8✔
825
{
826
    ExpectPollQueries();
2✔
827
    Note() << "LoopOnce()";
2✔
828
    for (auto i = 0; i < 5; ++i) {
12✔
829
        SerialDriver->LoopOnce();
10✔
830
    }
831

832
    PublishWaitOnValue("/devices/modbus-sample/controls/U8:1/on", "1");
2✔
833

834
    EnqueueU8Shift1SingleBitHoldingWriteResponse();
2✔
835
    EnqueueU8Shift0Bits8HoldingReadResponse();
2✔
836
    EnqueueU16Shift8HoldingReadResponse(false);
2✔
837
    // Read all single bit registers at once as they are located in the same modbus register
838
    EnqueueU8Shift0SingleBitHoldingReadResponse(true);
2✔
839
    Note() << "LoopOnce()";
2✔
840
    for (auto i = 0; i < 3; ++i) {
8✔
841
        SerialDriver->LoopOnce();
6✔
842
    }
843
}
2✔
844

845
class TModbusSeveralBitmasksIntegrationTest: public TModbusIntegrationTest
846
{
847
protected:
848
    const char* ConfigPath() const override
1✔
849
    {
850
        return "configs/config-modbus-several-bitmasks-test.json";
1✔
851
    }
852
};
853

854
TEST_F(TModbusSeveralBitmasksIntegrationTest, Poll)
8✔
855
{
856
    EnqueueInputReadResponse(16, {0xA1, 0xA2});
2✔
857
    EnqueueInputReadResponse(16, {0xA1, 0xA2});
2✔
858
    EnqueueInputReadResponse(17, {0xB3, 0xB4});
2✔
859
    EnqueueInputReadResponse(17, {0xB3, 0xB4});
2✔
860
    EnqueueInputReadResponse(18, {0xC5, 0xC6});
2✔
861
    EnqueueInputReadResponse(18, {0xC5, 0xC6});
2✔
862
    EnqueueInputReadResponse(16, {0x00, 0x02, 0x02, 0x04, 0xff, 0x04});
2✔
863

864
    Note() << "LoopOnce()";
2✔
865
    for (auto i = 0; i < 7; ++i) {
16✔
866
        SerialDriver->LoopOnce();
14✔
867
    }
868
}
2✔
869

870
class TModbusBitmasksU32IntegrationTest: public TModbusIntegrationTest
871
{
872
protected:
873
    const char* ConfigPath() const override
2✔
874
    {
875
        return "configs/config-modbus-bitmasks-u32-test.json";
2✔
876
    }
877

878
    void ExpectPollQueries(bool afterWrite = false);
879
};
880

881
TEST_F(TModbusBitmasksU32IntegrationTest, Poll)
8✔
882
{
883
    EnqueueU32BitsHoldingReadResponse(false);
2✔
884
    Note() << "LoopOnce()";
2✔
885
    for (auto i = 0; i < 4; ++i) {
10✔
886
        SerialDriver->LoopOnce();
8✔
887
    }
888
}
2✔
889

890
TEST_F(TModbusBitmasksU32IntegrationTest, Write)
8✔
891
{
892
    EnqueueU32BitsHoldingReadResponse(false);
2✔
893
    Note() << "LoopOnce()";
2✔
894
    for (auto i = 0; i < 4; ++i) {
10✔
895
        SerialDriver->LoopOnce();
8✔
896
    }
897

898
    PublishWaitOnValue("/devices/modbus-sample/controls/U32:1/on", "1");
2✔
899
    PublishWaitOnValue("/devices/modbus-sample/controls/U32:20/on", "4");
2✔
900
    PublishWaitOnValue("/devices/modbus-sample/controls/U32:1 le/on", "1");
2✔
901
    PublishWaitOnValue("/devices/modbus-sample/controls/U32:20 le/on", "4");
2✔
902

903
    EnqueueU32BitsHoldingWriteResponse();
2✔
904
    EnqueueU32BitsHoldingReadResponse(true);
2✔
905
    Note() << "LoopOnce()";
2✔
906
    for (auto i = 0; i < 4; ++i) {
10✔
907
        SerialDriver->LoopOnce();
8✔
908
    }
909
}
2✔
910

911
class TModbusUnavailableRegistersIntegrationTest: public TSerialDeviceIntegrationTest, public TModbusExpectations
912
{
913
protected:
914
    void SetUp() override
3✔
915
    {
916
        SelectModbusType(MODBUS_RTU);
3✔
917
        TSerialDeviceIntegrationTest::SetUp();
3✔
918
        ASSERT_TRUE(!!SerialPort);
3✔
919
    }
920

921
    void TearDown() override
3✔
922
    {
923
        SerialPort->Close();
3✔
924
        TSerialDeviceIntegrationTest::TearDown();
3✔
925
    }
3✔
926

927
    const char* ConfigPath() const override
3✔
928
    {
929
        return "configs/config-modbus-unavailable-registers-test.json";
3✔
930
    }
931
};
932

933
TEST_F(TModbusUnavailableRegistersIntegrationTest, UnavailableRegisterOnBorder)
8✔
934
{
935
    // we check that driver detects unavailable register on the ranges border and stops to read it
936
    EnqueueHoldingPackUnavailableOnBorderReadResponse();
2✔
937
    Note() << "LoopOnce() [one by one]";
2✔
938
    for (auto i = 0; i < 6; ++i) {
14✔
939
        SerialDriver->LoopOnce();
12✔
940
    }
941
    Note() << "LoopOnce() [new range]";
2✔
942
    SerialDriver->LoopOnce();
2✔
943
}
2✔
944

945
TEST_F(TModbusUnavailableRegistersIntegrationTest, UnavailableRegisterInTheMiddle)
8✔
946
{
947
    // we check that driver detects unavailable register in the middle of the range
948
    // It must split the range into two parts and exclude unavailable register from reading
949
    EnqueueHoldingPackUnavailableInTheMiddleReadResponse();
2✔
950
    Note() << "LoopOnce() [one by one]";
2✔
951
    for (auto i = 0; i < 6; ++i) {
14✔
952
        SerialDriver->LoopOnce();
12✔
953
    }
954
    Note() << "LoopOnce() [new range]";
2✔
955
    for (auto i = 0; i < 2; ++i) {
6✔
956
        SerialDriver->LoopOnce();
4✔
957
    }
958
}
2✔
959

960
TEST_F(TModbusUnavailableRegistersIntegrationTest, UnsupportedRegisterOnBorder)
8✔
961
{
962
    // Check that driver detects unsupported registers
963
    // It must remove unsupported registers from request if they are on borders of a range
964
    // Unsupported registers in the middle of a range must be polled with the whole range
965
    EnqueueHoldingUnsupportedOnBorderReadResponse();
2✔
966
    Note() << "LoopOnce() [first read]";
2✔
967
    for (auto i = 0; i < 6; ++i) {
14✔
968
        SerialDriver->LoopOnce();
12✔
969
    }
970
    Note() << "LoopOnce() [new range]";
2✔
971
    for (auto i = 0; i < 2; ++i) {
6✔
972
        SerialDriver->LoopOnce();
4✔
973
    }
974
}
2✔
975

976
class TModbusUnavailableRegistersAndHolesIntegrationTest: public TSerialDeviceIntegrationTest,
977
                                                          public TModbusExpectations
978
{
979
protected:
980
    void SetUp() override
1✔
981
    {
982
        SelectModbusType(MODBUS_RTU);
1✔
983
        TSerialDeviceIntegrationTest::SetUp();
1✔
984
        ASSERT_TRUE(!!SerialPort);
1✔
985
    }
986

987
    void TearDown() override
1✔
988
    {
989
        SerialPort->Close();
1✔
990
        TSerialDeviceIntegrationTest::TearDown();
1✔
991
    }
1✔
992

993
    const char* ConfigPath() const override
1✔
994
    {
995
        return "configs/config-modbus-unavailable-registers-and-holes-test.json";
1✔
996
    }
997
};
998

999
TEST_F(TModbusUnavailableRegistersAndHolesIntegrationTest, HolesAndUnavailable)
8✔
1000
{
1001
    // we check that driver disables holes feature and after that detects and excludes unavailable register
1002
    Config->PortConfigs[0]->Devices[0]->Device->DeviceConfig()->MaxRegHole = 10;
2✔
1003

1004
    EnqueueHoldingPackUnavailableAndHolesReadResponse();
2✔
1005
    Note() << "LoopOnce() [one by one]";
2✔
1006
    for (auto i = 0; i < 5; ++i) {
12✔
1007
        SerialDriver->LoopOnce();
10✔
1008
    }
1009
    Note() << "LoopOnce() [with holes]";
2✔
1010
    SerialDriver->LoopOnce();
2✔
1011
    Note() << "LoopOnce() [new ranges]";
2✔
1012
    for (auto i = 0; i < 2; ++i) {
6✔
1013
        SerialDriver->LoopOnce();
4✔
1014
    }
1015
}
2✔
1016

1017
class TModbusContinuousRegisterReadTest: public TSerialDeviceIntegrationTest, public TModbusExpectations
1018
{
1019
protected:
1020
    const char* ConfigPath() const override
2✔
1021
    {
1022
        return "configs/config-modbus-continuous-read-test.json";
2✔
1023
    }
1024
};
1025

1026
TEST_F(TModbusContinuousRegisterReadTest, Supported)
8✔
1027
{
1028
    EnqueueContinuousReadEnableResponse();
2✔
1029
    EnqueueContinuousReadHoldingResponse();
2✔
1030
    EnqueueContinuousReadCoilResponse(false);
2✔
1031
    Note() << "LoopOnce() [one by one]";
2✔
1032
    for (auto i = 0; i < 5; ++i) {
12✔
1033
        SerialDriver->LoopOnce();
10✔
1034
    }
1035
    EnqueueContinuousReadHoldingResponse(false);
2✔
1036
    EnqueueContinuousReadCoilResponse(false);
2✔
1037
    Note() << "LoopOnce() [continuous]";
2✔
1038
    for (auto i = 0; i < 4; ++i) {
10✔
1039
        SerialDriver->LoopOnce();
8✔
1040
    }
1041
}
2✔
1042

1043
TEST_F(TModbusContinuousRegisterReadTest, NotSupported)
8✔
1044
{
1045
    EnqueueContinuousReadEnableResponse(false);
2✔
1046
    EnqueueContinuousReadHoldingResponse();
2✔
1047
    EnqueueContinuousReadCoilResponse();
2✔
1048
    Note() << "LoopOnce() [one by one]";
2✔
1049
    for (auto i = 0; i < 6; ++i) {
14✔
1050
        SerialDriver->LoopOnce();
12✔
1051
    }
1052
    EnqueueContinuousReadHoldingResponse();
2✔
1053
    EnqueueContinuousReadCoilResponse();
2✔
1054
    Note() << "LoopOnce() [separated]";
2✔
1055
    for (auto i = 0; i < 6; ++i) {
14✔
1056
        SerialDriver->LoopOnce();
12✔
1057
    }
1058
}
2✔
1059

1060
class TModbusContinuousRegisterWriteTest: public TSerialDeviceIntegrationTest, public TModbusExpectations
1061
{
1062
protected:
1063
    const char* ConfigPath() const override
1✔
1064
    {
1065
        return "configs/config-modbus-continuous-write-test.json";
1✔
1066
    }
1067
};
1068

1069
TEST_F(TModbusContinuousRegisterWriteTest, SetupItems)
8✔
1070
{
1071
    EnqueueContinuousWriteResponse();
2✔
1072
    EnqueueSimpleChannelReadResponse();
2✔
1073
    SerialDriver->LoopOnce();
2✔
1074
}
2✔
1075

1076
class TModbusPartialRegisterWriteTest: public TSerialDeviceIntegrationTest, public TModbusExpectations
1077
{
1078
protected:
1079
    const char* ConfigPath() const override
1✔
1080
    {
1081
        return "configs/config-modbus-partial-write-test.json";
1✔
1082
    }
1083
};
1084

1085
TEST_F(TModbusPartialRegisterWriteTest, SetupItems)
8✔
1086
{
1087
    EnqueuePartialWriteReadCacheDataResponse();
2✔
1088
    EnqueuePartialWriteResponse();
2✔
1089
    EnqueueSimpleChannelReadResponse();
2✔
1090
    SerialDriver->LoopOnce();
2✔
1091
}
2✔
1092

1093
class TModbusLittleEndianRegisterTest: public TSerialDeviceIntegrationTest, public TModbusExpectations
1094
{
1095
protected:
1096
    const char* ConfigPath() const override
2✔
1097
    {
1098
        return "configs/config-modbus-little-endian-test.json";
2✔
1099
    }
1100
};
1101

1102
TEST_F(TModbusLittleEndianRegisterTest, Read)
8✔
1103
{
1104
    EnqueueLittleEndianReadResponses();
2✔
1105
    for (auto i = 0; i < 15; ++i) {
32✔
1106
        SerialDriver->LoopOnce();
30✔
1107
    }
1108
}
2✔
1109

1110
TEST_F(TModbusLittleEndianRegisterTest, Write)
8✔
1111
{
1112
    EnqueueLittleEndianReadResponses();
2✔
1113
    for (auto i = 0; i < 15; ++i) {
32✔
1114
        SerialDriver->LoopOnce();
30✔
1115
    }
1116

1117
    PublishWaitOnValue("/devices/modbus-sample/controls/U8_LB/on", "1");
2✔
1118
    PublishWaitOnValue("/devices/modbus-sample/controls/U16_LB/on", std::to_string(0x0304));
2✔
1119
    PublishWaitOnValue("/devices/modbus-sample/controls/U24_LB/on", std::to_string(0x050607));
2✔
1120
    PublishWaitOnValue("/devices/modbus-sample/controls/U32_LB/on", std::to_string(0x08090A0B));
2✔
1121
    PublishWaitOnValue("/devices/modbus-sample/controls/U64_LB/on", std::to_string(0x0C0D0E0F11121314));
2✔
1122

1123
    PublishWaitOnValue("/devices/modbus-sample/controls/U8_BL/on", "1");
2✔
1124
    PublishWaitOnValue("/devices/modbus-sample/controls/U16_BL/on", std::to_string(0x0304));
2✔
1125
    PublishWaitOnValue("/devices/modbus-sample/controls/U24_BL/on", std::to_string(0x050607));
2✔
1126
    PublishWaitOnValue("/devices/modbus-sample/controls/U32_BL/on", std::to_string(0x08090A0B));
2✔
1127
    PublishWaitOnValue("/devices/modbus-sample/controls/U64_BL/on", std::to_string(0x0C0D0E0F11121314));
2✔
1128

1129
    PublishWaitOnValue("/devices/modbus-sample/controls/Float_LL/on", std::to_string(3.1415));
2✔
1130
    PublishWaitOnValue("/devices/modbus-sample/controls/String_LL/on", "dolor");
2✔
1131
    PublishWaitOnValue("/devices/modbus-sample/controls/String8_LL/on", "magna");
2✔
1132
    PublishWaitOnValue("/devices/modbus-sample/controls/String_BL/on", "dolor");
2✔
1133
    PublishWaitOnValue("/devices/modbus-sample/controls/String8_BL/on", "magna");
2✔
1134

1135
    EnqueueLittleEndianWriteResponses();
2✔
1136
    EnqueueLittleEndianReadResponses();
2✔
1137
    for (auto i = 0; i < 15; ++i) {
32✔
1138
        SerialDriver->LoopOnce();
30✔
1139
    }
1140
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc