• 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

92.86
/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)
190✔
90
    {
91
        const auto cmdTime =
92
            std::max(port.GetSendTimeBytes(Modbus::STANDARD_FRAME_TIMEOUT_BYTES), port.GetSendTimeBits(20) + 800us);
190✔
93
        const auto arbitrationTime = 9 * std::max(port.GetSendTimeBits(13), port.GetSendTimeBits(12) + 50us);
190✔
94
        return std::chrono::ceil<std::chrono::milliseconds>(cmdTime + arbitrationTime);
380✔
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)
16✔
100
    {
101
        if (command == TModbusExtCommand::DEPRECATED) {
16✔
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);
16✔
106
        const auto arbitrationTime = 33 * std::max(port.GetSendTimeBits(13), port.GetSendTimeBits(12) + 50us);
16✔
107
        return cmdTime + arbitrationTime;
16✔
108
    }
109

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

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

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

149
    void CheckCRC16(const uint8_t* packet, size_t size)
344✔
150
    {
151
        if (size < CRC_SIZE) {
344✔
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);
344✔
156
        if (crc != CRC16::CalculateCRC16(packet, size - CRC_SIZE)) {
344✔
157
            throw Modbus::TMalformedResponseError("invalid crc");
2✔
158
        }
159
    }
342✔
160

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

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

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

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

214
    const uint8_t* GetRTUPacketStart(const uint8_t* data, size_t size)
1,996✔
215
    {
216
        while (size > RTU_SUB_COMMAND_POS) {
1,996✔
217
            if (IsModbusExtRTUPacket(data, size)) {
1,202✔
218
                return data;
342✔
219
            }
220
            ++data;
860✔
221
            --size;
860✔
222
        }
223
        return nullptr;
794✔
224
    }
225

226
    TPort::TFrameCompletePred ExpectFastModbusRTU()
×
227
    {
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)
12✔
232
    {
233
        while (size != 0) {
12✔
234
            size_t dataSize = data[EVENT_SIZE_POS];
6✔
235
            size_t eventSize = dataSize + EVENT_HEADER_SIZE;
6✔
236
            if (eventSize > size) {
6✔
237
                throw Modbus::TMalformedResponseError("invalid event data size");
×
238
            }
239
            eventVisitor.Event(slaveId,
6✔
240
                               data[EVENT_TYPE_POS],
6✔
241
                               GetFromBigEndian<uint16_t>(data + EVENT_ID_POS),
242
                               data + EVENT_DATA_POS,
243
                               dataSize);
6✔
244
            data += eventSize;
6✔
245
            size -= eventSize;
6✔
246
        }
247
    }
6✔
248

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

262
    bool ReadEvents(TPort& port,
190✔
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);
190✔
273

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

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

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

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

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

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

325
    bool HasSpaceForEnableEventRecord(const std::vector<uint8_t>& requestPdu)
144✔
326
    {
327
        return requestPdu.size() + requestPdu[PDU_ENABLE_EVENTS_RESPONSE_DATA_SIZE_POS] +
144✔
328
                   PDU_ENABLE_EVENTS_MIN_REC_SIZE <
329
               RTU_MAX_PACKET_SIZE - CRC_SIZE - RTU_HEADER_SIZE;
144✔
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)
32✔
344
        {}
32✔
345

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

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

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

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

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

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

388
        if (res.Pdu.size() < PDU_ENABLE_EVENTS_RESPONSE_MIN_SIZE) {
34✔
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) {
34✔
393
            throw Modbus::TMalformedResponseError("invalid sub command");
2✔
394
        }
395

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

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

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

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

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

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

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

479
        EnableEvents(requestPdu);
36✔
480
    }
32✔
481

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

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

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

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

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

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

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

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

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

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

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

560
    Modbus::TReadResult TModbusTraits::Transaction(TPort& port,
14✔
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()));
28✔
569
        std::copy(requestPdu.begin(), requestPdu.end(), request.begin() + PDU_MODBUS_STANDARD_COMMAND_HEADER_SIZE);
14✔
570
        FinalizeIntermediatePdu(request);
14✔
571

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

581
        ValidateIntermediateResponsePdu(intermediateResponse.Pdu);
8✔
582

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

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

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

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

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

613
        port.SleepSinceLastInteraction(standardFrameTimeout);
16✔
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),
2✔
619
                                      std::chrono::ceil<std::chrono::milliseconds>(scanFrameTimeout));
32✔
620

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

625
        const uint8_t* packet = res.Pdu.data();
14✔
626
        switch (packet[PDU_SUB_COMMAND_POS]) {
14✔
627
            case DEVICE_FOUND_RESPONSE_SCAN_COMMAND: {
6✔
628
                if (res.Pdu.size() < PDU_SCAN_RESPONSE_DEVICE_FOUND_SIZE) {
6✔
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],
6✔
633
                                   BinUtils::GetFromBigEndian<uint32_t>(packet + PDU_SCAN_RESPONSE_SN_POS)}};
6✔
634
            }
635
            case NO_MORE_DEVICES_RESPONSE_SCAN_COMMAND: {
6✔
636
                return std::nullopt;
6✔
637
            }
638
            default: {
2✔
639
                throw Modbus::TMalformedResponseError("invalid sub command");
2✔
640
            }
641
        }
642
    }
643

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

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

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

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

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

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

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

701
    Modbus::TReadResult TModbusRTUWithArbitrationTraits::Transaction(TPort& port,
206✔
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);
412✔
710
        std::copy(requestPdu.begin(), requestPdu.end(), request.begin() + 1);
206✔
711
        FinalizeRequest(request, slaveId);
206✔
712

713
        port.WriteBytes(request);
206✔
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());
208✔
718

719
        const uint8_t* packet = GetRTUPacketStart(response.data(), res.Count);
204✔
720
        if (packet == nullptr) {
204✔
721
            throw Modbus::TMalformedResponseError("invalid packet");
24✔
722
        }
723
        if (matchSlaveId && (packet[0] != slaveId)) {
180✔
724
            throw Modbus::TUnexpectedResponseError("slave id mismatch");
×
725
        }
726
        const uint8_t* data = response.data();
180✔
727
        Modbus::TReadResult result;
180✔
728
        result.ResponseTime = res.ResponseTime;
180✔
729
        result.Pdu.assign(packet + RTU_HEADER_SIZE, data + (res.Count - CRC_SIZE));
180✔
730
        result.SlaveId = packet[0];
180✔
731
        return result;
360✔
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