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

wirenboard / wb-mqtt-mbgate / 79

31 Jul 2026 10:42AM UTC coverage: 69.352% (-1.3%) from 70.692%
79

push

github

web-flow
Use exported vars instead of MAKEFLAGS for coverage options (#62)

706 of 926 branches covered (76.24%)

1188 of 1713 relevant lines covered (69.35%)

11.4 hits per line

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

81.99
/src/modbus_wrapper.cpp
1
#include "modbus_wrapper.h"
2
#include "log.h"
3
#include <modbus/modbus.h>
4

5
#include <map>
6

7
#define LOG(logger) ::logger.Log() << "[modbus] "
8

9
using namespace std;
10

11
TModbusServer::TModbusServer(PModbusBackend backend): mb(backend)
8✔
12
{
13
    // fill _CmdRangeMap for quick and pretty access in parser
14
    _CmdRangeMap[READ_COIL_STATUS] = &_co;
8✔
15
    _CmdRangeMap[READ_DISCRETE_INPUTS] = &_di;
8✔
16
    _CmdRangeMap[READ_HOLDING_REGISTERS] = &_hr;
8✔
17
    _CmdRangeMap[READ_INPUT_REGISTERS] = &_ir;
8✔
18

19
    _CmdRangeMap[FORCE_SINGLE_COIL] = &_co;
8✔
20
    _CmdRangeMap[PRESET_SINGLE_REGISTER] = &_hr;
8✔
21
    _CmdRangeMap[FORCE_MULTIPLE_COILS] = &_co;
8✔
22
    _CmdRangeMap[PRESET_MULTIPLE_REGISTERS] = &_hr;
8✔
23

24
    _CmdStoreTypeMap[READ_COIL_STATUS] = COIL;
8✔
25
    _CmdStoreTypeMap[READ_DISCRETE_INPUTS] = DISCRETE_INPUT;
8✔
26
    _CmdStoreTypeMap[READ_HOLDING_REGISTERS] = HOLDING_REGISTER;
8✔
27
    _CmdStoreTypeMap[READ_INPUT_REGISTERS] = INPUT_REGISTER;
8✔
28

29
    _CmdStoreTypeMap[FORCE_SINGLE_COIL] = COIL;
8✔
30
    _CmdStoreTypeMap[PRESET_SINGLE_REGISTER] = HOLDING_REGISTER;
8✔
31
    _CmdStoreTypeMap[FORCE_MULTIPLE_COILS] = COIL;
8✔
32
    _CmdStoreTypeMap[PRESET_MULTIPLE_REGISTERS] = HOLDING_REGISTER;
8✔
33
}
8✔
34

35
void TModbusServer::Backend(PModbusBackend backend)
×
36
{
37
    mb = backend;
×
38
}
×
39

40
PModbusBackend TModbusServer::Backend()
×
41
{
42
    return mb;
×
43
}
44

45
void TModbusServer::Observe(PModbusServerObserver o,
32✔
46
                            TStoreType store,
47
                            const TModbusAddressRange& range,
48
                            uint8_t slave_id)
49
{
50
    int offset = slave_id << 16;
32✔
51
    TRSet& max_addr = _maxSlaveAddresses[slave_id];
32✔
52

53
#define PROCESS(a, b)                                                                                                  \
54
    do {                                                                                                               \
55
        if (store & (a)) {                                                                                             \
56
            _##b.insert(range + offset, o);                                                                            \
57
            if (range.getEnd() > max_addr.b)                                                                           \
58
                max_addr.b = range.getEnd();                                                                           \
59
        }                                                                                                              \
60
    } while (0)
61

62
    PROCESS(DISCRETE_INPUT, di);
32✔
63
    PROCESS(COIL, co);
32✔
64
    PROCESS(INPUT_REGISTER, ir);
32✔
65
    PROCESS(HOLDING_REGISTER, hr);
32✔
66

67
#undef PROCESS
68
}
32✔
69

70
bool TModbusServer::IsObserved(uint8_t slave_id) const
10✔
71
{
72
    return _maxSlaveAddresses.find(slave_id) != _maxSlaveAddresses.end();
10✔
73
}
74

75
static void _callCacheAllocate(const TModbusAddressRange& range, uint8_t slave_id, TStoreType store, void* cache_start)
52✔
76
{
77
    map<PModbusServerObserver, TModbusCacheAddressRange> observers;
52✔
78

79
    // collect ranges for observers
80
    for (auto item = range.cbegin(); item != range.cend(); ++item) {
103✔
81
        int cache_offset = item->first;
51✔
82
        void* cache_ptr = cache_start;
51✔
83

84
        if (((cache_offset >> 16) & 0xFF) != slave_id)
51✔
85
            continue;
19✔
86
        else
87
            cache_offset &= 0xFFFF;
32✔
88

89
        // shift data offset for 16-bit values
90
        if (store == INPUT_REGISTER || store == HOLDING_REGISTER)
32✔
91
            cache_ptr = static_cast<uint16_t*>(cache_ptr) + cache_offset;
9✔
92
        else
93
            cache_ptr = static_cast<uint8_t*>(cache_ptr) + cache_offset;
23✔
94

95
        observers[item->second.second].insert(cache_offset, item->second.first - item->first, cache_ptr);
32✔
96
    }
97

98
    // call OnCacheAllocate() for each observer
99
    for (auto& item: observers) {
84✔
100
        item.first->OnCacheAllocate(store, slave_id, item.second);
32✔
101
    }
102
}
52✔
103

104
void TModbusServer::AllocateCache()
8✔
105
{
106
    for (auto& s: _maxSlaveAddresses) {
21✔
107
        const int slave_id = s.first;
13✔
108
        const TRSet& r = s.second;
13✔
109

110
        // allocate modbus mapping
111
        mb->AllocateCache(slave_id, r.di, r.co, r.ir, r.hr);
13✔
112

113
        // call OnCacheAllocate with correct ranges for each observer
114
        _callCacheAllocate(_di, slave_id, DISCRETE_INPUT, mb->GetCache(DISCRETE_INPUT, slave_id));
13✔
115
        _callCacheAllocate(_co, slave_id, COIL, mb->GetCache(COIL, slave_id));
13✔
116
        _callCacheAllocate(_ir, slave_id, INPUT_REGISTER, mb->GetCache(INPUT_REGISTER, slave_id));
13✔
117
        _callCacheAllocate(_hr, slave_id, HOLDING_REGISTER, mb->GetCache(HOLDING_REGISTER, slave_id));
13✔
118
    }
119

120
    LOG(Debug) << "Modbus cache allocated";
8✔
121
}
8✔
122

123
int TModbusServer::Loop(int timeoutMilliS)
6✔
124
{
125
    int rc = mb->WaitForMessages(timeoutMilliS);
6✔
126
    if (rc == -1) {
6✔
127
        LOG(Error) << mb->GetStrError();
×
128

129
        int error = mb->GetError();
×
130
        // if /dev/ttyRS485-2 is configured as CAN, this error is occurring
131
        // and service needs to restart
132
        if (error == ECONNRESET) {
×
133
            return -1;
×
134
        }
135
        // in other cases (CRC error for example) just skip message and keep working
136
        return 0;
×
137
    }
138

139
    // receive message, process, run callback
140
    while (mb->Available()) {
16✔
141
        TModbusQuery q = mb->ReceiveQuery();
10✔
142
        auto slave_id = q.header_length > 0 ? q.data[q.header_length - 1] : 0;
10✔
143
        if (q.size > 0 && IsObserved(slave_id)) {
10✔
144
            _ProcessQuery(q);
9✔
145
        }
146
    }
10✔
147

148
    return 0;
6✔
149
}
150

151
void TModbusServer::_ProcessQuery(const TModbusQuery& query)
9✔
152
{
153
    // get command code
154
    Command command = static_cast<Command>(query.data[query.header_length]);
9✔
155

156
    // get register address
157
    uint16_t start_address = _ReadU16(&(query.data[query.header_length + 1]));
9✔
158
    uint8_t slave_id = 0;
9✔
159

160
    // get slave ID and append it to address
161
    if (query.header_length > 0) {
9✔
162
        slave_id = query.data[query.header_length - 1];
1✔
163
    }
164

165
    uint16_t count;
166

167
    // get command data - address range and access mode
168
    if (_IsReadCmd(command)) {
9✔
169
        count = _ReadU16(&(query.data[query.header_length + 3]));
3✔
170
        _ProcessReadQuery(_CmdStoreTypeMap[command], *_CmdRangeMap[command], slave_id, start_address, count, query);
3✔
171
    } else {
172
        if (_IsSingleWriteCmd(command)) {
6✔
173
            count = 1;
2✔
174
        } else {
175
            count = _ReadU16(&(query.data[query.header_length + 3]));
4✔
176
        }
177

178
        // get values from write request to run pre-write action
179
        void* values;
180

181
        if (_IsCoilWriteCmd(command)) {
6✔
182
            uint8_t* raw_data = &(query.data[query.header_length + (_IsSingleWriteCmd(command) ? 3 : 6)]);
2✔
183
            uint8_t* int_values = new uint8_t[count];
2✔
184

185
            uint8_t bits = 1;
2✔
186
            int q = 0;
2✔
187

188
            for (int i = 0; i < count; i++) {
5✔
189
                int_values[i] = raw_data[q] & bits ? 0xFF : 0;
3✔
190
                bits <<= 1;
3✔
191
                if (bits == 0) {
3✔
192
                    q++;
×
193
                    bits = 1;
×
194
                }
195
            }
196

197
            values = int_values;
2✔
198
        } else {
199
            uint8_t* raw_data = &(query.data[query.header_length + (_IsSingleWriteCmd(command) ? 3 : 6)]);
4✔
200
            uint16_t* int_values = new uint16_t[count];
4✔
201

202
            for (int i = 0; i < count; i++)
10✔
203
                int_values[i] = (raw_data[2 * i] << 8) | (raw_data[2 * i + 1]);
6✔
204

205
            values = int_values;
4✔
206
        }
207

208
        _ProcessWriteQuery(_CmdStoreTypeMap[command],
6✔
209
                           *_CmdRangeMap[command],
6✔
210
                           slave_id,
211
                           start_address,
212
                           count,
213
                           query,
214
                           values);
215

216
        if (_IsCoilWriteCmd(command)) {
6✔
217
            delete[] static_cast<uint8_t*>(values);
2✔
218
        } else {
219
            delete[] static_cast<uint16_t*>(values);
4✔
220
        }
221
    }
222
}
9✔
223

224
void TModbusServer::_ProcessReadQuery(TStoreType type,
3✔
225
                                      TModbusAddressRange& range,
226
                                      uint8_t slave_id,
227
                                      int start,
228
                                      unsigned count,
229
                                      const TModbusQuery& query)
230
{
231
    // ask callback, then reply
232
    try {
233
        void* cache_ptr;
234
        int item_size;
235

236
        try {
237
            if (type == COIL || type == DISCRETE_INPUT) {
3✔
238
                cache_ptr = static_cast<uint8_t*>(mb->GetCache(type, slave_id)) + start;
3✔
239
                item_size = sizeof(uint8_t);
3✔
240
            } else {
241
                cache_ptr = static_cast<uint16_t*>(mb->GetCache(type, slave_id)) + start;
×
242
                item_size = sizeof(uint16_t);
×
243
            }
244
        } catch (const TModbusException& e) {
×
245
            mb->ReplyException(TReplyState::REPLY_ILLEGAL_ADDRESS, query);
×
246
            return;
×
247
        }
×
248

249
        int slave_offset = slave_id << 16;
3✔
250

251
        auto segments = range.getSegments(start + slave_offset,
252
                                          count); //->OnGetValue(type, mb->GetSlave(), start, count, cache_ptr);
3✔
253
        TReplyState reply = REPLY_ILLEGAL_ADDRESS;
3✔
254

255
        for (auto& s: segments) {
6✔
256
            const int count = s.getCount();
3✔
257
            reply = s.getParam()->OnGetValue(type, slave_id, s.getStart() - slave_offset, count, cache_ptr);
3✔
258

259
            if (item_size == sizeof(uint16_t)) {
3✔
260
                cache_ptr = static_cast<uint16_t*>(cache_ptr) + count;
×
261
            } else {
262
                cache_ptr = static_cast<uint8_t*>(cache_ptr) + count;
3✔
263
            }
264

265
            if (reply > 0)
3✔
266
                break;
×
267
        }
268

269
        if (reply <= 0)
3✔
270
            mb->Reply(query);
3✔
271
        else
272
            mb->ReplyException(reply, query);
×
273
    } catch (const WrongSegmentException& e) {
3✔
274
        mb->ReplyException(TReplyState::REPLY_ILLEGAL_ADDRESS, query);
×
275
    }
×
276
}
277

278
void TModbusServer::_ProcessWriteQuery(TStoreType type,
6✔
279
                                       TModbusAddressRange& range,
280
                                       uint8_t slave_id,
281
                                       int start,
282
                                       unsigned count,
283
                                       const TModbusQuery& query,
284
                                       const void* data_ptr)
285
{
286
    // reply then ask callback (modbus cache will contain required value)
287
    PModbusServerObserver obs;
6✔
288

289
    try {
290
        int item_size;
291

292
        if (type == COIL || type == DISCRETE_INPUT) {
6✔
293
            item_size = sizeof(uint8_t);
2✔
294
        } else {
295
            item_size = sizeof(uint16_t);
4✔
296
        }
297

298
        int slave_offset = slave_id << 16;
6✔
299

300
        auto segments = range.getSegments(start + slave_offset, count);
6✔
301

302
        TReplyState reply = REPLY_ILLEGAL_ADDRESS;
6✔
303

304
        for (auto& s: segments) {
14✔
305
            const int count = s.getCount();
8✔
306
            reply = s.getParam()->OnSetValue(type, slave_id, s.getStart() - slave_offset, count, data_ptr);
8✔
307

308
            if (item_size == sizeof(uint16_t)) {
8✔
309
                data_ptr = static_cast<const uint16_t*>(data_ptr) + count;
5✔
310
            } else {
311
                data_ptr = static_cast<const uint8_t*>(data_ptr) + count;
3✔
312
            }
313

314
            if (reply > 0)
8✔
315
                break;
×
316
        }
317

318
        if (reply <= 0) {
6✔
319
            mb->Reply(query);
6✔
320
        } else {
321
            mb->ReplyException(reply, query);
×
322
            throw 10; // just to exit from try {} block
×
323
        }
324
    } catch (const WrongSegmentException& e) {
6✔
325
        mb->ReplyException(TReplyState::REPLY_ILLEGAL_ADDRESS, query);
×
326
    } catch (const int&) {
×
327
        // dummy, just to get away
328
    }
×
329
}
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc