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

wirenboard / wb-mqtt-gpio / 93

31 Jul 2026 10:43AM UTC coverage: 44.281% (+4.5%) from 39.772%
93

push

github

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

493 of 1086 branches covered (45.4%)

875 of 1976 relevant lines covered (44.28%)

3.96 hits per line

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

0.0
/src/main.cpp
1
#include "config.h"
2
#include "gpio_driver.h"
3
#include "interruption_context.h"
4
#include "log.h"
5
#include "utils.h"
6

7
#include <wblib/json_utils.h>
8
#include <wblib/signal_handling.h>
9
#include <wblib/wbmqtt.h>
10

11
#include <getopt.h>
12
#include <sys/utsname.h>
13
#include <unordered_set>
14

15
// From LSB
16
#define EXIT_INVALIDARGUMENT 2 // Invalid or excess arguments
17
#define EXIT_NOTCONFIGURED 6   // The program is not configured
18

19
using namespace std;
20

21
using PGpioDriver = unique_ptr<TGpioDriver>;
22

23
#define LOG(logger) ::logger.Log() << "[gpio] "
24

25
const auto WBMQTT_DB_FILE = "/var/lib/wb-mqtt-gpio/libwbmqtt.db";
26
const auto CONFIG_FILE = "/etc/wb-mqtt-gpio.conf";
27
const auto SYSTEM_CONFIGS_DIR = "/var/lib/wb-mqtt-gpio/conf.d";
28
const auto CONFIG_SCHEMA_FILE = "/var/lib/wb-mqtt-confed/schemas/wb-mqtt-gpio.schema.json";
29
const auto GPIO_DRIVER_INIT_TIMEOUT_S = chrono::seconds(30);
30
const auto GPIO_DRIVER_STOP_TIMEOUT_S = chrono::seconds(60); // topic cleanup can take a lot of time
31

32
namespace
33
{
34
    enum class DebugLevel : int32_t
35
    {
36
        NONE = 0,
37
        SILENT_GPIO = -1,
38
        SILENT_MQTT = -2,
39
        SILENT_GPIO_MQTT = -3,
40
        DEBUG_GPIO = 1,
41
        DEBUG_MQTT = 2,
42
        DEBUG_GPIO_MQTT = 3
43
    };
44

45
    void PrintUsage()
×
46
    {
47
        cout << "Usage:" << endl
×
48
             << " wb-mqtt-gpio [options]" << endl
×
49
             << "Options:" << endl
×
50
             << "  -d level     enable debuging output:" << endl
×
51
             << "                 1 - gpio only;" << endl
×
52
             << "                 2 - mqtt only;" << endl
×
53
             << "                 3 - both;" << endl
×
54
             << "                 negative values - silent mode (-1, -2, -3))" << endl
×
55
             << "  -c config    config file" << endl
×
56
             << "  -p port      MQTT broker port (default: 1883)" << endl
×
57
             << "  -h IP        MQTT broker IP (default: localhost)" << endl
×
58
             << "  -u user      MQTT user (optional)" << endl
×
59
             << "  -P password  MQTT user password (optional)" << endl
×
60
             << "  -T prefix    MQTT topic prefix (optional)" << endl
×
61
             << "  -j           Make JSON for wb-mqtt-confed from "
62
                "/etc/wb-mqtt-gpio.conf"
×
63
             << endl
×
64
             << "  -J           Make /etc/wb-mqtt-gpio.conf from wb-mqtt-confed output" << endl;
×
65
    }
×
66

67
    void ParseCommandLine(int argc,
×
68
                          char* argv[],
69
                          WBMQTT::TMosquittoMqttConfig& mqttConfig,
70
                          string& customConfig,
71
                          DebugLevel& debugLevel)
72
    {
73
        int c;
74
        debugLevel = DebugLevel::NONE;
×
75

76
        while ((c = getopt(argc, argv, "d:c:h:p:u:P:T:jJ")) != -1) {
×
77
            switch (c) {
×
78
                case 'd':
×
79
                    debugLevel = static_cast<DebugLevel>(stoi(optarg));
×
80
                    break;
×
81
                case 'c':
×
82
                    customConfig = optarg;
×
83
                    break;
×
84
                case 'p':
×
85
                    mqttConfig.Port = stoi(optarg);
×
86
                    break;
×
87
                case 'h':
×
88
                    mqttConfig.Host = optarg;
×
89
                    break;
×
90
                case 'T':
×
91
                    mqttConfig.Prefix = optarg;
×
92
                    break;
×
93
                case 'u':
×
94
                    mqttConfig.User = optarg;
×
95
                    break;
×
96
                case 'P':
×
97
                    mqttConfig.Password = optarg;
×
98
                    break;
×
99
                case 'j':
×
100
                    try {
101
                        MakeJsonForConfed(CONFIG_FILE, SYSTEM_CONFIGS_DIR, CONFIG_SCHEMA_FILE);
×
102
                        exit(EXIT_SUCCESS);
×
103
                    } catch (const std::exception& e) {
×
104
                        LOG(Error) << "FATAL: " << e.what();
×
105
                        exit(EXIT_FAILURE);
×
106
                    }
×
107
                case 'J':
×
108
                    try {
109
                        MakeConfigFromConfed(SYSTEM_CONFIGS_DIR, CONFIG_SCHEMA_FILE);
×
110
                        exit(EXIT_SUCCESS);
×
111
                    } catch (const std::exception& e) {
×
112
                        LOG(Error) << "FATAL: " << e.what();
×
113
                        exit(EXIT_FAILURE);
×
114
                    }
×
115
                case '?':
×
116
                default:
117
                    PrintUsage();
×
118
                    exit(EXIT_INVALIDARGUMENT);
×
119
            }
120
        }
121

122
        if (optind < argc) {
×
123
            for (int index = optind; index < argc; ++index) {
×
124
                cout << "Skipping unknown argument " << argv[index] << endl;
×
125
            }
126
        }
127
    }
×
128

129
    struct TLinuxKernelVersion
130
    {
131
        int Version = -1;
132
        int Patchlevel = -1;
133
    };
134

135
    TLinuxKernelVersion GetLinuxKernelVersion()
×
136
    {
137
        utsname buf{};
×
138
        uname(&buf);
×
139
        TLinuxKernelVersion res;
×
140
        auto v = WBMQTT::StringSplit(buf.release, ".");
×
141
        res.Version = stol(v[0].c_str());
×
142
        if (v.size() > 1) {
×
143
            res.Patchlevel = stoul(v[1].c_str());
×
144
        }
145
        return res;
×
146
    }
×
147

148
    // Since linux kernel v5.7-rc1 interrupt events have monotonic clock timestamp.
149
    // Prior to v5.7-rc1 they had realtime clock timestamp.
150
    // https://github.com/torvalds/linux/commit/f8850206e160bfe35de9ca2e726ab6d6b8cb77dd
151
    bool HasMonotonicClockForInterruptionTimestamps(const TLinuxKernelVersion& kernel)
×
152
    {
153
        if (kernel.Version < 5) {
×
154
            return false;
×
155
        }
156
        if (kernel.Version > 5) {
×
157
            return true;
×
158
        }
159
        return (kernel.Patchlevel >= 7);
×
160
    }
161

162
    // In kernels prior to v5.3-rc3 there is a bug with events polarity and
163
    // GPIOHANDLE_REQUEST_ACTIVE_LOW. A kernel sends events with inverted polarity
164
    // not with requested.
165
    // https://github.com/torvalds/linux/commit/223ecaf140b1dd1c1d2a1a1d96281efc5c906984
166
    bool HasActiveLowEventsBug(const TLinuxKernelVersion& kernel)
×
167
    {
168
        if (kernel.Version > 5) {
×
169
            return false;
×
170
        }
171
        if (kernel.Version < 5) {
×
172
            return true;
×
173
        }
174
        return (kernel.Patchlevel < 3);
×
175
    }
176

177
    void SetDebugLevel(DebugLevel level)
×
178
    {
179
        switch (level) {
×
180
            case DebugLevel::NONE:
×
181
                break;
×
182
            case DebugLevel::SILENT_GPIO:
×
183
                Info.SetEnabled(false);
×
184
                break;
×
185

186
            case DebugLevel::SILENT_MQTT:
×
187
                WBMQTT::Info.SetEnabled(false);
×
188
                break;
×
189

190
            case DebugLevel::SILENT_GPIO_MQTT:
×
191
                WBMQTT::Info.SetEnabled(false);
×
192
                Info.SetEnabled(false);
×
193
                break;
×
194

195
            case DebugLevel::DEBUG_GPIO:
×
196
                Debug.SetEnabled(true);
×
197
                break;
×
198

199
            case DebugLevel::DEBUG_MQTT:
×
200
                WBMQTT::Debug.SetEnabled(true);
×
201
                break;
×
202

203
            case DebugLevel::DEBUG_GPIO_MQTT:
×
204
                WBMQTT::Debug.SetEnabled(true);
×
205
                Debug.SetEnabled(true);
×
206
                break;
×
207

208
            default:
×
209
                cout << "Invalid -d parameter value " << static_cast<int32_t>(level) << endl;
×
210
                PrintUsage();
×
211
                exit(EXIT_INVALIDARGUMENT);
×
212
        }
213
    }
×
214
} // namespace
215

216
int main(int argc, char* argv[])
×
217
{
218
    WBMQTT::TMosquittoMqttConfig mqttConfig;
×
219
    mqttConfig.Id = TGpioDriver::Name;
×
220
    DebugLevel debugLevel = DebugLevel::NONE;
×
221

222
    string configFileName;
×
223
    ParseCommandLine(argc, argv, mqttConfig, configFileName, debugLevel);
×
224

225
    WBMQTT::TPromise<void> initialized;
×
226

227
    WBMQTT::SetThreadName("wb-mqtt-gpio");
×
228
    WBMQTT::SignalHandling::Handle({SIGINT, SIGTERM});
×
229
    WBMQTT::SignalHandling::OnSignals({SIGINT, SIGTERM}, [&] { WBMQTT::SignalHandling::Stop(); });
×
230

231
    /* if signal arrived before driver is initialized:
232
        wait some time to initialize and then exit gracefully
233
        else if timed out: exit with error
234
    */
235
    WBMQTT::SignalHandling::SetWaitFor(GPIO_DRIVER_INIT_TIMEOUT_S, initialized.GetFuture(), [&] {
×
236
        LOG(Error) << "Driver takes too long to initialize. Exiting.";
×
237
        exit(EXIT_FAILURE);
×
238
    });
239

240
    /* if handling of signal takes too much time: exit with error */
241
    WBMQTT::SignalHandling::SetOnTimeout(GPIO_DRIVER_STOP_TIMEOUT_S, [&] {
×
242
        LOG(Error) << "Driver takes too long to stop. Exiting.";
×
243
        exit(EXIT_FAILURE);
×
244
    });
245
    WBMQTT::SignalHandling::Start();
×
246

247
    TLinuxKernelVersion kernel = GetLinuxKernelVersion();
×
248

249
    if (HasMonotonicClockForInterruptionTimestamps(kernel)) {
×
250
        LOG(Info) << "Kernel uses monotonic clock for interrupt timestamps";
×
251
        TInterruptionContext::SetMonotonicClockForInterruptTimestamp();
×
252
    }
253

254
    TGpioDriverConfig config;
×
255

256
    try {
257
        config = LoadConfig(CONFIG_FILE,
×
258
                            configFileName,
259
                            SYSTEM_CONFIGS_DIR,
260
                            CONFIG_SCHEMA_FILE,
261
                            {HasActiveLowEventsBug(kernel)});
×
262
    } catch (const std::exception& e) {
×
263
        LOG(Error) << "FATAL: " << e.what();
×
264
        return EXIT_NOTCONFIGURED;
×
265
    }
×
266

267
    if (debugLevel != DebugLevel::NONE) {
×
268
        SetDebugLevel(debugLevel);
×
269
    } else if (config.Debug) {
×
270
        SetDebugLevel(DebugLevel::DEBUG_GPIO);
×
271
    }
272

273
    try {
274
        auto mqttDriver =
275
            WBMQTT::NewDriver(WBMQTT::TDriverArgs{}
×
276
                                  .SetBackend(WBMQTT::NewDriverBackend(WBMQTT::NewMosquittoMqttClient(mqttConfig)))
×
277
                                  .SetId(mqttConfig.Id)
×
278
                                  .SetUseStorage(true)
×
279
                                  .SetReownUnknownDevices(true)
×
280
                                  .SetStoragePath(WBMQTT_DB_FILE),
×
281
                              config.PublishParameters);
×
282
        mqttDriver->StartLoop();
×
283
        mqttDriver->WaitForReady();
×
284
        auto gpioDriver = WBMQTT::MakeUnique<TGpioDriver>(mqttDriver, config);
×
285
        Utils::ClearMappingCache();
×
286
        gpioDriver->Start();
×
287

288
        WBMQTT::SignalHandling::OnSignals({SIGINT, SIGTERM}, [&] {
×
289
            gpioDriver.reset();
×
290
            mqttDriver->StopLoop();
×
291
            mqttDriver->Close();
×
292
            mqttDriver.reset();
×
293
        });
×
294

295
        initialized.Complete();
×
296
        WBMQTT::SignalHandling::Wait();
×
297
    } catch (const std::exception& e) {
×
298
        LOG(Error) << "FATAL: " << e.what();
×
299
        return EXIT_FAILURE;
×
300
    }
×
301

302
    return EXIT_SUCCESS;
×
303
}
×
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