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

wirenboard / wb-mqtt-serial / 707

06 Oct 2025 09:51AM UTC coverage: 73.346% (-0.05%) from 73.391%
707

push

github

web-flow
Add support for WB-MGE v.3 (#1002)

* Add protocol parameter to Scan RPC.
* Add WB-MGE v.3 detection.
* Enable Fast Modbus events for devices connected through WB-MGE v.3 in Modbus TCP gateway mode.

6750 of 9574 branches covered (70.5%)

321 of 399 new or added lines in 22 files covered. (80.45%)

5 existing lines in 4 files now uncovered.

12796 of 17446 relevant lines covered (73.35%)

410.74 hits per line

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

92.84
/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 HAS_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 CRC_SIZE = 2;
35
    const size_t RTU_MAX_PACKET_SIZE = 256;
36
    const size_t RTU_HEADER_SIZE = 1;
37

38
    // PDU
39
    const size_t PDU_MODBUS_STANDARD_COMMAND_HEADER_SIZE = 6; // 0x46 0x08 (3b) + SN (4b)
40
    const size_t PDU_ENABLE_EVENTS_MIN_REC_SIZE = 5;
41
    const size_t PDU_EVENT_MAX_SIZE = 6;
42
    const size_t PDU_EVENTS_RESPONSE_NO_EVENTS_SIZE = 2; // 0x46 0x12
43
    const size_t PDU_EVENTS_RESPONSE_MIN_SIZE = PDU_EVENTS_RESPONSE_NO_EVENTS_SIZE;
44
    const size_t PDU_EVENTS_RESPONSE_HEADER_SIZE = 5; // 0x46 0x11 Flag Nevents DataSize
45
    const size_t PDU_EVENTS_REQUEST_MAX_BYTES =
46
        RTU_MAX_PACKET_SIZE - PDU_EVENTS_RESPONSE_HEADER_SIZE - CRC_SIZE - RTU_HEADER_SIZE;
47

48
    const size_t PDU_SUB_COMMAND_POS = 1;
49

50
    const size_t PDU_SCAN_RESPONSE_NO_MORE_DEVICES_SIZE = 2;
51
    const size_t PDU_SCAN_RESPONSE_DEVICE_FOUND_SIZE = 7;
52
    const size_t PDU_SCAN_RESPONSE_MIN_SIZE = PDU_SCAN_RESPONSE_NO_MORE_DEVICES_SIZE;
53

54
    const size_t PDU_SCAN_RESPONSE_SN_POS = 2;
55
    const size_t PDU_SCAN_RESPONSE_SLAVE_ID_POS = 6;
56

57
    const size_t PDU_EVENTS_RESPONSE_CONFIRM_FLAG_POS = 2;
58
    const size_t PDU_EVENTS_RESPONSE_DATA_SIZE_POS = 4;
59
    const size_t PDU_EVENTS_RESPONSE_DATA_POS = 5;
60

61
    const size_t PDU_ENABLE_EVENTS_RESPONSE_MIN_SIZE = 3;
62
    const size_t PDU_ENABLE_EVENTS_RESPONSE_DATA_SIZE_POS = 2;
63
    const size_t PDU_ENABLE_EVENTS_RESPONSE_DATA_POS = 3;
64

65
    const size_t PDU_MODBUS_STANDARD_COMMAND_RESPONSE_SN_POS = 2;
66

67
    // RTU
68
    const size_t RTU_ARBITRATION_HEADER_MAX_BYTES = 32;
69

70
    const size_t RTU_EVENTS_RESPONSE_NO_EVENTS_SIZE = PDU_EVENTS_RESPONSE_NO_EVENTS_SIZE + CRC_SIZE + RTU_HEADER_SIZE;
71
    const size_t RTU_SCAN_RESPONSE_NO_MORE_DEVICES_SIZE =
72
        PDU_SCAN_RESPONSE_NO_MORE_DEVICES_SIZE + CRC_SIZE + RTU_HEADER_SIZE;
73
    const size_t RTU_SCAN_RESPONSE_DEVICE_FOUND_SIZE = PDU_SCAN_RESPONSE_DEVICE_FOUND_SIZE + CRC_SIZE + RTU_HEADER_SIZE;
74

75
    const size_t RTU_COMMAND_POS = 1;
76
    const size_t RTU_SUB_COMMAND_POS = 2;
77

78
    const size_t RTU_EVENTS_RESPONSE_DATA_SIZE_POS = PDU_EVENTS_RESPONSE_DATA_SIZE_POS + RTU_HEADER_SIZE;
79

80
    // Event record
81
    const size_t EVENT_HEADER_SIZE = 4;
82

83
    const size_t EVENT_SIZE_POS = 0;
84
    const size_t EVENT_TYPE_POS = 1;
85
    const size_t EVENT_ID_POS = 2;
86
    const size_t EVENT_DATA_POS = 4;
87

88
    // max(3.5 symbols, (20 bits + 800us)) + 9 * max(13 bits, 12 bits + 50us)
89
    std::chrono::milliseconds GetTimeoutForEvents(const TPort& port)
95✔
90
    {
91
        const auto cmdTime =
92
            std::max(port.GetSendTimeBytes(Modbus::STANDARD_FRAME_TIMEOUT_BYTES), port.GetSendTimeBits(20) + 800us);
95✔
93
        const auto arbitrationTime = 9 * std::max(port.GetSendTimeBits(13), port.GetSendTimeBits(12) + 50us);
95✔
94
        return std::chrono::ceil<std::chrono::milliseconds>(cmdTime + arbitrationTime);
190✔
95
    }
96

97
    // For 0x46 command: max(3.5 symbols, (20 bits + 800us)) + 33 * max(13 bits, 12 bits + 50us)
98
    // For 0x60 command: (44 bits) + 33 * 20 bits
99
    std::chrono::microseconds GetTimeoutForScan(const TPort& port, TModbusExtCommand command)
8✔
100
    {
101
        if (command == TModbusExtCommand::DEPRECATED) {
8✔
102
            return port.GetSendTimeBits(44) + 33 * port.GetSendTimeBits(20);
×
103
        }
104
        const auto cmdTime =
105
            std::max(port.GetSendTimeBytes(Modbus::STANDARD_FRAME_TIMEOUT_BYTES), port.GetSendTimeBits(20) + 800us);
8✔
106
        const auto arbitrationTime = 33 * std::max(port.GetSendTimeBits(13), port.GetSendTimeBits(12) + 50us);
8✔
107
        return cmdTime + arbitrationTime;
8✔
108
    }
109

110
    uint8_t GetMaxReadEventsResponseDataSize(const TPort& port, std::chrono::milliseconds maxTime)
95✔
111
    {
112
        // Read at least one event regardless of time
113
        if (maxTime.count() <= 0) {
95✔
NEW
114
            return PDU_EVENT_MAX_SIZE;
×
115
        }
116
        auto timePerByte = port.GetSendTimeBytes(1);
95✔
117
        if (timePerByte.count() == 0) {
95✔
NEW
118
            return PDU_EVENTS_REQUEST_MAX_BYTES;
×
119
        }
120
        size_t maxBytes = std::chrono::duration_cast<std::chrono::microseconds>(maxTime).count() / timePerByte.count();
95✔
121
        if (maxBytes > RTU_MAX_PACKET_SIZE) {
95✔
122
            maxBytes = RTU_MAX_PACKET_SIZE;
91✔
123
        }
124
        if (maxBytes <= PDU_EVENTS_RESPONSE_HEADER_SIZE + PDU_EVENT_MAX_SIZE) {
95✔
NEW
125
            return PDU_EVENT_MAX_SIZE;
×
126
        }
127
        maxBytes -= PDU_EVENTS_RESPONSE_HEADER_SIZE + CRC_SIZE + RTU_HEADER_SIZE;
95✔
128
        return static_cast<uint8_t>(maxBytes);
95✔
129
    }
130

131
    std::vector<uint8_t> MakeReadEventsRequest(const TEventConfirmationState& state,
95✔
132
                                               uint8_t startingSlaveId,
133
                                               uint8_t maxBytes)
134
    {
135
        std::vector<uint8_t> request({TModbusExtCommand::ACTUAL, EVENTS_REQUEST_COMMAND});
95✔
136
        auto it = std::back_inserter(request);
95✔
137
        Append(it, startingSlaveId);
95✔
138
        Append(it, maxBytes);
95✔
139
        Append(it, state.SlaveId);
95✔
140
        Append(it, state.Flag);
95✔
141
        return request;
190✔
142
    }
143

144
    std::vector<uint8_t> MakeScanRequestPdu(uint8_t fastModbusCommand, uint8_t scanCommand)
8✔
145
    {
146
        return {fastModbusCommand, scanCommand};
16✔
147
    }
148

149
    void CheckCRC16(const uint8_t* packet, size_t size)
172✔
150
    {
151
        if (size < CRC_SIZE) {
172✔
152
            throw Modbus::TMalformedResponseError("invalid packet size: " + std::to_string(size));
×
153
        }
154

155
        auto crc = GetBigEndian<uint16_t>(packet + size - CRC_SIZE, packet + size);
172✔
156
        if (crc != CRC16::CalculateCRC16(packet, size - CRC_SIZE)) {
172✔
157
            throw Modbus::TMalformedResponseError("invalid crc");
1✔
158
        }
159
    }
171✔
160

161
    bool IsModbusExtCommand(uint8_t command)
576✔
162
    {
163
        return command == TModbusExtCommand::ACTUAL || command == TModbusExtCommand::DEPRECATED;
576✔
164
    }
165

166
    bool IsModbusExtRTUPacket(const uint8_t* buf, size_t size)
601✔
167
    {
168
        if ((buf[0] == 0xFF) || (RTU_SUB_COMMAND_POS >= size) || (!IsModbusExtCommand(buf[RTU_COMMAND_POS]))) {
601✔
169
            return false;
215✔
170
        }
171

172
        switch (buf[RTU_SUB_COMMAND_POS]) {
386✔
173
            case HAS_EVENTS_RESPONSE_COMMAND: {
16✔
174
                if ((size <= RTU_EVENTS_RESPONSE_DATA_SIZE_POS) ||
16✔
175
                    (size != PDU_EVENTS_RESPONSE_HEADER_SIZE + buf[RTU_EVENTS_RESPONSE_DATA_SIZE_POS] + CRC_SIZE +
13✔
176
                                 RTU_HEADER_SIZE))
177
                {
178
                    return false;
10✔
179
                }
180
                break;
6✔
181
            }
182
            case NO_EVENTS_RESPONSE_COMMAND: {
316✔
183
                if (size != RTU_EVENTS_RESPONSE_NO_EVENTS_SIZE) {
316✔
184
                    return false;
156✔
185
                }
186
                break;
160✔
187
            }
188
            case NO_MORE_DEVICES_RESPONSE_SCAN_COMMAND: {
3✔
189
                if (size != RTU_SCAN_RESPONSE_NO_MORE_DEVICES_SIZE) {
3✔
190
                    return false;
×
191
                }
192
                break;
3✔
193
            }
194
            case DEVICE_FOUND_RESPONSE_SCAN_COMMAND: {
3✔
195
                if (size != RTU_SCAN_RESPONSE_DEVICE_FOUND_SIZE) {
3✔
196
                    return false;
×
197
                }
198
                break;
3✔
199
            }
200
            default: {
48✔
201
                // Unexpected sub command
202
                return false;
48✔
203
            }
204
        }
205

206
        try {
207
            CheckCRC16(buf, size);
172✔
208
        } catch (const Modbus::TMalformedResponseError& err) {
1✔
209
            return false;
1✔
210
        }
211
        return true;
171✔
212
    }
213

214
    const uint8_t* GetRTUPacketStart(const uint8_t* data, size_t size)
998✔
215
    {
216
        while (size > RTU_SUB_COMMAND_POS) {
998✔
217
            if (IsModbusExtRTUPacket(data, size)) {
601✔
218
                return data;
171✔
219
            }
220
            ++data;
430✔
221
            --size;
430✔
222
        }
223
        return nullptr;
397✔
224
    }
225

NEW
226
    TPort::TFrameCompletePred ExpectFastModbusRTU()
×
227
    {
NEW
228
        return [=](uint8_t* buf, size_t size) { return GetRTUPacketStart(buf, size) != nullptr; };
×
229
    }
230

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

249
    bool IsRegisterEvent(uint8_t eventType)
40✔
250
    {
251
        switch (eventType) {
40✔
252
            case TEventType::COIL:
13✔
253
            case TEventType::DISCRETE:
254
            case TEventType::HOLDING:
255
            case TEventType::INPUT:
256
                return true;
13✔
257
            default:
27✔
258
                return false;
27✔
259
        }
260
    }
261

262
    bool ReadEvents(TPort& port,
95✔
263
                    Modbus::IModbusTraits& traits,
264
                    std::chrono::milliseconds maxEventsReadTime,
265
                    uint8_t startingSlaveId,
266
                    TEventConfirmationState& state,
267
                    IEventsVisitor& eventVisitor)
268
    {
269
        // TODO: Count request and arbitration.
270
        //       maxEventsReadTime limits not only response, but total request-response time.
271
        //       So request and arbitration time must be subtracted from time for response
272
        auto maxBytes = GetMaxReadEventsResponseDataSize(port, maxEventsReadTime);
95✔
273

274
        auto requestPdu = MakeReadEventsRequest(state, startingSlaveId, maxBytes);
190✔
275
        auto frameTimeout = port.GetSendTimeBytes(Modbus::STANDARD_FRAME_TIMEOUT_BYTES);
95✔
276
        const auto timeout = GetTimeoutForEvents(port);
95✔
277

278
        port.SleepSinceLastInteraction(frameTimeout);
95✔
279
        Modbus::TReadResult res;
190✔
280
        try {
281
            res = traits.Transaction(port,
83✔
282
                                     BROADCAST_ADDRESS,
283
                                     requestPdu,
284
                                     RTU_MAX_PACKET_SIZE + RTU_ARBITRATION_HEADER_MAX_BYTES,
285
                                     timeout,
286
                                     timeout,
287
                                     false);
95✔
288
        } catch (std::exception& ex) {
24✔
289
            port.SleepSinceLastInteraction(frameTimeout);
12✔
290
            throw;
12✔
291
        }
292
        port.SleepSinceLastInteraction(frameTimeout);
83✔
293

294
        if (res.Pdu.size() < PDU_EVENTS_RESPONSE_MIN_SIZE) {
83✔
NEW
295
            throw Modbus::TMalformedResponseError("invalid packet size: " + std::to_string(res.Pdu.size()));
×
296
        }
297

298
        const uint8_t* packet = res.Pdu.data();
83✔
299

300
        switch (packet[PDU_SUB_COMMAND_POS]) {
83✔
301
            case HAS_EVENTS_RESPONSE_COMMAND: {
3✔
302
                if ((res.Pdu.size() <= PDU_EVENTS_RESPONSE_DATA_SIZE_POS) ||
6✔
303
                    (res.Pdu.size() != PDU_EVENTS_RESPONSE_HEADER_SIZE + packet[PDU_EVENTS_RESPONSE_DATA_SIZE_POS]))
3✔
304
                {
NEW
305
                    throw Modbus::TMalformedResponseError("invalid packet size: " + std::to_string(res.Pdu.size()));
×
306
                }
307

308
                state.SlaveId = res.SlaveId;
3✔
309
                state.Flag = packet[PDU_EVENTS_RESPONSE_CONFIRM_FLAG_POS];
3✔
310
                IterateOverEvents(res.SlaveId,
3✔
311
                                  packet + PDU_EVENTS_RESPONSE_DATA_POS,
312
                                  packet[PDU_EVENTS_RESPONSE_DATA_SIZE_POS],
3✔
313
                                  eventVisitor);
314
                return true;
3✔
315
            }
316
            case NO_EVENTS_RESPONSE_COMMAND: {
80✔
317
                return false;
80✔
318
            }
319
            default: {
×
320
                throw Modbus::TMalformedResponseError("invalid sub command");
×
321
            }
322
        }
323
    }
324

325
    bool HasSpaceForEnableEventRecord(const std::vector<uint8_t>& requestPdu)
72✔
326
    {
327
        return requestPdu.size() + requestPdu[PDU_ENABLE_EVENTS_RESPONSE_DATA_SIZE_POS] +
72✔
328
                   PDU_ENABLE_EVENTS_MIN_REC_SIZE <
329
               RTU_MAX_PACKET_SIZE - CRC_SIZE - RTU_HEADER_SIZE;
72✔
330
    }
331

332
    //=====================================================
333
    //                TBitIterator
334
    //=====================================================
335

336
    class TBitIterator
337
    {
338
        uint8_t* Data;
339
        const uint8_t* End;
340
        size_t BitIndex;
341

342
    public:
343
        TBitIterator(uint8_t* data, size_t size): Data(data), End(data + size), BitIndex(0)
16✔
344
        {}
16✔
345

346
        void NextBit()
21✔
347
        {
348
            ++BitIndex;
21✔
349
            if (BitIndex == 8) {
21✔
350
                NextByte();
2✔
351
            }
352
        }
21✔
353

354
        void NextByte()
34✔
355
        {
356
            ++Data;
34✔
357
            BitIndex = 0;
34✔
358
            if (Data > End) {
34✔
359
                throw Modbus::TMalformedResponseError("not enough data in enable events response");
×
360
            }
361
        }
34✔
362

363
        bool GetBit() const
53✔
364
        {
365
            return (*Data >> BitIndex) & 0x01;
53✔
366
        }
367
    };
368

369
    //=====================================================
370
    //                TEventsEnabler
371
    //=====================================================
372

373
    void TEventsEnabler::EnableEvents(const std::vector<uint8_t>& requestPdu)
18✔
374
    {
375
        // Use response timeout from MR6C template
376
        auto res = Traits.Transaction(Port,
18✔
377
                                      SlaveId,
378
                                      requestPdu,
379
                                      RTU_MAX_PACKET_SIZE + RTU_ARBITRATION_HEADER_MAX_BYTES,
NEW
380
                                      8ms,
×
381
                                      FrameTimeout);
36✔
382

383
        // Old firmwares can send any command with exception bit
384
        if (res.Pdu.size() == 2 && Modbus::IsException(res.Pdu[0])) {
18✔
385
            throw Modbus::TModbusExceptionError(res.Pdu[1]);
1✔
386
        }
387

388
        if (res.Pdu.size() < PDU_ENABLE_EVENTS_RESPONSE_MIN_SIZE) {
17✔
NEW
389
            throw Modbus::TMalformedResponseError("invalid packet size: " + std::to_string(res.Pdu.size()));
×
390
        }
391

392
        if (res.Pdu[PDU_SUB_COMMAND_POS] != ENABLE_EVENTS_COMMAND) {
17✔
393
            throw Modbus::TMalformedResponseError("invalid sub command");
1✔
394
        }
395

396
        TBitIterator dataIt(res.Pdu.data() + PDU_ENABLE_EVENTS_RESPONSE_DATA_POS - 1,
16✔
397
                            res.Pdu[PDU_ENABLE_EVENTS_RESPONSE_DATA_SIZE_POS]);
16✔
398

399
        TEventType lastType = TEventType::REBOOT;
16✔
400
        uint16_t lastRegAddr = 0;
16✔
401
        uint16_t lastDataAddr = 0;
16✔
402
        bool firstRegister = true;
16✔
403

404
        for (auto regIt = SettingsStart; regIt != SettingsEnd; ++regIt) {
67✔
405
            if (firstRegister || (lastType != regIt->Type) || (lastRegAddr + MaxRegDistance < regIt->Addr)) {
51✔
406
                dataIt.NextByte();
32✔
407
                lastDataAddr = regIt->Addr;
32✔
408
                lastType = regIt->Type;
32✔
409
                Visitor(lastType, lastDataAddr, dataIt.GetBit());
32✔
410
                firstRegister = false;
32✔
411
            } else {
412
                do {
2✔
413
                    dataIt.NextBit();
21✔
414
                    ++lastDataAddr;
21✔
415
                    Visitor(lastType, lastDataAddr, dataIt.GetBit());
21✔
416
                } while (lastDataAddr != regIt->Addr);
21✔
417
            }
418
            lastRegAddr = regIt->Addr;
51✔
419
        }
420
    }
16✔
421

422
    TEventsEnabler::TEventsEnabler(uint8_t slaveId,
76✔
423
                                   TPort& port,
424
                                   Modbus::IModbusTraits& traits,
425
                                   TEventsEnabler::TVisitorFn visitor,
426
                                   TEventsEnablerFlags flags)
76✔
427
        : SlaveId(slaveId),
428
          Port(port),
429
          Traits(traits),
430
          MaxRegDistance(1),
431
          Visitor(visitor)
76✔
432
    {
433
        if (flags == TEventsEnablerFlags::DISABLE_EVENTS_IN_HOLES) {
76✔
434
            MaxRegDistance = PDU_ENABLE_EVENTS_MIN_REC_SIZE;
73✔
435
        }
436
        FrameTimeout =
437
            std::chrono::ceil<std::chrono::milliseconds>(port.GetSendTimeBytes(Modbus::STANDARD_FRAME_TIMEOUT_BYTES));
76✔
438
    }
76✔
439

440
    void TEventsEnabler::AddRegister(uint16_t addr, TEventType type, TEventPriority priority)
54✔
441
    {
442
        Settings.emplace_back(TRegisterToEnable{type, addr, priority});
54✔
443
    }
54✔
444

445
    void TEventsEnabler::SendSingleRequest()
18✔
446
    {
447
        std::vector<uint8_t> requestPdu({TModbusExtCommand::ACTUAL, ENABLE_EVENTS_COMMAND, 0});
36✔
448
        auto requestBack = std::back_inserter(requestPdu);
18✔
449
        TEventType lastType = TEventType::REBOOT;
18✔
450
        uint16_t lastAddr = 0;
18✔
451
        auto regIt = SettingsEnd;
18✔
452
        size_t regCountPos;
453
        bool firstRegister = true;
18✔
454
        for (; HasSpaceForEnableEventRecord(requestPdu) && regIt != Settings.cend(); ++regIt) {
72✔
455
            if (firstRegister || (lastType != regIt->Type) || (lastAddr + MaxRegDistance < regIt->Addr)) {
54✔
456
                Append(requestBack, static_cast<uint8_t>(regIt->Type));
35✔
457
                AppendBigEndian(requestBack, regIt->Addr);
35✔
458
                Append(requestBack, static_cast<uint8_t>(1));
35✔
459
                regCountPos = requestPdu.size() - 1;
35✔
460
                Append(requestBack, static_cast<uint8_t>(regIt->Priority));
35✔
461
                requestPdu[PDU_ENABLE_EVENTS_RESPONSE_DATA_SIZE_POS] += PDU_ENABLE_EVENTS_MIN_REC_SIZE;
35✔
462
                firstRegister = false;
35✔
463
            } else {
464
                const auto nRegs = static_cast<size_t>(regIt->Addr - lastAddr);
19✔
465
                requestPdu[regCountPos] += nRegs;
19✔
466
                for (size_t i = 0; i + 1 < nRegs; ++i) {
21✔
467
                    Append(requestBack, static_cast<uint8_t>(TEventPriority::DISABLE));
2✔
468
                }
469
                Append(requestBack, static_cast<uint8_t>(regIt->Priority));
19✔
470
                requestPdu[PDU_ENABLE_EVENTS_RESPONSE_DATA_SIZE_POS] += nRegs;
19✔
471
            }
472
            lastType = regIt->Type;
54✔
473
            lastAddr = regIt->Addr;
54✔
474
        }
475

476
        SettingsStart = SettingsEnd;
18✔
477
        SettingsEnd = regIt;
18✔
478

479
        EnableEvents(requestPdu);
18✔
480
    }
16✔
481

482
    void TEventsEnabler::SendRequests()
18✔
483
    {
484
        if (Settings.empty()) {
18✔
485
            return;
×
486
        }
487
        std::sort(Settings.begin(), Settings.end(), [](const auto& a, const auto& b) {
18✔
488
            if (a.Type == b.Type) {
72✔
489
                return a.Addr < b.Addr;
22✔
490
            }
491
            return a.Type < b.Type;
50✔
492
        });
493
        auto last = std::unique(Settings.begin(), Settings.end(), [](const auto& a, const auto& b) {
36✔
494
            return a.Type == b.Type && a.Addr == b.Addr;
36✔
495
        });
18✔
496
        Settings.erase(last, Settings.end());
18✔
497
        for (SettingsEnd = SettingsStart = Settings.cbegin(); SettingsEnd != Settings.cend();) {
34✔
498
            SendSingleRequest();
18✔
499
        }
500
    }
501

502
    bool TEventsEnabler::HasEventsToSetup() const
72✔
503
    {
504
        return !Settings.empty();
72✔
505
    }
506

507
    void TEventConfirmationState::Reset()
78✔
508
    {
509
        Flag = 0;
78✔
510
        SlaveId = 0;
78✔
511
    }
78✔
512

513
    //=========================================================
514
    //                    TModbusTraits
515
    //=========================================================
516

517
    TModbusTraits::TModbusTraits(std::unique_ptr<Modbus::IModbusTraits> baseTraits)
4✔
518
        : Sn(0),
519
          ModbusExtCommand(TModbusExtCommand::ACTUAL),
520
          BaseTraits(std::move(baseTraits))
4✔
521
    {}
4✔
522

523
    size_t TModbusTraits::GetIntermediatePduSize(size_t pduSize) const
7✔
524
    {
525
        return PDU_MODBUS_STANDARD_COMMAND_HEADER_SIZE + pduSize;
7✔
526
    }
527

528
    void TModbusTraits::FinalizeIntermediatePdu(std::vector<uint8_t>& request)
7✔
529
    {
530
        request[0] = ModbusExtCommand;
7✔
531
        request[1] = MODBUS_STANDARD_REQUEST_COMMAND;
7✔
532
        request[2] = static_cast<uint8_t>(Sn >> 24);
7✔
533
        request[3] = static_cast<uint8_t>(Sn >> 16);
7✔
534
        request[4] = static_cast<uint8_t>(Sn >> 8);
7✔
535
        request[5] = static_cast<uint8_t>(Sn);
7✔
536
    }
7✔
537

538
    void TModbusTraits::ValidateIntermediateResponsePdu(const std::vector<uint8_t>& response) const
4✔
539
    {
540
        if (response.size() < PDU_MODBUS_STANDARD_COMMAND_HEADER_SIZE) {
4✔
UNCOV
541
            throw Modbus::TMalformedResponseError("invalid data size");
×
542
        }
543

544
        if (response[0] != ModbusExtCommand) {
4✔
545
            throw Modbus::TUnexpectedResponseError("invalid response command");
1✔
546
        }
547

548
        if (response[1] != MODBUS_STANDARD_RESPONSE_COMMAND) {
3✔
549
            throw Modbus::TUnexpectedResponseError("invalid response subcommand: " + std::to_string(response[1]));
1✔
550
        }
551

552
        auto responseSn = GetBigEndian<uint32_t>(response.cbegin() + PDU_MODBUS_STANDARD_COMMAND_RESPONSE_SN_POS,
4✔
553
                                                 response.cbegin() + PDU_MODBUS_STANDARD_COMMAND_HEADER_SIZE);
2✔
554
        if (responseSn != Sn) {
2✔
555
            throw Modbus::TUnexpectedResponseError("SN mismatch: got " + std::to_string(responseSn) + ", wait " +
3✔
556
                                                   std::to_string(Sn));
4✔
557
        }
558
    }
1✔
559

560
    Modbus::TReadResult TModbusTraits::Transaction(TPort& port,
7✔
561
                                                   uint8_t slaveId,
562
                                                   const std::vector<uint8_t>& requestPdu,
563
                                                   size_t expectedResponsePduSize,
564
                                                   const std::chrono::milliseconds& responseTimeout,
565
                                                   const std::chrono::milliseconds& frameTimeout,
566
                                                   bool matchSlaveId)
567
    {
568
        std::vector<uint8_t> request(GetIntermediatePduSize(requestPdu.size()));
14✔
569
        std::copy(requestPdu.begin(), requestPdu.end(), request.begin() + PDU_MODBUS_STANDARD_COMMAND_HEADER_SIZE);
7✔
570
        FinalizeIntermediatePdu(request);
7✔
571

572
        auto intermediateResponse =
573
            BaseTraits->Transaction(port,
7✔
574
                                    BROADCAST_ADDRESS,
575
                                    request,
576
                                    expectedResponsePduSize + PDU_MODBUS_STANDARD_COMMAND_HEADER_SIZE,
577
                                    responseTimeout,
578
                                    frameTimeout,
579
                                    matchSlaveId);
11✔
580

581
        ValidateIntermediateResponsePdu(intermediateResponse.Pdu);
4✔
582

583
        Modbus::TReadResult res;
1✔
584
        res.ResponseTime = intermediateResponse.ResponseTime;
1✔
585
        res.SlaveId = intermediateResponse.SlaveId;
1✔
586
        res.Pdu.assign(intermediateResponse.Pdu.begin() + PDU_MODBUS_STANDARD_COMMAND_HEADER_SIZE,
1✔
587
                       intermediateResponse.Pdu.end());
588
        return res;
2✔
589
    }
590

591
    void TModbusTraits::SetSn(uint32_t sn)
4✔
592
    {
593
        Sn = sn;
4✔
594
    }
4✔
595

596
    void TModbusTraits::SetModbusExtCommand(TModbusExtCommand command)
×
597
    {
598
        ModbusExtCommand = command;
×
599
    }
600

601
    //=========================================================
602

603
    std::optional<TScannedDevice> SendScanRequest(TPort& port,
8✔
604
                                                  Modbus::IModbusTraits& traits,
605
                                                  TModbusExtCommand modbusExtCommand,
606
                                                  uint8_t scanCommand)
607
    {
608
        auto req = MakeScanRequestPdu(modbusExtCommand, scanCommand);
16✔
609
        const auto standardFrameTimeout = port.GetSendTimeBytes(Modbus::STANDARD_FRAME_TIMEOUT_BYTES);
8✔
610
        const auto scanFrameTimeout = GetTimeoutForScan(port, modbusExtCommand);
8✔
611
        const auto responseTimeout = scanFrameTimeout + port.GetSendTimeBytes(1);
8✔
612

613
        port.SleepSinceLastInteraction(standardFrameTimeout);
8✔
614
        auto res = traits.Transaction(port,
615
                                      BROADCAST_ADDRESS,
616
                                      req,
617
                                      RTU_MAX_PACKET_SIZE + RTU_ARBITRATION_HEADER_MAX_BYTES,
618
                                      std::chrono::ceil<std::chrono::milliseconds>(responseTimeout),
1✔
619
                                      std::chrono::ceil<std::chrono::milliseconds>(scanFrameTimeout));
16✔
620

621
        if (res.Pdu.size() < PDU_SCAN_RESPONSE_MIN_SIZE) {
7✔
UNCOV
622
            throw Modbus::TMalformedResponseError("invalid packet");
×
623
        }
624

625
        const uint8_t* packet = res.Pdu.data();
7✔
626
        switch (packet[PDU_SUB_COMMAND_POS]) {
7✔
627
            case DEVICE_FOUND_RESPONSE_SCAN_COMMAND: {
3✔
628
                if (res.Pdu.size() < PDU_SCAN_RESPONSE_DEVICE_FOUND_SIZE) {
3✔
NEW
629
                    throw Modbus::TMalformedResponseError("invalid device found response packet size");
×
630
                }
631
                return std::optional<TScannedDevice>{
632
                    TScannedDevice{packet[PDU_SCAN_RESPONSE_SLAVE_ID_POS],
3✔
633
                                   BinUtils::GetFromBigEndian<uint32_t>(packet + PDU_SCAN_RESPONSE_SN_POS)}};
3✔
634
            }
635
            case NO_MORE_DEVICES_RESPONSE_SCAN_COMMAND: {
3✔
636
                return std::nullopt;
3✔
637
            }
638
            default: {
1✔
639
                throw Modbus::TMalformedResponseError("invalid sub command");
1✔
640
            }
641
        }
642
    }
643

644
    std::optional<TScannedDevice> ScanStart(TPort& port,
5✔
645
                                            Modbus::IModbusTraits& traits,
646
                                            TModbusExtCommand modbusExtCommand)
647
    {
648
        try {
649
            return SendScanRequest(port, traits, modbusExtCommand, START_SCAN_COMMAND);
5✔
650
        } catch (const TResponseTimeoutException& ex) {
2✔
651
            // It is ok. No Fast Modbus capable devices on port.
652
            LOG(Debug) << "No Fast Modbus capable devices on port " << port.GetDescription();
1✔
653
            return std::nullopt;
1✔
654
        }
655
    }
656

657
    std::optional<TScannedDevice> ScanNext(TPort& port,
3✔
658
                                           Modbus::IModbusTraits& traits,
659
                                           TModbusExtCommand modbusExtCommand)
660
    {
661
        return SendScanRequest(port, traits, modbusExtCommand, CONTINUE_SCAN_COMMAND);
3✔
662
    }
663

664
    void Scan(TPort& port,
5✔
665
              Modbus::IModbusTraits& traits,
666
              TModbusExtCommand modbusExtCommand,
667
              std::vector<TScannedDevice>& scannedDevices)
668
    {
669
        auto res = ScanStart(port, traits, modbusExtCommand);
5✔
670
        if (!res) {
4✔
671
            return;
2✔
672
        }
673
        scannedDevices.push_back(*res);
2✔
674
        while (true) {
675
            res = ScanNext(port, traits, modbusExtCommand);
3✔
676
            if (!res) {
3✔
677
                return;
2✔
678
            }
679
            scannedDevices.push_back(*res);
1✔
680
        }
681
    }
682

683
    //=========================================================
684
    //             TModbusRTUWithArbitrationTraits
685
    //=========================================================
686

687
    TModbusRTUWithArbitrationTraits::TModbusRTUWithArbitrationTraits()
89✔
688
    {}
89✔
689

690
    TPort::TFrameCompletePred TModbusRTUWithArbitrationTraits::ExpectFastModbusRTU() const
103✔
691
    {
692
        return [=](uint8_t* buf, size_t size) { return GetRTUPacketStart(buf, size) != nullptr; };
565✔
693
    }
694

695
    void TModbusRTUWithArbitrationTraits::FinalizeRequest(std::vector<uint8_t>& request, uint8_t slaveId)
103✔
696
    {
697
        request[0] = slaveId;
103✔
698
        WriteAs2Bytes(&request[request.size() - 2], CRC16::CalculateCRC16(request.data(), request.size() - 2));
103✔
699
    }
103✔
700

701
    Modbus::TReadResult TModbusRTUWithArbitrationTraits::Transaction(TPort& port,
103✔
702
                                                                     uint8_t slaveId,
703
                                                                     const std::vector<uint8_t>& requestPdu,
704
                                                                     size_t expectedResponsePduSize,
705
                                                                     const std::chrono::milliseconds& responseTimeout,
706
                                                                     const std::chrono::milliseconds& frameTimeout,
707
                                                                     bool matchSlaveId)
708
    {
709
        std::vector<uint8_t> request(requestPdu.size() + 3);
206✔
710
        std::copy(requestPdu.begin(), requestPdu.end(), request.begin() + 1);
103✔
711
        FinalizeRequest(request, slaveId);
103✔
712

713
        port.WriteBytes(request);
103✔
714

715
        std::array<uint8_t, RTU_MAX_PACKET_SIZE + RTU_ARBITRATION_HEADER_MAX_BYTES> response;
716
        auto res =
717
            port.ReadFrame(response.data(), response.size(), responseTimeout, frameTimeout, ExpectFastModbusRTU());
104✔
718

719
        const uint8_t* packet = GetRTUPacketStart(response.data(), res.Count);
102✔
720
        if (packet == nullptr) {
102✔
721
            throw Modbus::TMalformedResponseError("invalid packet");
12✔
722
        }
723
        if (matchSlaveId && (packet[0] != slaveId)) {
90✔
NEW
724
            throw Modbus::TUnexpectedResponseError("slave id mismatch");
×
725
        }
726
        Modbus::TReadResult result;
90✔
727
        result.ResponseTime = res.ResponseTime;
90✔
728
        result.Pdu.assign(packet + RTU_HEADER_SIZE, response.cbegin() + (res.Count - CRC_SIZE));
90✔
729
        result.SlaveId = packet[0];
90✔
730
        return result;
180✔
731
    }
732

733
}
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