• 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

92.58
/src/modbus_ext_common.cpp
1
#include "modbus_ext_common.h"
2

3
#include "bin_utils.h"
4
#include "crc16.h"
5
#include "log.h"
6
#include "serial_exc.h"
7
#include <algorithm>
8
#include <stddef.h>
9
#include <stdint.h>
10
#include <vector>
11

12
using namespace BinUtils;
13
using namespace std::chrono;
14
using namespace std::chrono_literals;
15

16
#define LOG(logger) logger.Log() << "[modbus-ext] "
17

18
namespace ModbusExt // modbus extension protocol declarations
19
{
20
    const uint8_t BROADCAST_ADDRESS = 0xFD;
21

22
    // Function codes
23
    const uint8_t START_SCAN_COMMAND = 0x01;
24
    const uint8_t CONTINUE_SCAN_COMMAND = 0x02;
25
    const uint8_t DEVICE_FOUND_RESPONSE_SCAN_COMMAND = 0x03;
26
    const uint8_t NO_MORE_DEVICES_RESPONSE_SCAN_COMMAND = 0x04;
27
    const uint8_t MODBUS_STANDARD_REQUEST_COMMAND = 0x08;
28
    const uint8_t MODBUS_STANDARD_RESPONSE_COMMAND = 0x09;
29
    const uint8_t EVENTS_REQUEST_COMMAND = 0x10;
30
    const uint8_t EVENTS_RESPONSE_COMMAND = 0x11;
31
    const uint8_t NO_EVENTS_RESPONSE_COMMAND = 0x12;
32
    const uint8_t ENABLE_EVENTS_COMMAND = 0x18;
33

34
    const size_t NO_EVENTS_RESPONSE_SIZE = 5;
35
    const size_t EVENTS_RESPONSE_HEADER_SIZE = 6;
36
    const size_t CRC_SIZE = 2;
37
    const size_t MAX_PACKET_SIZE = 256;
38
    const size_t EVENT_HEADER_SIZE = 4;
39
    const size_t MIN_ENABLE_EVENTS_RESPONSE_SIZE = 6;
40
    const size_t MIN_ENABLE_EVENTS_REC_SIZE = 5;
41
    // const size_t EXCEPTION_SIZE = 5;
42
    const size_t MODBUS_STANDARD_COMMAND_HEADER_SIZE = 7; // 0xFD 0x46 0x08 (3b) + SN (4b)
43
    const size_t NO_MORE_DEVICES_RESPONSE_SIZE = 5;
44
    const size_t DEVICE_FOUND_RESPONSE_SIZE = 10;
45

46
    const size_t EVENTS_REQUEST_MAX_BYTES = MAX_PACKET_SIZE - EVENTS_RESPONSE_HEADER_SIZE - CRC_SIZE;
47
    const size_t ARBITRATION_HEADER_MAX_BYTES = 32;
48

49
    const size_t SLAVE_ID_POS = 0;
50
    const size_t COMMAND_POS = 1;
51
    const size_t SUB_COMMAND_POS = 2;
52
    const size_t EXCEPTION_CODE_POS = 2;
53

54
    // const size_t EVENTS_RESPONSE_SLAVE_ID_POS = 0;
55
    const size_t EVENTS_RESPONSE_CONFIRM_FLAG_POS = 3;
56
    const size_t EVENTS_RESPONSE_DATA_SIZE_POS = 5;
57
    const size_t EVENTS_RESPONSE_DATA_POS = 6;
58

59
    const size_t EVENT_SIZE_POS = 0;
60
    const size_t EVENT_TYPE_POS = 1;
61
    const size_t EVENT_ID_POS = 2;
62
    const size_t EVENT_DATA_POS = 4;
63
    const size_t MAX_EVENT_SIZE = 6;
64

65
    const size_t SCAN_SN_POS = 3;
66
    const size_t SCAN_SLAVE_ID_POS = 7;
67

68
    const size_t ENABLE_EVENTS_RESPONSE_DATA_SIZE_POS = 3;
69
    const size_t ENABLE_EVENTS_RESPONSE_DATA_POS = 4;
70

71
    const size_t MODBUS_STANDARD_COMMAND_RESPONSE_SN_POS = 3;
72
    const size_t MODBUS_STANDARD_COMMAND_PDU_POS = 7;
73

74
    // const size_t ENABLE_EVENTS_REC_TYPE_POS = 0;
75
    // const size_t ENABLE_EVENTS_REC_ADDR_POS = 1;
76
    // const size_t ENABLE_EVENTS_REC_STATE_POS = 3;
77

78
    // max(3.5 symbols, (20 bits + 800us)) + 9 * max(13 bits, 12 bits + 50us)
79
    std::chrono::milliseconds GetTimeoutForEvents(const TPort& port)
95✔
80
    {
81
        const auto cmdTime =
82
            std::max(port.GetSendTimeBytes(Modbus::STANDARD_FRAME_TIMEOUT_BYTES), port.GetSendTimeBits(20) + 800us);
95✔
83
        const auto arbitrationTime = 9 * std::max(port.GetSendTimeBits(13), port.GetSendTimeBits(12) + 50us);
95✔
84
        return std::chrono::ceil<std::chrono::milliseconds>(cmdTime + arbitrationTime);
190✔
85
    }
86

87
    // For 0x46 command: max(3.5 symbols, (20 bits + 800us)) + 33 * max(13 bits, 12 bits + 50us)
88
    // For 0x60 command: (44 bits) + 33 * 20 bits
89
    std::chrono::microseconds GetTimeoutForScan(const TPort& port, TModbusExtCommand command)
8✔
90
    {
91
        if (command == TModbusExtCommand::DEPRECATED) {
8✔
92
            return port.GetSendTimeBits(44) + 33 * port.GetSendTimeBits(20);
×
93
        }
94
        const auto cmdTime =
95
            std::max(port.GetSendTimeBytes(Modbus::STANDARD_FRAME_TIMEOUT_BYTES), port.GetSendTimeBits(20) + 800us);
8✔
96
        const auto arbitrationTime = 33 * std::max(port.GetSendTimeBits(13), port.GetSendTimeBits(12) + 50us);
8✔
97
        return cmdTime + arbitrationTime;
8✔
98
    }
99

100
    uint8_t GetMaxReadEventsResponseSize(const TPort& port, std::chrono::milliseconds maxTime)
95✔
101
    {
102
        if (maxTime.count() <= 0) {
95✔
103
            return MAX_EVENT_SIZE;
×
104
        }
105
        auto timePerByte = port.GetSendTimeBytes(1);
95✔
106
        if (timePerByte.count() == 0) {
95✔
107
            return EVENTS_REQUEST_MAX_BYTES;
×
108
        }
109
        size_t maxBytes = std::chrono::duration_cast<std::chrono::microseconds>(maxTime).count() / timePerByte.count();
95✔
110
        if (maxBytes > MAX_PACKET_SIZE) {
95✔
111
            maxBytes = MAX_PACKET_SIZE;
91✔
112
        }
113
        if (maxBytes <= EVENTS_RESPONSE_HEADER_SIZE + MAX_EVENT_SIZE + CRC_SIZE) {
95✔
114
            return MAX_EVENT_SIZE;
×
115
        }
116
        maxBytes -= EVENTS_RESPONSE_HEADER_SIZE + CRC_SIZE;
95✔
117
        if (maxBytes > std::numeric_limits<uint8_t>::max()) {
95✔
118
            return std::numeric_limits<uint8_t>::max();
×
119
        }
120
        return static_cast<uint8_t>(maxBytes);
95✔
121
    }
122

123
    std::vector<uint8_t> MakeReadEventsRequest(const TEventConfirmationState& state,
95✔
124
                                               uint8_t startingSlaveId = 0,
125
                                               uint8_t maxBytes = EVENTS_REQUEST_MAX_BYTES)
126
    {
127
        std::vector<uint8_t> request({BROADCAST_ADDRESS, TModbusExtCommand::ACTUAL, EVENTS_REQUEST_COMMAND});
95✔
128
        auto it = std::back_inserter(request);
95✔
129
        Append(it, startingSlaveId);
95✔
130
        Append(it, maxBytes);
95✔
131
        Append(it, state.SlaveId);
95✔
132
        Append(it, state.Flag);
95✔
133
        AppendBigEndian(it, CRC16::CalculateCRC16(request.data(), request.size()));
95✔
134
        return request;
190✔
135
    }
136

137
    std::vector<uint8_t> MakeScanRequest(uint8_t fastModbusCommand, uint8_t scanCommand)
8✔
138
    {
139
        std::vector<uint8_t> request({BROADCAST_ADDRESS, fastModbusCommand, scanCommand});
8✔
140
        AppendBigEndian(std::back_inserter(request), CRC16::CalculateCRC16(request.data(), request.size()));
8✔
141
        return request;
8✔
142
    }
143

144
    void CheckCRC16(const uint8_t* packet, size_t size)
190✔
145
    {
146
        if (size < CRC_SIZE) {
190✔
147
            throw Modbus::TMalformedResponseError("invalid packet size: " + std::to_string(size));
×
148
        }
149

150
        auto crc = GetBigEndian<uint16_t>(packet + size - CRC_SIZE, packet + size);
190✔
151
        if (crc != CRC16::CalculateCRC16(packet, size - CRC_SIZE)) {
190✔
152
            throw Modbus::TMalformedResponseError("invalid crc");
1✔
153
        }
154
    }
189✔
155

156
    bool IsModbusExtCommand(uint8_t command)
576✔
157
    {
158
        return command == TModbusExtCommand::ACTUAL || command == TModbusExtCommand::DEPRECATED;
576✔
159
    }
160

161
    bool IsModbusExtPacket(const uint8_t* buf, size_t size)
601✔
162
    {
163
        if ((buf[0] == 0xFF) || (SUB_COMMAND_POS >= size) || (!IsModbusExtCommand(buf[COMMAND_POS]))) {
601✔
164
            return false;
215✔
165
        }
166

167
        switch (buf[SUB_COMMAND_POS]) {
386✔
168
            case EVENTS_RESPONSE_COMMAND: {
16✔
169
                if ((size <= EVENTS_RESPONSE_DATA_SIZE_POS) ||
16✔
170
                    (size != EVENTS_RESPONSE_HEADER_SIZE + buf[EVENTS_RESPONSE_DATA_SIZE_POS] + CRC_SIZE))
13✔
171
                {
172
                    return false;
10✔
173
                }
174
                break;
6✔
175
            }
176
            case NO_EVENTS_RESPONSE_COMMAND: {
316✔
177
                if (size != NO_EVENTS_RESPONSE_SIZE) {
316✔
178
                    return false;
156✔
179
                }
180
                break;
160✔
181
            }
182
            case NO_MORE_DEVICES_RESPONSE_SCAN_COMMAND: {
3✔
183
                if (size != NO_MORE_DEVICES_RESPONSE_SIZE) {
3✔
184
                    return false;
×
185
                }
186
                break;
3✔
187
            }
188
            case DEVICE_FOUND_RESPONSE_SCAN_COMMAND: {
3✔
189
                if (size != DEVICE_FOUND_RESPONSE_SIZE) {
3✔
190
                    return false;
×
191
                }
192
                break;
3✔
193
            }
194
            default: {
48✔
195
                // Unexpected sub command
196
                return false;
48✔
197
            }
198
        }
199

200
        try {
201
            CheckCRC16(buf, size);
172✔
202
        } catch (const Modbus::TMalformedResponseError& err) {
1✔
203
            return false;
1✔
204
        }
205
        return true;
171✔
206
    }
207

208
    const uint8_t* GetPacketStart(const uint8_t* data, size_t size)
998✔
209
    {
210
        while (size > SUB_COMMAND_POS) {
998✔
211
            if (IsModbusExtPacket(data, size)) {
601✔
212
                return data;
171✔
213
            }
214
            ++data;
430✔
215
            --size;
430✔
216
        }
217
        return nullptr;
397✔
218
    }
219

220
    TPort::TFrameCompletePred ExpectFastModbus()
103✔
221
    {
222
        return [=](uint8_t* buf, size_t size) { return GetPacketStart(buf, size) != nullptr; };
565✔
223
    }
224

225
    void IterateOverEvents(uint8_t slaveId, const uint8_t* data, size_t size, IEventsVisitor& eventVisitor)
6✔
226
    {
227
        while (size != 0) {
6✔
228
            size_t dataSize = data[EVENT_SIZE_POS];
3✔
229
            size_t eventSize = dataSize + EVENT_HEADER_SIZE;
3✔
230
            if (eventSize > size) {
3✔
231
                throw Modbus::TMalformedResponseError("invalid event data size");
×
232
            }
233
            eventVisitor.Event(slaveId,
3✔
234
                               data[EVENT_TYPE_POS],
3✔
235
                               GetFromBigEndian<uint16_t>(data + EVENT_ID_POS),
236
                               data + EVENT_DATA_POS,
237
                               dataSize);
3✔
238
            data += eventSize;
3✔
239
            size -= eventSize;
3✔
240
        }
241
    }
3✔
242

243
    bool IsRegisterEvent(uint8_t eventType)
40✔
244
    {
245
        switch (eventType) {
40✔
246
            case TEventType::COIL:
13✔
247
            case TEventType::DISCRETE:
248
            case TEventType::HOLDING:
249
            case TEventType::INPUT:
250
                return true;
13✔
251
            default:
27✔
252
                return false;
27✔
253
        }
254
    }
255

256
    bool ReadEvents(TPort& port,
95✔
257
                    std::chrono::milliseconds maxEventsReadTime,
258
                    uint8_t startingSlaveId,
259
                    TEventConfirmationState& state,
260
                    IEventsVisitor& eventVisitor)
261
    {
262
        // TODO: Count request and arbitration.
263
        //       maxEventsReadTime limits not only response, but total request-response time.
264
        //       So request and arbitration time must be subtracted from time for response
265
        auto maxBytes = GetMaxReadEventsResponseSize(port, maxEventsReadTime);
95✔
266

267
        auto req = MakeReadEventsRequest(state, startingSlaveId, maxBytes);
190✔
268
        auto frameTimeout = port.GetSendTimeBytes(Modbus::STANDARD_FRAME_TIMEOUT_BYTES);
95✔
269
        port.SleepSinceLastInteraction(frameTimeout);
95✔
270
        port.WriteBytes(req);
95✔
271

272
        const auto timeout = GetTimeoutForEvents(port);
95✔
273
        std::array<uint8_t, MAX_PACKET_SIZE + ARBITRATION_HEADER_MAX_BYTES> res;
274
        auto rc = port.ReadFrame(res.data(), res.size(), timeout, timeout, ExpectFastModbus()).Count;
95✔
275
        port.SleepSinceLastInteraction(frameTimeout);
95✔
276

277
        const uint8_t* packet = GetPacketStart(res.data(), rc);
95✔
278
        if (packet == nullptr) {
95✔
279
            throw Modbus::TMalformedResponseError("invalid packet");
12✔
280
        }
281

282
        switch (packet[SUB_COMMAND_POS]) {
83✔
283
            case EVENTS_RESPONSE_COMMAND: {
3✔
284
                state.SlaveId = packet[SLAVE_ID_POS];
3✔
285
                state.Flag = packet[EVENTS_RESPONSE_CONFIRM_FLAG_POS];
3✔
286
                IterateOverEvents(packet[SLAVE_ID_POS],
3✔
287
                                  packet + EVENTS_RESPONSE_DATA_POS,
288
                                  packet[EVENTS_RESPONSE_DATA_SIZE_POS],
3✔
289
                                  eventVisitor);
290
                return true;
3✔
291
            }
292
            case NO_EVENTS_RESPONSE_COMMAND: {
80✔
293
                if (packet[0] != BROADCAST_ADDRESS) {
80✔
294
                    throw Modbus::TMalformedResponseError("invalid address");
×
295
                }
296
                return false;
80✔
297
            }
298
            default: {
×
299
                throw Modbus::TMalformedResponseError("invalid sub command");
×
300
            }
301
        }
302
    }
303

304
    bool HasSpaceForEnableEventRecord(const std::vector<uint8_t>& request)
72✔
305
    {
306
        return request.size() + request[ENABLE_EVENTS_RESPONSE_DATA_SIZE_POS] + CRC_SIZE + MIN_ENABLE_EVENTS_REC_SIZE <
72✔
307
               request.capacity();
72✔
308
    }
309

310
    class TBitIterator
311
    {
312
        uint8_t* Data;
313
        const uint8_t* End;
314
        size_t BitIndex;
315

316
    public:
317
        TBitIterator(uint8_t* data, size_t size): Data(data), End(data + size), BitIndex(0)
16✔
318
        {}
16✔
319

320
        void NextBit()
21✔
321
        {
322
            ++BitIndex;
21✔
323
            if (BitIndex == 8) {
21✔
324
                NextByte();
2✔
325
            }
326
        }
21✔
327

328
        void NextByte()
34✔
329
        {
330
            ++Data;
34✔
331
            BitIndex = 0;
34✔
332
            if (Data > End) {
34✔
333
                throw Modbus::TMalformedResponseError("not enough data in enable events response");
×
334
            }
335
        }
34✔
336

337
        bool GetBit() const
53✔
338
        {
339
            return (*Data >> BitIndex) & 0x01;
53✔
340
        }
341
    };
342

343
    void TEventsEnabler::EnableEvents()
18✔
344
    {
345
        Port.WriteBytes(Request);
18✔
346

347
        // Use response timeout from MR6C template
348
        auto rc = Port.ReadFrame(Response.data(), Request.size(), 8ms, FrameTimeout).Count;
18✔
349

350
        CheckCRC16(Response.data(), rc);
18✔
351

352
        // Old firmwares can send any command with exception bit
353
        if (Modbus::IsException(Response[COMMAND_POS])) {
18✔
354
            throw Modbus::TModbusExceptionError(Response[EXCEPTION_CODE_POS]);
1✔
355
        }
356

357
        if (rc < MIN_ENABLE_EVENTS_RESPONSE_SIZE) {
17✔
358
            throw Modbus::TMalformedResponseError("invalid packet size: " + std::to_string(rc));
×
359
        }
360

361
        if (Response[SLAVE_ID_POS] != SlaveId) {
17✔
362
            throw Modbus::TMalformedResponseError("invalid slave id");
×
363
        }
364

365
        if (Response[SUB_COMMAND_POS] != ENABLE_EVENTS_COMMAND) {
17✔
366
            throw Modbus::TMalformedResponseError("invalid sub command");
1✔
367
        }
368

369
        TBitIterator dataIt(Response.data() + ENABLE_EVENTS_RESPONSE_DATA_POS - 1,
16✔
370
                            Response[ENABLE_EVENTS_RESPONSE_DATA_SIZE_POS]);
16✔
371

372
        TEventType lastType = TEventType::REBOOT;
16✔
373
        uint16_t lastRegAddr = 0;
16✔
374
        uint16_t lastDataAddr = 0;
16✔
375
        bool firstRegister = true;
16✔
376

377
        for (auto regIt = SettingsStart; regIt != SettingsEnd; ++regIt) {
67✔
378
            if (firstRegister || (lastType != regIt->Type) || (lastRegAddr + MaxRegDistance < regIt->Addr)) {
51✔
379
                dataIt.NextByte();
32✔
380
                lastDataAddr = regIt->Addr;
32✔
381
                lastType = regIt->Type;
32✔
382
                Visitor(lastType, lastDataAddr, dataIt.GetBit());
32✔
383
                firstRegister = false;
32✔
384
            } else {
385
                do {
2✔
386
                    dataIt.NextBit();
21✔
387
                    ++lastDataAddr;
21✔
388
                    Visitor(lastType, lastDataAddr, dataIt.GetBit());
21✔
389
                } while (lastDataAddr != regIt->Addr);
21✔
390
            }
391
            lastRegAddr = regIt->Addr;
51✔
392
        }
393
    }
16✔
394

395
    void TEventsEnabler::ClearRequest()
18✔
396
    {
397
        Request.erase(Request.begin() + ENABLE_EVENTS_RESPONSE_DATA_POS, Request.end());
18✔
398
        Request[ENABLE_EVENTS_RESPONSE_DATA_SIZE_POS] = 0;
18✔
399
    }
18✔
400

401
    TEventsEnabler::TEventsEnabler(uint8_t slaveId,
50✔
402
                                   TPort& port,
403
                                   TEventsEnabler::TVisitorFn visitor,
404
                                   TEventsEnablerFlags flags)
50✔
405
        : SlaveId(slaveId),
406
          Port(port),
407
          MaxRegDistance(1),
408
          Visitor(visitor)
50✔
409
    {
410
        if (flags == TEventsEnablerFlags::DISABLE_EVENTS_IN_HOLES) {
50✔
411
            MaxRegDistance = MIN_ENABLE_EVENTS_REC_SIZE;
47✔
412
        }
413
        Request.reserve(MAX_PACKET_SIZE);
50✔
414
        Append(std::back_inserter(Request), {SlaveId, TModbusExtCommand::ACTUAL, ENABLE_EVENTS_COMMAND, 0});
50✔
415
        Response.reserve(MAX_PACKET_SIZE);
50✔
416
        FrameTimeout =
417
            std::chrono::ceil<std::chrono::milliseconds>(port.GetSendTimeBytes(Modbus::STANDARD_FRAME_TIMEOUT_BYTES));
50✔
418
    }
50✔
419

420
    void TEventsEnabler::AddRegister(uint16_t addr, TEventType type, TEventPriority priority)
54✔
421
    {
422
        Settings.emplace_back(TRegisterToEnable{type, addr, priority});
54✔
423
    }
54✔
424

425
    void TEventsEnabler::SendSingleRequest()
18✔
426
    {
427
        ClearRequest();
18✔
428
        auto requestBack = std::back_inserter(Request);
18✔
429
        TEventType lastType = TEventType::REBOOT;
18✔
430
        uint16_t lastAddr = 0;
18✔
431
        auto regIt = SettingsEnd;
18✔
432
        size_t regCountPos;
433
        bool firstRegister = true;
18✔
434
        for (; HasSpaceForEnableEventRecord(Request) && regIt != Settings.cend(); ++regIt) {
72✔
435
            if (firstRegister || (lastType != regIt->Type) || (lastAddr + MaxRegDistance < regIt->Addr)) {
54✔
436
                Append(requestBack, static_cast<uint8_t>(regIt->Type));
35✔
437
                AppendBigEndian(requestBack, regIt->Addr);
35✔
438
                Append(requestBack, static_cast<uint8_t>(1));
35✔
439
                regCountPos = Request.size() - 1;
35✔
440
                Append(requestBack, static_cast<uint8_t>(regIt->Priority));
35✔
441
                Request[ENABLE_EVENTS_RESPONSE_DATA_SIZE_POS] += MIN_ENABLE_EVENTS_REC_SIZE;
35✔
442
                firstRegister = false;
35✔
443
            } else {
444
                const auto nRegs = static_cast<size_t>(regIt->Addr - lastAddr);
19✔
445
                Request[regCountPos] += nRegs;
19✔
446
                for (size_t i = 0; i + 1 < nRegs; ++i) {
21✔
447
                    Append(requestBack, static_cast<uint8_t>(TEventPriority::DISABLE));
2✔
448
                }
449
                Append(requestBack, static_cast<uint8_t>(regIt->Priority));
19✔
450
                Request[ENABLE_EVENTS_RESPONSE_DATA_SIZE_POS] += nRegs;
19✔
451
            }
452
            lastType = regIt->Type;
54✔
453
            lastAddr = regIt->Addr;
54✔
454
        }
455

456
        SettingsStart = SettingsEnd;
18✔
457
        SettingsEnd = regIt;
18✔
458

459
        AppendBigEndian(std::back_inserter(Request), CRC16::CalculateCRC16(Request.data(), Request.size()));
18✔
460
        EnableEvents();
18✔
461
    }
16✔
462

463
    void TEventsEnabler::SendRequests()
18✔
464
    {
465
        if (Settings.empty()) {
18✔
466
            return;
×
467
        }
468
        std::sort(Settings.begin(), Settings.end(), [](const auto& a, const auto& b) {
18✔
469
            if (a.Type == b.Type) {
72✔
470
                return a.Addr < b.Addr;
22✔
471
            }
472
            return a.Type < b.Type;
50✔
473
        });
474
        auto last = std::unique(Settings.begin(), Settings.end(), [](const auto& a, const auto& b) {
36✔
475
            return a.Type == b.Type && a.Addr == b.Addr;
36✔
476
        });
18✔
477
        Settings.erase(last, Settings.end());
18✔
478
        for (SettingsEnd = SettingsStart = Settings.cbegin(); SettingsEnd != Settings.cend();) {
34✔
479
            SendSingleRequest();
18✔
480
        }
481
    }
482

483
    bool TEventsEnabler::HasEventsToSetup() const
46✔
484
    {
485
        return !Settings.empty();
46✔
486
    }
487

488
    void TEventConfirmationState::Reset()
78✔
489
    {
490
        Flag = 0;
78✔
491
        SlaveId = 0;
78✔
492
    }
78✔
493

494
    // TModbusTraits
495

496
    TModbusTraits::TModbusTraits(): Sn(0), ModbusExtCommand(TModbusExtCommand::ACTUAL)
4✔
497
    {}
4✔
498

499
    TPort::TFrameCompletePred TModbusTraits::ExpectNBytes(size_t n) const
7✔
500
    {
501
        return [=](uint8_t* buf, size_t size) {
×
502
            if (size < MODBUS_STANDARD_COMMAND_HEADER_SIZE + 1)
×
503
                return false;
×
504
            if (Modbus::IsException(buf[MODBUS_STANDARD_COMMAND_PDU_POS])) // GetPDU
×
505
                return size >= MODBUS_STANDARD_COMMAND_HEADER_SIZE + Modbus::EXCEPTION_RESPONSE_PDU_SIZE + CRC_SIZE;
×
506
            return size >= n;
×
507
        };
7✔
508
    }
509

510
    size_t TModbusTraits::GetPacketSize(size_t pduSize) const
14✔
511
    {
512
        return MODBUS_STANDARD_COMMAND_HEADER_SIZE + CRC_SIZE + pduSize;
14✔
513
    }
514

515
    void TModbusTraits::FinalizeRequest(std::vector<uint8_t>& request)
7✔
516
    {
517
        request[0] = BROADCAST_ADDRESS;
7✔
518
        request[1] = ModbusExtCommand;
7✔
519
        request[2] = MODBUS_STANDARD_REQUEST_COMMAND;
7✔
520
        request[3] = static_cast<uint8_t>(Sn >> 24);
7✔
521
        request[4] = static_cast<uint8_t>(Sn >> 16);
7✔
522
        request[5] = static_cast<uint8_t>(Sn >> 8);
7✔
523
        request[6] = static_cast<uint8_t>(Sn);
7✔
524
        auto crc = CRC16::CalculateCRC16(request.data(), request.size() - CRC_SIZE);
7✔
525
        request[request.size() - 2] = static_cast<uint8_t>(crc >> 8);
7✔
526
        request[request.size() - 1] = static_cast<uint8_t>(crc);
7✔
527
    }
7✔
528

529
    TReadFrameResult TModbusTraits::ReadFrame(TPort& port,
7✔
530
                                              const milliseconds& responseTimeout,
531
                                              const milliseconds& frameTimeout,
532
                                              std::vector<uint8_t>& res) const
533
    {
534
        auto rc = port.ReadFrame(res.data(),
535
                                 res.size(),
536
                                 responseTimeout + frameTimeout,
7✔
537
                                 frameTimeout,
538
                                 ExpectNBytes(res.size()));
14✔
539

540
        if (rc.Count < MODBUS_STANDARD_COMMAND_HEADER_SIZE + CRC_SIZE) {
7✔
541
            throw Modbus::TMalformedResponseError("invalid data size");
1✔
542
        }
543

544
        uint16_t crc = (res[rc.Count - 2] << 8) + res[rc.Count - 1];
6✔
545
        if (crc != CRC16::CalculateCRC16(res.data(), rc.Count - CRC_SIZE)) {
6✔
546
            throw Modbus::TMalformedResponseError("invalid crc");
1✔
547
        }
548

549
        if (res[0] != BROADCAST_ADDRESS) {
5✔
550
            throw Modbus::TUnexpectedResponseError("invalid response address");
1✔
551
        }
552

553
        if (res[1] != ModbusExtCommand) {
4✔
554
            throw Modbus::TUnexpectedResponseError("invalid response command");
1✔
555
        }
556

557
        if (res[2] != MODBUS_STANDARD_RESPONSE_COMMAND) {
3✔
558
            throw Modbus::TUnexpectedResponseError("invalid response subcommand");
1✔
559
        }
560

561
        auto responseSn = GetBigEndian<uint32_t>(res.cbegin() + MODBUS_STANDARD_COMMAND_RESPONSE_SN_POS,
4✔
562
                                                 res.cbegin() + MODBUS_STANDARD_COMMAND_HEADER_SIZE);
2✔
563
        if (responseSn != Sn) {
2✔
564
            throw Modbus::TUnexpectedResponseError("SN mismatch: got " + std::to_string(responseSn) + ", wait " +
3✔
565
                                                   std::to_string(Sn));
4✔
566
        }
567
        return rc;
1✔
568
    }
569

570
    Modbus::TReadResult TModbusTraits::Transaction(TPort& port,
7✔
571
                                                   uint8_t slaveId,
572
                                                   const std::vector<uint8_t>& requestPdu,
573
                                                   size_t expectedResponsePduSize,
574
                                                   const std::chrono::milliseconds& responseTimeout,
575
                                                   const std::chrono::milliseconds& frameTimeout)
576
    {
577
        std::vector<uint8_t> request(GetPacketSize(requestPdu.size()));
14✔
578
        std::copy(requestPdu.begin(), requestPdu.end(), request.begin() + MODBUS_STANDARD_COMMAND_PDU_POS);
7✔
579
        FinalizeRequest(request);
7✔
580

581
        port.WriteBytes(request.data(), request.size());
7✔
582

583
        std::vector<uint8_t> response(GetPacketSize(expectedResponsePduSize));
14✔
584

585
        auto readRes = ReadFrame(port, responseTimeout, frameTimeout, response);
7✔
586

587
        Modbus::TReadResult res;
1✔
588
        res.ResponseTime = readRes.ResponseTime;
1✔
589
        res.Pdu.assign(response.begin() + MODBUS_STANDARD_COMMAND_HEADER_SIZE,
2✔
590
                       response.begin() + (readRes.Count - CRC_SIZE));
1✔
591
        return res;
2✔
592
    }
593

594
    void TModbusTraits::SetSn(uint32_t sn)
4✔
595
    {
596
        Sn = sn;
4✔
597
    }
4✔
598

599
    void TModbusTraits::SetModbusExtCommand(TModbusExtCommand command)
×
600
    {
601
        ModbusExtCommand = command;
×
602
    }
603

604
    std::optional<TScannedDevice> SendScanRequest(TPort& port, TModbusExtCommand modbusExtCommand, uint8_t scanCommand)
8✔
605
    {
606
        auto req = MakeScanRequest(modbusExtCommand, scanCommand);
16✔
607
        const auto standardFrameTimeout = port.GetSendTimeBytes(Modbus::STANDARD_FRAME_TIMEOUT_BYTES);
8✔
608
        port.SleepSinceLastInteraction(standardFrameTimeout);
8✔
609
        port.WriteBytes(req);
8✔
610

611
        const auto scanFrameTimeout = GetTimeoutForScan(port, modbusExtCommand);
8✔
612
        const auto responseTimeout = scanFrameTimeout + port.GetSendTimeBytes(1);
8✔
613
        std::array<uint8_t, MAX_PACKET_SIZE + ARBITRATION_HEADER_MAX_BYTES> response;
614
        size_t rc =
615
            port.ReadFrame(response.data(), response.size(), responseTimeout, scanFrameTimeout, ExpectFastModbus())
9✔
616
                .Count;
7✔
617

618
        const uint8_t* packet = GetPacketStart(response.data(), rc);
7✔
619
        if (packet == nullptr) {
7✔
NEW
620
            throw Modbus::TMalformedResponseError("invalid packet");
×
621
        }
622

623
        switch (packet[SUB_COMMAND_POS]) {
7✔
624
            case DEVICE_FOUND_RESPONSE_SCAN_COMMAND: {
3✔
625
                return std::optional<TScannedDevice>{
626
                    TScannedDevice{packet[SCAN_SLAVE_ID_POS], GetFromBigEndian<uint32_t>(packet + SCAN_SN_POS)}};
6✔
627
            }
628
            case NO_MORE_DEVICES_RESPONSE_SCAN_COMMAND: {
3✔
629
                return std::nullopt;
3✔
630
            }
631
            default: {
1✔
632
                throw Modbus::TMalformedResponseError("invalid sub command");
1✔
633
            }
634
        }
635
    }
636

637
    std::optional<TScannedDevice> ScanStart(TPort& port, TModbusExtCommand modbusExtCommand)
5✔
638
    {
639
        try {
640
            return SendScanRequest(port, modbusExtCommand, START_SCAN_COMMAND);
5✔
641
        } catch (const TResponseTimeoutException& ex) {
2✔
642
            // It is ok. No Fast Modbus capable devices on port.
643
            LOG(Debug) << "No Fast Modbus capable devices on port " << port.GetDescription();
1✔
644
            return std::nullopt;
1✔
645
        }
646
    }
647

648
    std::optional<TScannedDevice> ScanNext(TPort& port, TModbusExtCommand modbusExtCommand)
3✔
649
    {
650
        return SendScanRequest(port, modbusExtCommand, CONTINUE_SCAN_COMMAND);
3✔
651
    }
652

653
    void Scan(TPort& port, TModbusExtCommand modbusExtCommand, std::vector<TScannedDevice>& scannedDevices)
5✔
654
    {
655
        auto res = ScanStart(port, modbusExtCommand);
5✔
656
        if (!res) {
4✔
657
            return;
2✔
658
        }
659
        scannedDevices.push_back(*res);
2✔
660
        while (true) {
661
            res = ScanNext(port, modbusExtCommand);
3✔
662
            if (!res) {
3✔
663
                return;
2✔
664
            }
665
            scannedDevices.push_back(*res);
1✔
666
        }
667
    }
668
}
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