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

wirenboard / wb-mqtt-serial / 691

27 Aug 2025 12:12PM UTC coverage: 73.322% (+0.3%) from 73.031%
691

push

github

web-flow
Add mode to port/Scan RPC (#991)

6695 of 9482 branches covered (70.61%)

229 of 252 new or added lines in 3 files covered. (90.87%)

2 existing lines in 2 files now uncovered.

12673 of 17284 relevant lines covered (73.32%)

370.29 hits per line

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

97.27
/test/modbus_ext_common_test.cpp
1
#include "crc16.h"
2
#include "modbus_ext_common.h"
3
#include "serial_exc.h"
4
#include "gtest/gtest.h"
5

6
#include <cmath>
7
#include <list>
8

9
namespace
10
{
11
    void SetCrc(std::vector<uint8_t>& data)
11✔
12
    {
13
        if (data.size() < 2) {
11✔
NEW
14
            return;
×
15
        }
16
        auto crc = CRC16::CalculateCRC16(data.data(), data.size() - 2);
11✔
17
        data[data.size() - 1] = crc & 0xFF;
11✔
18
        data[data.size() - 2] = (crc >> 8) & 0xFF;
11✔
19
    }
20

21
    class TPortMock: public TPort
22
    {
23
    public:
24
        std::vector<std::vector<uint8_t>> Requests;
25
        std::list<std::vector<uint8_t>> Responses;
26
        bool ThrowTimeout = false;
27

28
        TPortMock()
16✔
29
        {}
16✔
30

31
        void Open() override
×
32
        {}
33
        void Close() override
×
34
        {}
35
        bool IsOpen() const override
×
36
        {
37
            return false;
×
38
        }
39
        void CheckPortOpen() const override
×
40
        {}
41

42
        void WriteBytes(const uint8_t* buf, int count) override
23✔
43
        {
44
            std::vector<uint8_t> data(buf, buf + count);
46✔
45
            Requests.emplace_back(std::move(data));
23✔
46
        }
23✔
47

48
        uint8_t ReadByte(const std::chrono::microseconds& timeout) override
×
49
        {
50
            return 0;
×
51
        }
52

53
        TReadFrameResult ReadFrame(uint8_t* buf,
23✔
54
                                   size_t count,
55
                                   const std::chrono::microseconds& responseTimeout,
56
                                   const std::chrono::microseconds& frameTimeout,
57
                                   TFrameCompletePred frame_complete = 0) override
58
        {
59
            if (ThrowTimeout) {
23✔
60
                throw TResponseTimeoutException();
1✔
61
            }
62
            if (Responses.empty()) {
22✔
NEW
63
                throw TResponseTimeoutException();
×
64
            }
65
            if (Responses.front().size() > count) {
22✔
UNCOV
66
                throw std::runtime_error("Buffer is too small");
×
67
            }
68
            TReadFrameResult res;
22✔
69
            res.Count = Responses.front().size();
22✔
70
            memcpy(buf, Responses.front().data(), Responses.front().size());
22✔
71
            Responses.pop_front();
22✔
72
            return res;
22✔
73
        }
74

75
        void SkipNoise() override
×
76
        {}
77

78
        void SleepSinceLastInteraction(const std::chrono::microseconds& us) override
16✔
79
        {}
16✔
80

81
        std::string GetDescription(bool verbose) const override
1✔
82
        {
83
            return std::string();
1✔
84
        }
85

86
        std::chrono::microseconds GetSendTimeBytes(double bytesNumber) const override
40✔
87
        {
88
            // 8-N-2
89
            auto bits = std::ceil(11 * bytesNumber);
40✔
90
            return GetSendTimeBits(bits);
40✔
91
        }
92

93
        std::chrono::microseconds GetSendTimeBits(size_t bitsNumber) const override
76✔
94
        {
95
            // 115200 bps
96
            auto us = std::ceil((1000000.0 * bitsNumber) / 115200.0);
76✔
97
            return std::chrono::microseconds(static_cast<std::chrono::microseconds::rep>(us));
76✔
98
        }
99

100
        void AddResponse(const std::vector<uint8_t>& data)
22✔
101
        {
102
            Responses.push_back(data);
22✔
103
            if (Responses.back().size() >= 2 && Responses.back()[Responses.back().size() - 2] == 0 &&
33✔
104
                Responses.back()[Responses.back().size() - 1] == 0)
11✔
105
            {
106
                SetCrc(Responses.back());
11✔
107
            }
108
        }
22✔
109
    };
110

111
    class TTestEventsVisitor: public ModbusExt::IEventsVisitor
112
    {
113
    public:
114
        std::unordered_map<uint16_t, uint16_t> Events;
115
        bool Reboot = false;
116

117
        virtual void Event(uint8_t slaveId,
2✔
118
                           uint8_t eventType,
119
                           uint16_t eventId,
120
                           const uint8_t* data,
121
                           size_t dataSize) override
122
        {
123
            if (eventType == ModbusExt::TEventType::REBOOT) {
2✔
124
                Reboot = true;
1✔
125
                return;
1✔
126
            }
127
            Events[eventId] = data[0] | (data[1] << 8);
1✔
128
        }
129
    };
130

131
} // namespace
132

133
TEST(TModbusExtTest, EventsEnablerOneReg)
8✔
134
{
135
    TPortMock port;
4✔
136
    port.AddResponse({0x0A, 0x46, 0x18, 0x01, 0x01, 0xE9, 0x1E});
2✔
137

138
    std::map<uint16_t, bool> response;
4✔
139
    ModbusExt::TEventsEnabler ev(10, port, [&response](uint8_t type, uint16_t reg, bool enabled) {
1✔
140
        response[reg] = enabled;
1✔
141
    });
5✔
142
    ev.AddRegister(101, ModbusExt::TEventType::COIL, ModbusExt::TEventPriority::HIGH);
2✔
143

144
    EXPECT_NO_THROW(ev.SendRequests());
2✔
145

146
    EXPECT_EQ(port.Requests.size(), 1);
2✔
147

148
    auto request = port.Requests.front();
4✔
149

150
    EXPECT_EQ(request.size(), 11);
2✔
151
    EXPECT_EQ(request[0], 0x0A); // slave id
2✔
152
    EXPECT_EQ(request[1], 0x46); // command
2✔
153
    EXPECT_EQ(request[2], 0x18); // subcommand
2✔
154
    EXPECT_EQ(request[3], 0x05); // settings size
2✔
155

156
    EXPECT_EQ(request[4], 0x01); // event type
2✔
157
    EXPECT_EQ(request[5], 0x00); // address MSB
2✔
158
    EXPECT_EQ(request[6], 0x65); // address LSB
2✔
159
    EXPECT_EQ(request[7], 0x01); // count
2✔
160
    EXPECT_EQ(request[8], 0x02); // priority
2✔
161

162
    EXPECT_EQ(request[9], 0xC5);  // CRC16 LSB
2✔
163
    EXPECT_EQ(request[10], 0x90); // CRC16 MSB
2✔
164

165
    EXPECT_EQ(response.size(), 1);
2✔
166
    EXPECT_TRUE(response[101]);
2✔
167
}
2✔
168

169
TEST(TModbusExtTest, EventsEnablerIllegalFunction)
8✔
170
{
171
    TPortMock port;
4✔
172
    port.AddResponse({0x0A, 0xC6, 0x01, 0xC3, 0xA2});
2✔
173

174
    ModbusExt::TEventsEnabler ev(10, port, [](uint8_t, uint16_t, bool) {});
4✔
175
    ev.AddRegister(101, ModbusExt::TEventType::COIL, ModbusExt::TEventPriority::HIGH);
2✔
176

177
    EXPECT_THROW(ev.SendRequests(), Modbus::TModbusExceptionError);
2✔
178
}
2✔
179

180
TEST(TModbusExtTest, EventsEnablerTwoRanges)
8✔
181
{
182
    TPortMock port;
4✔
183
    // 0x03 = 0b00000011
184
    // 0xFB = 0b11111011
185
    // 0x02 = 0b00000010
186
    port.AddResponse({0x0A, 0x46, 0x18, 0x03, 0x03, 0xFB, 0x02, 0xAD, 0x11});
2✔
187

188
    std::map<uint16_t, bool> response;
4✔
189
    ModbusExt::TEventsEnabler ev(10, port, [&response](uint8_t type, uint16_t reg, bool enabled) {
12✔
190
        response[reg] = enabled;
12✔
191
    });
16✔
192

193
    ev.AddRegister(101, ModbusExt::TEventType::COIL, ModbusExt::TEventPriority::HIGH);
2✔
194
    ev.AddRegister(102, ModbusExt::TEventType::COIL, ModbusExt::TEventPriority::HIGH);
2✔
195

196
    ev.AddRegister(103, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::LOW);
2✔
197
    ev.AddRegister(104, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::LOW);
2✔
198
    ev.AddRegister(105, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::LOW);
2✔
199
    ev.AddRegister(106, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::LOW);
2✔
200
    ev.AddRegister(107, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::LOW);
2✔
201
    ev.AddRegister(108, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::LOW);
2✔
202
    ev.AddRegister(109, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::LOW);
2✔
203
    ev.AddRegister(110, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::HIGH);
2✔
204
    ev.AddRegister(111, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::HIGH);
2✔
205
    ev.AddRegister(112, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::HIGH);
2✔
206

207
    EXPECT_NO_THROW(ev.SendRequests());
2✔
208

209
    EXPECT_EQ(port.Requests.size(), 1);
2✔
210

211
    auto request = port.Requests.front();
4✔
212

213
    EXPECT_EQ(request.size(), 26);
2✔
214
    EXPECT_EQ(request[0], 0x0A); // slave id
2✔
215
    EXPECT_EQ(request[1], 0x46); // command
2✔
216
    EXPECT_EQ(request[2], 0x18); // subcommand
2✔
217
    EXPECT_EQ(request[3], 0x14); // settings size
2✔
218

219
    EXPECT_EQ(request[4], 0x01); // event type
2✔
220
    EXPECT_EQ(request[5], 0x00); // address MSB
2✔
221
    EXPECT_EQ(request[6], 0x65); // address LSB
2✔
222
    EXPECT_EQ(request[7], 0x02); // count
2✔
223
    EXPECT_EQ(request[8], 0x02); // priority 101
2✔
224
    EXPECT_EQ(request[9], 0x02); // priority 102
2✔
225

226
    EXPECT_EQ(request[10], 0x04); // event type
2✔
227
    EXPECT_EQ(request[11], 0x00); // address MSB
2✔
228
    EXPECT_EQ(request[12], 0x67); // address LSB
2✔
229
    EXPECT_EQ(request[13], 0x0A); // count
2✔
230
    EXPECT_EQ(request[14], 0x01); // priority 103
2✔
231
    EXPECT_EQ(request[15], 0x01); // priority 104
2✔
232
    EXPECT_EQ(request[16], 0x01); // priority 105
2✔
233
    EXPECT_EQ(request[17], 0x01); // priority 106
2✔
234
    EXPECT_EQ(request[18], 0x01); // priority 107
2✔
235
    EXPECT_EQ(request[19], 0x01); // priority 108
2✔
236
    EXPECT_EQ(request[20], 0x01); // priority 109
2✔
237
    EXPECT_EQ(request[21], 0x02); // priority 110
2✔
238
    EXPECT_EQ(request[22], 0x02); // priority 111
2✔
239
    EXPECT_EQ(request[23], 0x02); // priority 112
2✔
240

241
    EXPECT_EQ(request[24], 0x25); // CRC16 LSB
2✔
242
    EXPECT_EQ(request[25], 0xF0); // CRC16 MSB
2✔
243

244
    EXPECT_EQ(response.size(), 12);
2✔
245
    EXPECT_TRUE(response[101]);
2✔
246
    EXPECT_TRUE(response[102]);
2✔
247
    EXPECT_TRUE(response[103]);
2✔
248
    EXPECT_TRUE(response[104]);
2✔
249
    EXPECT_FALSE(response[105]);
2✔
250
    EXPECT_TRUE(response[106]);
2✔
251
    EXPECT_TRUE(response[107]);
2✔
252
    EXPECT_TRUE(response[108]);
2✔
253
    EXPECT_TRUE(response[109]);
2✔
254
    EXPECT_TRUE(response[110]);
2✔
255
    EXPECT_FALSE(response[111]);
2✔
256
    EXPECT_TRUE(response[112]);
2✔
257
}
2✔
258

259
TEST(TModbusExtTest, EventsEnablerRangesWithHoles)
8✔
260
{
261
    TPortMock port;
4✔
262
    // 0x03 = 0b00000011
263
    // 0xF3 = 0b11110011
264
    // 0x02 = 0b00000010
265
    // 0x03 = 0b00000011
266
    port.AddResponse({0x0A, 0x46, 0x18, 0x04, 0x03, 0xF3, 0x02, 0x03, 0xA4, 0xBE});
2✔
267

268
    std::map<uint16_t, bool> response;
4✔
269
    ModbusExt::TEventsEnabler ev(
270
        10,
271
        port,
272
        [&response](uint8_t type, uint16_t reg, bool enabled) { response[reg] = enabled; },
14✔
273
        ModbusExt::TEventsEnabler::DISABLE_EVENTS_IN_HOLES);
4✔
274

275
    ev.AddRegister(101, ModbusExt::TEventType::COIL, ModbusExt::TEventPriority::HIGH);
2✔
276
    ev.AddRegister(102, ModbusExt::TEventType::COIL, ModbusExt::TEventPriority::HIGH);
2✔
277
    ev.AddRegister(103, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::LOW);
2✔
278
    ev.AddRegister(104, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::LOW);
2✔
279
    ev.AddRegister(107, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::LOW);
2✔
280
    ev.AddRegister(108, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::LOW);
2✔
281
    ev.AddRegister(109, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::LOW);
2✔
282
    ev.AddRegister(110, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::HIGH);
2✔
283
    ev.AddRegister(111, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::HIGH);
2✔
284
    ev.AddRegister(112, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::HIGH);
2✔
285
    ev.AddRegister(118, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::HIGH);
2✔
286
    ev.AddRegister(119, ModbusExt::TEventType::INPUT, ModbusExt::TEventPriority::HIGH);
2✔
287

288
    EXPECT_NO_THROW(ev.SendRequests());
2✔
289

290
    EXPECT_EQ(port.Requests.size(), 1);
2✔
291

292
    auto request = port.Requests.front();
4✔
293

294
    EXPECT_EQ(request.size(), 32);
2✔
295
    EXPECT_EQ(request[0], 0x0A); // slave id
2✔
296
    EXPECT_EQ(request[1], 0x46); // command
2✔
297
    EXPECT_EQ(request[2], 0x18); // subcommand
2✔
298
    EXPECT_EQ(request[3], 0x1A); // settings size
2✔
299

300
    EXPECT_EQ(request[4], 0x01); // event type
2✔
301
    EXPECT_EQ(request[5], 0x00); // address MSB
2✔
302
    EXPECT_EQ(request[6], 0x65); // address LSB
2✔
303
    EXPECT_EQ(request[7], 0x02); // count
2✔
304
    EXPECT_EQ(request[8], 0x02); // priority 101
2✔
305
    EXPECT_EQ(request[9], 0x02); // priority 102
2✔
306

307
    EXPECT_EQ(request[10], 0x04); // event type
2✔
308
    EXPECT_EQ(request[11], 0x00); // address MSB
2✔
309
    EXPECT_EQ(request[12], 0x67); // address LSB
2✔
310
    EXPECT_EQ(request[13], 0x0A); // count
2✔
311
    EXPECT_EQ(request[14], 0x01); // priority 103
2✔
312
    EXPECT_EQ(request[15], 0x01); // priority 104
2✔
313
    EXPECT_EQ(request[16], 0x00); // priority 105
2✔
314
    EXPECT_EQ(request[17], 0x00); // priority 106
2✔
315
    EXPECT_EQ(request[18], 0x01); // priority 107
2✔
316
    EXPECT_EQ(request[19], 0x01); // priority 108
2✔
317
    EXPECT_EQ(request[20], 0x01); // priority 109
2✔
318
    EXPECT_EQ(request[21], 0x02); // priority 110
2✔
319
    EXPECT_EQ(request[22], 0x02); // priority 111
2✔
320
    EXPECT_EQ(request[23], 0x02); // priority 112
2✔
321

322
    EXPECT_EQ(request[24], 0x04); // event type
2✔
323
    EXPECT_EQ(request[25], 0x00); // address MSB
2✔
324
    EXPECT_EQ(request[26], 0x76); // address LSB
2✔
325
    EXPECT_EQ(request[27], 0x02); // count
2✔
326
    EXPECT_EQ(request[28], 0x02); // priority 118
2✔
327
    EXPECT_EQ(request[29], 0x02); // priority 119
2✔
328

329
    EXPECT_EQ(request[30], 0x81); // CRC16 LSB
2✔
330
    EXPECT_EQ(request[31], 0x54); // CRC16 MSB
2✔
331

332
    EXPECT_EQ(response.size(), 14);
2✔
333
    EXPECT_TRUE(response[101]);
2✔
334
    EXPECT_TRUE(response[102]);
2✔
335
    EXPECT_TRUE(response[103]);
2✔
336
    EXPECT_TRUE(response[104]);
2✔
337
    EXPECT_FALSE(response[105]);
2✔
338
    EXPECT_FALSE(response[106]);
2✔
339
    EXPECT_TRUE(response[107]);
2✔
340
    EXPECT_TRUE(response[108]);
2✔
341
    EXPECT_TRUE(response[109]);
2✔
342
    EXPECT_TRUE(response[110]);
2✔
343
    EXPECT_FALSE(response[111]);
2✔
344
    EXPECT_TRUE(response[112]);
2✔
345
    EXPECT_EQ(response.count(113), 0);
2✔
346
    EXPECT_EQ(response.count(114), 0);
2✔
347
    EXPECT_EQ(response.count(115), 0);
2✔
348
    EXPECT_EQ(response.count(116), 0);
2✔
349
    EXPECT_EQ(response.count(117), 0);
2✔
350
    EXPECT_TRUE(response[118]);
2✔
351
    EXPECT_TRUE(response[119]);
2✔
352
}
2✔
353

354
TEST(TModbusExtTest, ReadEventsNoEventsNoConfirmation)
8✔
355
{
356
    TPortMock port;
4✔
357
    TTestEventsVisitor visitor;
4✔
358
    ModbusExt::TEventConfirmationState state;
2✔
359

360
    port.AddResponse({0xFF, 0xFF, 0xFF, 0xFD, 0x46, 0x12, 0x52, 0x5D}); // No events
2✔
361
    bool ret = true;
2✔
362
    EXPECT_NO_THROW(ret = ModbusExt::ReadEvents(port, std::chrono::milliseconds(100), 0, state, visitor));
2✔
363
    EXPECT_FALSE(ret);
2✔
364

365
    EXPECT_EQ(port.Requests.size(), 1);
2✔
366
    auto request = port.Requests.front();
4✔
367

368
    EXPECT_EQ(request.size(), 9);
2✔
369
    EXPECT_EQ(request[0], 0xFD); // broadcast
2✔
370
    EXPECT_EQ(request[1], 0x46); // command
2✔
371
    EXPECT_EQ(request[2], 0x10); // subcommand
2✔
372
    EXPECT_EQ(request[3], 0x00); // min slave id
2✔
373
    EXPECT_EQ(request[4], 0xF8); // max length
2✔
374
    EXPECT_EQ(request[5], 0x00); // slave id (confirmation)
2✔
375
    EXPECT_EQ(request[6], 0x00); // flag (confirmation)
2✔
376

377
    EXPECT_EQ(request[7], 0x79); // CRC16 LSB
2✔
378
    EXPECT_EQ(request[8], 0x5B); // CRC16 MSB
2✔
379

380
    EXPECT_EQ(visitor.Events.size(), 0);
2✔
381
}
2✔
382

383
TEST(TModbusExtTest, ReadEventsWithConfirmation)
8✔
384
{
385
    TPortMock port;
4✔
386
    TTestEventsVisitor visitor;
4✔
387
    ModbusExt::TEventConfirmationState state;
2✔
388

389
    port.AddResponse(
2✔
390
        {0xFF, 0xFF, 0xFF, 0x05, 0x46, 0x11, 0x01, 0x01, 0x06, 0x02, 0x04, 0x01, 0xD0, 0x04, 0x00, 0x2B, 0xAC});
4✔
391
    bool ret = false;
2✔
392
    EXPECT_NO_THROW(ret = ModbusExt::ReadEvents(port, std::chrono::milliseconds(100), 0, state, visitor));
2✔
393
    EXPECT_TRUE(ret);
2✔
394

395
    EXPECT_EQ(port.Requests.size(), 1);
2✔
396
    auto request = port.Requests.front();
4✔
397

398
    EXPECT_EQ(request.size(), 9);
2✔
399
    EXPECT_EQ(request[0], 0xFD); // slave id
2✔
400
    EXPECT_EQ(request[1], 0x46); // command
2✔
401
    EXPECT_EQ(request[2], 0x10); // subcommand
2✔
402
    EXPECT_EQ(request[3], 0x00); // min slave id
2✔
403
    EXPECT_EQ(request[4], 0xF8); // max length
2✔
404
    EXPECT_EQ(request[5], 0x00); // slave id (confirmation)
2✔
405
    EXPECT_EQ(request[6], 0x00); // flag (confirmation)
2✔
406

407
    EXPECT_EQ(request[7], 0x79); // CRC16 LSB
2✔
408
    EXPECT_EQ(request[8], 0x5B); // CRC16 MSB
2✔
409

410
    EXPECT_EQ(visitor.Events.size(), 1);
2✔
411
    EXPECT_EQ(visitor.Events[464], 4);
2✔
412

413
    port.AddResponse({0xFF, 0xFF, 0xFF, 0xFD, 0x46, 0x12, 0x52, 0x5D}); // No events
2✔
414
    visitor.Events.clear();
2✔
415
    port.Requests.clear();
2✔
416
    EXPECT_NO_THROW(ret = ModbusExt::ReadEvents(port, std::chrono::milliseconds(5), 5, state, visitor));
2✔
417
    EXPECT_FALSE(ret);
2✔
418

419
    EXPECT_EQ(port.Requests.size(), 1);
2✔
420
    request = port.Requests.front();
2✔
421

422
    EXPECT_EQ(request.size(), 9);
2✔
423
    EXPECT_EQ(request[0], 0xFD); // slave id
2✔
424
    EXPECT_EQ(request[1], 0x46); // command
2✔
425
    EXPECT_EQ(request[2], 0x10); // subcommand
2✔
426
    EXPECT_EQ(request[3], 0x05); // min slave id
2✔
427
    EXPECT_EQ(request[4], 0x2C); // max length
2✔
428
    EXPECT_EQ(request[5], 0x05); // slave id (confirmation)
2✔
429
    EXPECT_EQ(request[6], 0x01); // flag (confirmation)
2✔
430

431
    EXPECT_EQ(request[7], 0xFB); // CRC16 LSB
2✔
432
    EXPECT_EQ(request[8], 0x3F); // CRC16 MSB
2✔
433

434
    EXPECT_EQ(visitor.Events.size(), 0);
2✔
435
}
2✔
436

437
TEST(TModbusExtTest, ReadEventsReboot)
8✔
438
{
439
    TPortMock port;
4✔
440
    TTestEventsVisitor visitor;
4✔
441
    ModbusExt::TEventConfirmationState state;
2✔
442

443
    port.AddResponse(
2✔
444
        {0xFF, 0xFF, 0xFF, 0xFD, 0x46, 0x11, 0x00, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0xFF, 0x5E}); // Reboot event
4✔
445
    bool ret = false;
2✔
446
    EXPECT_NO_THROW(ret = ModbusExt::ReadEvents(port, std::chrono::milliseconds(100), 0, state, visitor));
2✔
447
    EXPECT_TRUE(ret);
2✔
448

449
    EXPECT_TRUE(visitor.Reboot);
2✔
450
}
2✔
451

452
TEST(TModbusExtTest, GetPacketStart)
8✔
453
{
454
    uint8_t goodPacket[] = {0xFF, 0xFF, 0xFF, 0xFD, 0x46, 0x11, 0x00, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0xFF, 0x5E};
2✔
455
    uint8_t goodPacketWithGlitch[] = {0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFD, 0x46, 0x12, 0x52, 0x5D};
2✔
456
    EXPECT_EQ(ModbusExt::GetPacketStart(goodPacket, sizeof(goodPacket)), goodPacket + 3);
2✔
457
    EXPECT_EQ(ModbusExt::GetPacketStart(goodPacketWithGlitch, sizeof(goodPacketWithGlitch)), goodPacketWithGlitch + 5);
2✔
458

459
    uint8_t notEnoughDataPacket[] = {0xFF, 0xFF, 0xFF, 0xFD, 0x46, 0x11, 0x00, 0x00, 0x04, 0x00, 0x0F, 0x00};
2✔
460
    uint8_t badCrcPacket[] = {0xFF, 0xFF, 0xFF, 0xFD, 0x46, 0x11, 0x00, 0x00, 0x04, 0x00, 0x0F, 0x00, 0x00, 0x11, 0x5E};
2✔
461
    EXPECT_EQ(ModbusExt::GetPacketStart(notEnoughDataPacket, sizeof(notEnoughDataPacket)), nullptr);
2✔
462
    EXPECT_EQ(ModbusExt::GetPacketStart(badCrcPacket, sizeof(badCrcPacket)), nullptr);
2✔
463
}
2✔
464

465
class TModbusExtTraitsTest: public testing::Test
466
{
467
public:
468
    void TestEqual(const std::vector<uint8_t>& b1, const std::vector<uint8_t>& b2) const
1✔
469
    {
470
        ASSERT_EQ(b1.size(), b2.size());
1✔
471

472
        for (size_t i = 0; i < b1.size(); ++i) {
6✔
473
            ASSERT_EQ(b1[i], b2[i]) << i;
5✔
474
        }
475
    }
476
};
477

478
TEST_F(TModbusExtTraitsTest, ReadFrameGood)
8✔
479
{
480
    TPortMock port;
2✔
481
    port.AddResponse({0xfd, 0x46, 0x09, 0xfe, 0xca, 0xe7, 0xe5, 0x06, 0x00, 0x80, 0x00, 0x02, 0x1c, 0xef});
2✔
482
    ModbusExt::TModbusTraits traits;
2✔
483
    traits.SetSn(0xfecae7e5);
2✔
484
    std::chrono::milliseconds t(10);
2✔
485

486
    std::vector<uint8_t> req = {0x06, 0x00, 0x80, 0x00, 0x02};
2✔
487

488
    auto resp = traits.Transaction(port, 0, req, req.size(), t, t);
2✔
489
    ASSERT_EQ(resp.Pdu.size(), req.size());
2✔
490

491
    TestEqual(resp.Pdu, req);
2✔
492
}
493

494
TEST_F(TModbusExtTraitsTest, ReadFrameTooSmallError)
8✔
495
{
496
    TPortMock port;
2✔
497
    port.AddResponse({0xfd, 0x46, 0x09, 0xfe, 0xca});
2✔
498
    ModbusExt::TModbusTraits traits;
2✔
499
    traits.SetSn(0xfecae7e5);
2✔
500
    std::chrono::milliseconds t(10);
2✔
501

502
    std::vector<uint8_t> req = {0x06, 0x00, 0x80, 0x00, 0x02};
2✔
503

504
    ASSERT_THROW(traits.Transaction(port, 0, req, req.size(), t, t), Modbus::TMalformedResponseError);
2✔
505
}
506

507
TEST_F(TModbusExtTraitsTest, ReadFrameInvalidCrc)
8✔
508
{
509
    TPortMock port;
2✔
510
    port.AddResponse({0xfd, 0x46, 0x09, 0xfe, 0xca, 0xe7, 0xe5, 0x06, 0x00, 0x80, 0x00, 0x02, 0x10, 0xef});
2✔
511
    ModbusExt::TModbusTraits traits;
2✔
512
    traits.SetSn(0xfecae7e5);
2✔
513
    std::chrono::milliseconds t(10);
2✔
514

515
    std::vector<uint8_t> req = {0x06, 0x00, 0x80, 0x00, 0x02};
2✔
516

517
    ASSERT_THROW(traits.Transaction(port, 0, req, req.size(), t, t), Modbus::TMalformedResponseError);
2✔
518
}
519

520
TEST_F(TModbusExtTraitsTest, ReadFrameInvalidHeader)
8✔
521
{
522
    TPortMock port;
2✔
523
    std::vector<uint8_t> badHeader =
524
        {0xfe, 0x46, 0x09, 0xfe, 0xca, 0xe7, 0xe5, 0x06, 0x00, 0x80, 0x00, 0x02, 0x00, 0x00};
2✔
525
    port.AddResponse(badHeader);
2✔
526

527
    ModbusExt::TModbusTraits traits;
2✔
528
    traits.SetSn(0xfecae7e5);
2✔
529
    std::chrono::milliseconds t(10);
2✔
530

531
    std::vector<uint8_t> req = {0x06, 0x00, 0x80, 0x00, 0x02};
2✔
532

533
    ASSERT_THROW(
4✔
534
        {
535
            try {
536
                traits.Transaction(port, 0, req, req.size(), t, t);
537
            } catch (const Modbus::TUnexpectedResponseError& e) {
538
                EXPECT_STREQ("invalid response address", e.what());
539
                throw;
540
            }
541
        },
542
        Modbus::TUnexpectedResponseError);
543

544
    badHeader[0] = 0xfd;
2✔
545
    badHeader[1] = 0x45;
2✔
546
    port.AddResponse(badHeader);
2✔
547
    ASSERT_THROW(
4✔
548
        {
549
            try {
550
                traits.Transaction(port, 0, req, req.size(), t, t);
551
            } catch (const Modbus::TUnexpectedResponseError& e) {
552
                EXPECT_STREQ("invalid response command", e.what());
553
                throw;
554
            }
555
        },
556
        Modbus::TUnexpectedResponseError);
557

558
    badHeader[1] = 0x46;
2✔
559
    badHeader[2] = 0x10;
2✔
560
    port.AddResponse(badHeader);
2✔
561
    ASSERT_THROW(
4✔
562
        {
563
            try {
564
                traits.Transaction(port, 0, req, req.size(), t, t);
565
            } catch (const Modbus::TUnexpectedResponseError& e) {
566
                EXPECT_STREQ("invalid response subcommand", e.what());
567
                throw;
568
            }
569
        },
570
        Modbus::TUnexpectedResponseError);
571

572
    badHeader[2] = 0x09;
2✔
573
    badHeader[3] = 0x01;
2✔
574
    port.AddResponse(badHeader);
2✔
575
    ASSERT_THROW(
4✔
576
        {
577
            try {
578
                traits.Transaction(port, 0, req, req.size(), t, t);
579
            } catch (const Modbus::TUnexpectedResponseError& e) {
580
                EXPECT_STREQ("SN mismatch: got 30074853, wait 4274710501", e.what());
581
                throw;
582
            }
583
        },
584
        Modbus::TUnexpectedResponseError);
585
}
586

587
TEST(TModbusExtTest, ScanStartDeviceFound)
8✔
588
{
589
    TPortMock port;
2✔
590
    // DEVICE_FOUND_RESPONSE_SCAN_COMMAND (0x03)
591
    port.AddResponse({0xFD, 0x46, 0x03, 0x12, 0x34, 0x56, 0x78, 0x05, 0x00, 0x00});
2✔
592
    // NO_MORE_DEVICES_RESPONSE_SCAN_COMMAND
593
    port.AddResponse({0xFD, 0x46, 0x04, 0x00, 0x00});
2✔
594

595
    std::vector<ModbusExt::TScannedDevice> scannedDevices;
2✔
596
    EXPECT_NO_THROW(ModbusExt::Scan(port, ModbusExt::TModbusExtCommand::ACTUAL, scannedDevices));
2✔
597
    ASSERT_EQ(scannedDevices.size(), 1);
2✔
598
    EXPECT_EQ(scannedDevices[0].SlaveId, 5);
2✔
599
    EXPECT_EQ(scannedDevices[0].Sn, 0x12345678);
2✔
600
}
601

602
TEST(TModbusExtTest, ScanStartNoMoreDevices)
8✔
603
{
604
    TPortMock port;
4✔
605
    // NO_MORE_DEVICES_RESPONSE_SCAN_COMMAND (0x04)
606
    port.AddResponse({0xFD, 0x46, 0x04, 0x00, 0x00});
2✔
607

608
    std::vector<ModbusExt::TScannedDevice> scannedDevices;
4✔
609
    EXPECT_NO_THROW(ModbusExt::Scan(port, ModbusExt::TModbusExtCommand::ACTUAL, scannedDevices));
2✔
610
    EXPECT_TRUE(scannedDevices.empty());
2✔
611
}
2✔
612

613
TEST(TModbusExtTest, ScanMultipleDevices)
8✔
614
{
615
    TPortMock port;
2✔
616
    port.AddResponse(
2✔
617
        {0xFD, 0x46, 0x03, 0x01, 0x02, 0x03, 0x04, 0x10, 0x00, 0x00}); // DEVICE_FOUND_RESPONSE_SCAN_COMMAND
4✔
618
    port.AddResponse(
2✔
619
        {0xFD, 0x46, 0x03, 0x05, 0x06, 0x07, 0x08, 0x20, 0x00, 0x00}); // DEVICE_FOUND_RESPONSE_SCAN_COMMAND
4✔
620
    port.AddResponse({0xFD, 0x46, 0x04, 0x00, 0x00});                  // NO_MORE_DEVICES_RESPONSE_SCAN_COMMAND
2✔
621

622
    std::vector<ModbusExt::TScannedDevice> scannedDevices;
2✔
623
    EXPECT_NO_THROW(ModbusExt::Scan(port, ModbusExt::TModbusExtCommand::ACTUAL, scannedDevices));
2✔
624
    ASSERT_EQ(scannedDevices.size(), 2);
2✔
625
    EXPECT_EQ(scannedDevices[0].SlaveId, 0x10);
2✔
626
    EXPECT_EQ(scannedDevices[0].Sn, 0x01020304);
2✔
627
    EXPECT_EQ(scannedDevices[1].SlaveId, 0x20);
2✔
628
    EXPECT_EQ(scannedDevices[1].Sn, 0x05060708);
2✔
629
}
630

631
TEST(TModbusExtTest, ScanStartTimeout)
8✔
632
{
633
    TPortMock port;
4✔
634
    port.ThrowTimeout = true;
2✔
635

636
    std::vector<ModbusExt::TScannedDevice> scannedDevices;
4✔
637
    EXPECT_NO_THROW(ModbusExt::Scan(port, ModbusExt::TModbusExtCommand::ACTUAL, scannedDevices));
2✔
638
    EXPECT_TRUE(scannedDevices.empty());
2✔
639
}
2✔
640

641
TEST(TModbusExtTest, ScanInvalidSubCommand)
8✔
642
{
643
    TPortMock port;
4✔
644
    port.AddResponse({0xFD, 0x46, 0x12, 0x00, 0x00});
2✔
645

646
    std::vector<ModbusExt::TScannedDevice> scannedDevices;
4✔
647
    EXPECT_THROW(ModbusExt::Scan(port, ModbusExt::TModbusExtCommand::ACTUAL, scannedDevices),
2✔
648
                 Modbus::TMalformedResponseError);
649
}
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