• 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

0.0
/src/modbus_lmb_backend.cpp
1
#include "log.h"
2

3
#include "modbus_lmb_backend.h"
4

5
#include <cerrno>
6
#include <cstdlib>
7
#include <error.h>
8
#include <iostream>
9
#include <string>
10

11
#include <arpa/inet.h>
12
#include <cstring>
13
#include <sys/select.h>
14
#include <sys/socket.h>
15
#include <unistd.h>
16

17
#define LOG(logger) ::logger.Log() << "[modbus] "
18

19
TModbusBaseBackend::TModbusBaseBackend(): _context(nullptr), _error(0), slaveId(0), queryBuffer(nullptr)
×
20
{}
×
21

22
TModbusBaseBackend::~TModbusBaseBackend()
×
23
{
24
    for (auto& p: _mappings)
×
25
        modbus_mapping_free(p.second);
×
26

27
    if (_context)
×
28
        modbus_free(_context);
×
29

30
    delete[] queryBuffer;
×
31
}
×
32

33
void TModbusBaseBackend::SetSlave(uint8_t slave_id)
×
34
{
35
    slaveId = slave_id;
×
36
    if (modbus_set_slave(_context, slave_id))
×
37
        _error = errno;
×
38
}
×
39

40
void TModbusBaseBackend::AllocateCache(uint8_t slave_id, size_t di, size_t co, size_t ir, size_t hr)
×
41
{
42
    if (_mappings[slave_id])
×
43
        return; // TODO: reallocations?
×
44

45
    _mappings[slave_id] = modbus_mapping_new(co, di, hr, ir);
×
46

47
    if (!_mappings[slave_id])
×
48
        _error = errno;
×
49
}
50

51
void* TModbusBaseBackend::GetCache(TStoreType type, uint8_t slave_id)
×
52
{
53
    if (!_mappings[slave_id]) {
×
54
        throw TModbusException(std::string("Cache for slave ID ") + std::to_string(slave_id) + " is not allocated");
×
55
    }
56

57
    switch (type) {
×
58
        case DISCRETE_INPUT:
×
59
            return _mappings[slave_id]->tab_input_bits;
×
60
        case COIL:
×
61
            return _mappings[slave_id]->tab_bits;
×
62
        case INPUT_REGISTER:
×
63
            return _mappings[slave_id]->tab_input_registers;
×
64
        case HOLDING_REGISTER:
×
65
            return _mappings[slave_id]->tab_registers;
×
66
        default:
×
67
            throw TModbusException("Unknown store type: " + std::to_string(type));
×
68
    }
69
}
70

71
uint8_t TModbusBaseBackend::GetSlave()
×
72
{
73
    return slaveId;
×
74
}
75

76
void TModbusBaseBackend::SetDebug(bool debug)
×
77
{
78
    modbus_set_debug(_context, debug ? 1 : 0);
×
79
}
×
80

81
bool TModbusBaseBackend::Available()
×
82
{
83
    return !QueuedQueries.empty();
×
84
}
85

86
void TModbusBaseBackend::Reply(const TModbusQuery& q)
×
87
{
88
    if (q.size <= 0)
×
89
        return;
×
90

91
    uint8_t slave_id = 0;
×
92
    if (q.header_length > 0)
×
93
        slave_id = q.data[q.header_length - 1];
×
94

95
    if (_mappings.find(slave_id) == _mappings.end())
×
96
        throw TModbusException(std::string("Trying to reply on query with unknown slave ID ") +
×
97
                               std::to_string(slave_id));
×
98

99
    PreReply(q);
×
100

101
    if (modbus_reply(_context, q.data, q.size, _mappings[slave_id]) < 0)
×
102
        _error = errno;
×
103

104
    PostReply(q);
×
105
}
106

107
void TModbusBaseBackend::ReplyException(TReplyState e, const TModbusQuery& q)
×
108
{
109
    unsigned code;
110

111
    switch (e) {
×
112
        case REPLY_ILLEGAL_FUNCTION:
×
113
            code = MODBUS_EXCEPTION_ILLEGAL_FUNCTION;
×
114
            break;
×
115
        case REPLY_ILLEGAL_ADDRESS:
×
116
            code = MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
×
117
            break;
×
118
        case REPLY_ILLEGAL_VALUE:
×
119
            code = MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE;
×
120
            break;
×
121
        case REPLY_SERVER_FAILURE:
×
122
            code = MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE;
×
123
            break;
×
124
        default:
×
125
            return; // wtf
×
126
    }
127

128
    PreReply(q);
×
129

130
    if (modbus_reply_exception(_context, q.data, code) == -1)
×
131
        _error = errno;
×
132

133
    PostReply(q);
×
134
}
135

136
int TModbusBaseBackend::GetError()
×
137
{
138
    return _error;
×
139
}
140

141
std::string TModbusBaseBackend::GetStrError()
×
142
{
143
    return std::string("libmodbus error: ") + std::string((char*)modbus_strerror(_error));
×
144
}
145

146
TModbusQuery TModbusBaseBackend::ReceiveQuery(bool block)
×
147
{
148
    if (block && !Available())
×
149
        WaitForMessages(-1);
×
150

151
    if (Available()) {
×
152
        TModbusQuery q = QueuedQueries.front();
×
153
        QueuedQueries.pop();
×
154
        return q;
×
155
    } else {
×
156
        return TModbusQuery::emptyQuery();
×
157
    }
158
}
159

160
TModbusTCPBackend::TModbusTCPBackend(const char* hostname, int port): server_socket(-1), fd_max(-1)
×
161
{
162
    char port_buffer[6]; // 5 dec symbols + \0
163
    std::snprintf(port_buffer, 6, "%u", port);
×
164
    _context = modbus_new_tcp_pi(hostname, port_buffer);
×
165

166
    if (!_context)
×
167
        throw TModbusException("can't allocate libmodbus context");
×
168

169
    queryBuffer = new uint8_t[MODBUS_TCP_MAX_ADU_LENGTH];
×
170
}
×
171

172
void TModbusTCPBackend::Listen()
×
173
{
174
    if (server_socket >= 0)
×
175
        throw TModbusException("No opened socket to listen");
×
176

177
    server_socket = modbus_tcp_pi_listen(_context, NB_CONNECTIONS);
×
178
    if (server_socket < 1)
×
179
        _error = errno;
×
180

181
    fd_max = server_socket;
×
182

183
    // configure select() stuff
184
    FD_ZERO(&refset);
×
185
    FD_SET(server_socket, &refset);
×
186

187
    LOG(Info) << "Modbus listening";
×
188
}
×
189

190
int TModbusTCPBackend::WaitForMessages(int timeoutMilliS)
×
191
{
192
    int num_msgs = 0;
×
193

194
    fd_set rdset = refset;
×
195

196
    struct timeval tv;
197
    tv.tv_usec = (timeoutMilliS % 1000) * 1000;
×
198
    tv.tv_sec = timeoutMilliS / 1000;
×
199

200
    int res = select(fd_max + 1, &rdset, NULL, NULL, timeoutMilliS == -1 ? NULL : &tv);
×
201
    if (res == 0) {
×
202
        return 0; // just tell that no messages are available
×
203
    }
204

205
    if (res == -1) {
×
206
        if (errno == EINTR)
×
207
            return 0; // just tell that no messages are available
×
208
        throw TModbusException(std::string("Error while select(): ") + strerror(errno));
×
209
    }
210

211
    // retrieve all available data into queue
212
    for (int s = 0; s <= fd_max; s++) {
×
213
        if (!FD_ISSET(s, &rdset))
×
214
            continue;
×
215

216
        if (s == server_socket) { // accepting new connection
×
217
            struct sockaddr_in client;
218
            socklen_t addrlen = sizeof(client);
×
219
            memset(&client, 0, addrlen);
×
220

221
            int newfd = accept(server_socket, (struct sockaddr*)&client, &addrlen);
×
222
            if (newfd == -1) {
×
223
                throw TModbusException(std::string("Error while accept(): ") + strerror(errno));
×
224
            }
225

226
            FD_SET(newfd, &refset);
×
227
            if (newfd > fd_max)
×
228
                fd_max = newfd;
×
229

230
            LOG(Debug) << "Modbus incoming connection";
×
231
        } else { // receiving new query
232
            modbus_set_socket(_context, s);
×
233

234
            int rc = modbus_receive(_context, queryBuffer);
×
235
            if (rc > 0) {
×
236
                QueuedQueries.push(TModbusQuery(queryBuffer, rc, modbus_get_header_length(_context), s));
×
237
                num_msgs++;
×
238
            } else {
239
                // TODO: error handling
240
                LOG(Debug) << "Modbus closed connection";
×
241
                close(s);
×
242
                FD_CLR(s, &refset);
×
243

244
                if (s == fd_max)
×
245
                    fd_max--;
×
246
            }
247
        }
248
    }
249

250
    return num_msgs;
×
251
}
252

253
void TModbusTCPBackend::Close()
×
254
{
255
    fd_max = -1;
×
256
}
×
257

258
void TModbusTCPBackend::PreReply(const TModbusQuery& q)
×
259
{
260
    modbus_set_socket(_context, q.socket_fd);
×
261
}
×
262

263
void TModbusTCPBackend::PostReply(const TModbusQuery& q)
×
264
{}
×
265

266
TModbusRTUBackend::TModbusRTUBackend(const TModbusRTUBackendArgs& args): fd(-1)
×
267
{
268
    _context = modbus_new_rtu(args.Device.c_str(), args.BaudRate, args.Parity, args.DataBits, args.StopBits);
×
269

270
    if (!_context)
×
271
        throw TModbusException("can't allocate libmodbus context");
×
272

273
    queryBuffer = new uint8_t[MODBUS_RTU_MAX_ADU_LENGTH];
×
274
}
×
275

276
void TModbusRTUBackend::Listen()
×
277
{
278
    if (fd >= 0) {
×
279
        throw TModbusException("Already listening");
×
280
    }
281

282
    if (modbus_connect(_context) < 0) {
×
283
        _error = errno;
×
284
        throw TModbusException(std::string("Unable to connect: ") + strerror(errno));
×
285
    }
286

287
    fd = modbus_get_socket(_context);
×
288

289
    // configure select() stuff
290
    FD_ZERO(&refset);
×
291
    FD_SET(fd, &refset);
×
292

293
    LOG(Info) << "Modbus listening";
×
294
}
×
295

296
int TModbusRTUBackend::WaitForMessages(int timeout)
×
297
{
298
    int num_msgs = 0;
×
299

300
    fd_set rdset = refset;
×
301

302
    struct timeval tv;
303
    tv.tv_usec = (timeout % 1000) * 1000;
×
304
    tv.tv_sec = timeout / 1000;
×
305

306
    int res = select(fd + 1, &rdset, NULL, NULL, timeout == -1 ? NULL : &tv);
×
307
    if (res == 0) {
×
308
        return 0; // just tell that no messages are available
×
309
    }
310

311
    if (res == -1) {
×
312
        if (errno == EINTR)
×
313
            return 0; // just tell that no messages are available
×
314
        throw TModbusException(std::string("Error while select(): ") + strerror(errno));
×
315
    }
316

317
    int rc = modbus_receive(_context, queryBuffer);
×
318
    if (rc > 0) {
×
319
        QueuedQueries.push(TModbusQuery(queryBuffer, rc, modbus_get_header_length(_context), fd));
×
320
        ++num_msgs;
×
321
    } else {
322
        // TODO: error handling
323
        LOG(Debug) << "modbus_receive returned " << rc << " errno " << errno;
×
324
        if (rc < 0) {
×
325
            _error = errno;
×
326
            return rc;
×
327
        }
328
    }
329

330
    return num_msgs;
×
331
}
332

333
void TModbusRTUBackend::Close()
×
334
{
335
    modbus_close(_context);
×
336
    modbus_free(_context);
×
337
    fd = -1;
×
338
}
×
339

340
void TModbusRTUBackend::PreReply(const TModbusQuery& q)
×
341
{
342
    uint8_t slave_id = 0;
×
343
    if (q.header_length > 0)
×
344
        slave_id = q.data[q.header_length - 1];
×
345

346
    modbus_set_slave(_context, slave_id);
×
347
}
×
348

349
void TModbusRTUBackend::PostReply(const TModbusQuery& q)
×
350
{
351
    modbus_set_slave(_context, slaveId);
×
352
}
×
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