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

wirenboard / wb-mqtt-serial / 2

29 Dec 2025 12:28PM UTC coverage: 76.817% (+4.0%) from 72.836%
2

Pull #1045

github

54aa0c
pgasheev
up changelog
Pull Request #1045: Fix firmware version in WB-M1W2 template

6873 of 9161 branches covered (75.02%)

12966 of 16879 relevant lines covered (76.82%)

1651.61 hits per line

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

99.17
/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
52✔
46
{
47
    TModbusDeviceConfig config;
52✔
48
    config.CommonConfig = std::make_shared<TDeviceConfig>("modbus", std::to_string(0x01), "modbus");
52✔
49
    config.CommonConfig->MaxReadRegisters = 10;
52✔
50
    return config;
52✔
51
}
52

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

190
    return errorRegisters;
20✔
191
}
192

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

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

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

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

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

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

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

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

220
TEST_F(TModbusTest, ReadHoldingRegiterWithWriteAddress)
16✔
221
{
222
    EnqueueHoldingReadU16ResponseWithWriteAddress();
4✔
223

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

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

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

244
TEST_F(TModbusTest, ReadHoldingRegiterWithOffsetWriteOptions)
16✔
245
{
246
    EnqueueHoldingReadU16ResponseWithOffsetWriteOptions();
4✔
247

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

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

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

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

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

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

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

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

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

319
TEST_F(TModbusTest, WriteHoldingRegiterWithOffsetWriteOptions)
16✔
320
{
321
    EnqueueHoldingWriteU16ResponseWithOffsetWriteOptions();
4✔
322

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

326
TEST_F(TModbusTest, WriteOnlyHoldingRegiter)
16✔
327
{
328
    PRegister ModbusHoldingU16WriteOnly;
4✔
329

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

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

338
TEST_F(TModbusTest, WriteOnlyHoldingRegiterNeg)
16✔
339
{
340
    TRegisterDesc regAddrDesc;
4✔
341

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

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

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

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

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

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

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

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

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

388
TEST_F(TModbusTest, CRCError)
16✔
389
{
390
    EnqueueInvalidCRCCoilReadResponse();
4✔
391

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

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

399
TEST_F(TModbusTest, WrongResponseDataSize)
16✔
400
{
401
    EnqueueWrongDataSizeReadResponse();
4✔
402

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

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

410
TEST_F(TModbusTest, WrongSlaveId)
16✔
411
{
412
    EnqueueWrongSlaveIdCoilReadResponse();
4✔
413

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

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

419
TEST_F(TModbusTest, WrongFunctionCode)
16✔
420
{
421
    EnqueueWrongFunctionCodeCoilReadResponse();
4✔
422

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

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

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

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

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

437
TEST_F(TModbusTest, WrongSlaveIdWrite)
16✔
438
{
439
    EnqueueWrongSlaveIdCoilWriteResponse();
4✔
440

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

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

451
TEST_F(TModbusTest, WrongFunctionCodeWrite)
16✔
452
{
453
    EnqueueWrongFunctionCodeCoilWriteResponse();
4✔
454

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

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

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

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

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

479
TEST_F(TModbusTest, MinReadRegisters)
16✔
480
{
481
    EnqueueHoldingReadU16Min2ReadResponse();
4✔
482

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

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

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

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

507
    auto range = dev->CreateRegisterRange();
8✔
508
    auto reg = dev->AddRegister(TRegisterConfig::Create(Modbus::REG_HOLDING, 0x272E, U16));
12✔
509
    range->Add(*SerialPort, reg, std::chrono::milliseconds::max());
4✔
510
    // Read with noise
511
    EXPECT_NO_THROW(dev->ReadRegisterRange(*SerialPort, range));
4✔
512
    // Read without noise
513
    EXPECT_NO_THROW(dev->ReadRegisterRange(*SerialPort, range));
4✔
514
}
4✔
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
14✔
530
    {
531
        return "configs/config-modbus-test.json";
14✔
532
    }
533
    void ExpectPollQueries(TestMode mode = TEST_DEFAULT);
534
    void InvalidateConfigPoll(TestMode mode = TEST_DEFAULT);
535
};
536

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

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

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

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

587
    EnqueueDiscreteReadResponse();
14✔
588

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

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

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

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

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

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

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

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

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

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

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

686
TEST_F(TModbusIntegrationTest, Holes)
16✔
687
{
688
    SerialPort->SetBaudRate(115200);
4✔
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;
4✔
691
    Config->PortConfigs[0]->Devices[0]->Device->DeviceConfig()->MaxBitHole = 80;
4✔
692
    // First cycle, read registers one by one to find unavailable registers
693
    ExpectPollQueries();
4✔
694
    Note() << "LoopOnce()";
4✔
695
    for (size_t i = 0; i < 18; ++i) {
76✔
696
        SerialDriver->LoopOnce();
72✔
697
    }
698
    // Second cycle with holes enabled
699
    ExpectPollQueries(TEST_HOLES);
4✔
700
    Note() << "LoopOnce() [holes enabled]";
4✔
701
    for (auto i = 0; i < 9; ++i) {
40✔
702
        SerialDriver->LoopOnce();
36✔
703
    }
704
}
4✔
705

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

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

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

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

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

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

746
TEST_F(TModbusIntegrationTest, MaxReadRegisters)
16✔
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;
4✔
753
    InvalidateConfigPoll(TEST_MAX_READ_REGISTERS_FIRST_CYCLE);
4✔
754
    ExpectPollQueries(TEST_MAX_READ_REGISTERS);
4✔
755
    Note() << "LoopOnce()";
4✔
756
    for (auto i = 0; i < 17; ++i) {
72✔
757
        SerialDriver->LoopOnce();
68✔
758
    }
759
}
4✔
760

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

945
TEST_F(TModbusUnavailableRegistersIntegrationTest, UnavailableRegisterInTheMiddle)
16✔
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();
4✔
950
    Note() << "LoopOnce() [one by one]";
4✔
951
    for (auto i = 0; i < 6; ++i) {
28✔
952
        SerialDriver->LoopOnce();
24✔
953
    }
954
    Note() << "LoopOnce() [new range]";
4✔
955
    for (auto i = 0; i < 2; ++i) {
12✔
956
        SerialDriver->LoopOnce();
8✔
957
    }
958
}
4✔
959

960
TEST_F(TModbusUnavailableRegistersIntegrationTest, UnsupportedRegisterOnBorder)
16✔
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();
4✔
966
    Note() << "LoopOnce() [first read]";
4✔
967
    for (auto i = 0; i < 6; ++i) {
28✔
968
        SerialDriver->LoopOnce();
24✔
969
    }
970
    Note() << "LoopOnce() [new range]";
4✔
971
    for (auto i = 0; i < 2; ++i) {
12✔
972
        SerialDriver->LoopOnce();
8✔
973
    }
974
}
4✔
975

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

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

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

999
TEST_F(TModbusUnavailableRegistersAndHolesIntegrationTest, HolesAndUnavailable)
16✔
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;
4✔
1003

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

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

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

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

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

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

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

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

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

1102
TEST_F(TModbusLittleEndianRegisterTest, Read)
16✔
1103
{
1104
    EnqueueLittleEndianReadResponses();
4✔
1105
    for (auto i = 0; i < 15; ++i) {
64✔
1106
        SerialDriver->LoopOnce();
60✔
1107
    }
1108
}
4✔
1109

1110
TEST_F(TModbusLittleEndianRegisterTest, Write)
16✔
1111
{
1112
    EnqueueLittleEndianReadResponses();
4✔
1113
    for (auto i = 0; i < 15; ++i) {
64✔
1114
        SerialDriver->LoopOnce();
60✔
1115
    }
1116

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

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

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

1135
    EnqueueLittleEndianWriteResponses();
4✔
1136
    EnqueueLittleEndianReadResponses();
4✔
1137
    for (auto i = 0; i < 15; ++i) {
64✔
1138
        SerialDriver->LoopOnce();
60✔
1139
    }
1140
}
4✔
1141

1142
class TModbusPublishTest: public TSerialDeviceIntegrationTest
1143
{
1144
protected:
1145
    void SetUp() override
2✔
1146
    {
1147
        TSerialDeviceIntegrationTest::SetUp();
2✔
1148
        SetMode(E_Normal);
2✔
1149
    }
2✔
1150
    const char* ConfigPath() const override
2✔
1151
    {
1152
        return "configs/config-modbus-publish-test.json";
2✔
1153
    }
1154
};
1155

1156
// Check that "sporadic" register data pubished on every OnValueRead call, even if value has not changed,
1157
// and "normal" register data published only if value changed.
1158
TEST_F(TModbusPublishTest, DuplicateValues)
16✔
1159
{
1160
    auto driver = SerialDriver->GetPortDrivers().front();
8✔
1161
    auto& regs = driver->GetSerialClient()->GetDevices().front()->GetRegisters();
4✔
1162
    for (auto reg: regs) {
12✔
1163
        if (reg->GetConfig()->SporadicMode == TRegisterConfig::TSporadicMode::ONLY_EVENTS) {
8✔
1164
            reg->ExcludeFromPolling();
4✔
1165
        }
1166
        reg->SetValue(TRegisterValue{1});
8✔
1167
    }
1168
    for (auto i = 0; i < 3; ++i) {
16✔
1169
        for (auto reg: regs) {
36✔
1170
            driver->OnValueRead(reg);
24✔
1171
        }
1172
    }
1173
}
4✔
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