• 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

6.09
/src/gpio_driver.cpp
1
#include "gpio_driver.h"
2
#include "config.h"
3
#include "exceptions.h"
4
#include "gpio_chip_driver.h"
5
#include "gpio_counter.h"
6
#include "gpio_line.h"
7
#include "interruption_context.h"
8
#include "log.h"
9

10
#include <wblib/wbmqtt.h>
11

12
#include <cassert>
13
#include <sys/epoll.h>
14
#include <unistd.h>
15

16
#define LOG(logger) ::logger.Log() << "[gpio driver] "
17

18
using namespace std;
19
using namespace WBMQTT;
20

21
const char* const TGpioDriver::Name = "wb-gpio";
22
const auto EPOLL_TIMEOUT_MS = 500;
23
const auto EPOLL_EVENT_COUNT = 20;
24

25
namespace
26
{
27
    template<int N> inline bool EndsWith(const string& str, const char (&with)[N])
×
28
    {
29
        return str.rfind(with) == str.size() - (N - 1);
×
30
    }
31

32
    template<typename F> inline void SuppressExceptions(F&& fn, const char* place)
×
33
    {
34
        try {
35
            fn();
×
36
        } catch (const exception& e) {
×
37
            LOG(Warn) << "Exception at " << place << ": " << e.what();
×
38
        } catch (...) {
×
39
            LOG(Warn) << "Unknown exception in " << place;
×
40
        }
41
    }
×
42
} // namespace
43

44
TFuture<PControl> CreateOutputControl(WBMQTT::PLocalDevice device,
4✔
45
                                      const WBMQTT::PDriverTx& tx,
46
                                      PGpioLine line,
47
                                      const TGpioLineConfig& lineConfig,
48
                                      std::function<void(uint8_t)> setLineValueFn,
49
                                      const std::string& error)
50
{
51
    auto futureControl = device->CreateControl(tx,
4✔
52
                                               TControlArgs{}
×
53
                                                   .SetId(lineConfig.Name)
4✔
54
                                                   .SetType("switch")
8✔
55
                                                   .SetReadonly(false)
4✔
56
                                                   .SetUserData(line)
8✔
57
                                                   .SetRawValue(lineConfig.InitialState ? "1" : "0")
16✔
58
                                                   .SetError(error)
4✔
59
                                                   .SetDoLoadPrevious(lineConfig.LoadPreviousState)
4✔
60
                                                   .SetDurable());
8✔
61
    setLineValueFn(futureControl.GetValue()->GetValue().As<bool>() ? 1 : 0);
4✔
62
    return futureControl;
4✔
63
}
×
64

65
TGpioDriver::TGpioDriver(const WBMQTT::PDeviceDriver& mqttDriver, const TGpioDriverConfig& config)
×
66
    : MqttDriver(mqttDriver),
×
67
      Active(false)
×
68
{
69
    try {
70
        auto tx = MqttDriver->BeginTx();
×
71
        auto device = tx->CreateDevice(TLocalDeviceArgs{}
×
72
                                           .SetId(Name)
×
73
                                           .SetTitle(config.DeviceName)
×
74
                                           .SetIsVirtual(true)
×
75
                                           .SetDoLoadPrevious(false))
76
                          .GetValue();
×
77

78
        if (config.Chips.empty()) {
×
79
            wb_throw(TGpioDriverException, "no chips defined in config. Nothing to do");
×
80
        }
81

82
        for (const auto& chipConfig: config.Chips) {
×
83
            if (chipConfig.Lines.empty()) {
×
84
                LOG(Warn) << "No lines for chip at '" << chipConfig.Path << "'. Skipping";
×
85
                continue;
×
86
            }
87

88
            try {
89
                ChipDrivers.push_back(make_shared<TGpioChipDriver>(chipConfig));
×
90
            } catch (const TGpioDriverException& e) {
×
91
                LOG(Error) << "Failed to create chip driver for " << chipConfig.Path << ": " << e.what();
×
92
                continue;
×
93
            }
×
94

95
            const auto& chipDriver = ChipDrivers.back();
×
96
            const auto& mappedLines = chipDriver->MapLinesByOffset();
×
97
            const auto& mappedDisconnectedLines = chipDriver->MapInitiallyDisconnectedLinesByOffset();
×
98

99
            size_t lineNumber = 0;
×
100
            for (const auto& lineConfig: chipConfig.Lines) {
×
101

102
                PGpioLine line;
×
103

104
                const auto& itOffsetLine = mappedLines.find(lineConfig.Offset);
×
105
                if (itOffsetLine != mappedLines.end()) {
×
106
                    line = itOffsetLine->second;
×
107
                } else {
108
                    const auto& itDisconnectedLine = mappedDisconnectedLines.find(lineConfig.Offset);
×
109

110
                    if (itDisconnectedLine == mappedDisconnectedLines.end()) {
×
111
                        continue; // happens if chip driver was unable to initialize line
×
112
                    }
113
                    line = itDisconnectedLine->second;
×
114
                    line->SetError("r");
×
115
                }
116

117
                auto futureControl = TPromise<PControl>::GetValueFuture(nullptr);
×
118

119
                if (const auto& counter = line->GetCounter()) {
×
120
                    for (auto& idType: counter->GetIdsAndTypes(lineConfig.Name)) {
×
121
                        auto& id = idType.first;
×
122
                        auto& type = idType.second;
×
123

124
                        bool isTotal = EndsWith(id, "_total");
×
125

126
                        futureControl = device->CreateControl(
×
127
                            tx,
128
                            TControlArgs{}
×
129
                                .SetId(move(id))
×
130
                                .SetType(move(type))
×
131
                                .SetReadonly(lineConfig.Direction == EGpioDirection::Input && !isTotal)
×
132
                                .SetUserData(line)
×
133
                                .SetError(line->GetError())
×
134
                                .SetDoLoadPrevious(isTotal));
×
135

136
                        if (isTotal) {
×
137
                            auto initialValue = futureControl.GetValue()->GetValue().As<double>();
×
138
                            counter->SetInitialValues(initialValue);
×
139

140
                            LOG(Info) << "Set initial value for " << lineConfig.Name << " counter: " << initialValue;
×
141
                        }
142
                    }
×
143
                } else {
144
                    if (lineConfig.Direction == EGpioDirection::Input) {
×
145
                        futureControl = device->CreateControl(tx,
×
146
                                                              TControlArgs{}
×
147
                                                                  .SetId(lineConfig.Name)
×
148
                                                                  .SetType("switch")
×
149
                                                                  .SetReadonly(true)
×
150
                                                                  .SetUserData(line)
×
151
                                                                  .SetError(line->GetError())
×
152
                                                                  .SetRawValue(line->GetValue() == 1 ? "1" : "0"));
×
153
                    } else {
154
                        futureControl = CreateOutputControl(
×
155
                            device,
156
                            tx,
157
                            line,
158
                            lineConfig,
159
                            [&](uint8_t value) { line->SetValue(value); },
×
160
                            line->GetError());
×
161
                    }
162
                }
163

164
                ++lineNumber;
×
165

166
                if (lineNumber == chipConfig.Lines.size()) {
×
167
                    futureControl.Wait(); // wait for last control
×
168
                }
169
            }
×
170
        }
×
171

172
        if (ChipDrivers.empty()) {
×
173
            wb_throw(TGpioDriverException, "Failed to create any chip driver. Nothing to do");
×
174
        }
175

176
    } catch (const exception& e) {
×
177
        LOG(Error) << "Unable to create GPIO driver: " << e.what();
×
178
        throw;
×
179
    }
×
180

181
    EventHandlerHandle = mqttDriver->On<TControlOnValueEvent>([](const TControlOnValueEvent& event) {
×
182
        const auto& line = event.Control->GetUserData().As<PGpioLine>();
×
183
        std::string valueForPublishing;
×
184
        if (line->IsOutput()) {
×
185
            uint8_t value;
186
            if (event.RawValue == "1") {
×
187
                value = 1;
×
188
            } else if (event.RawValue == "0") {
×
189
                value = 0;
×
190
            } else {
191
                LOG(Warn) << "Invalid value: " << event.RawValue;
×
192
                return;
×
193
            }
194
            line->SetValue(value);
×
195
            valueForPublishing = event.RawValue;
×
196
        } else {
197
            char* end;
198
            float value = strtof(event.RawValue.c_str(), &end);
×
199
            if (end == event.RawValue.c_str()) {
×
200
                LOG(Warn) << "Invalid value: " << event.RawValue;
×
201
                return;
×
202
            }
203
            if (line->GetCounter()) {
×
204
                line->GetCounter()->SetInitialValues(value);
×
205
                valueForPublishing = line->GetCounter()->GetRoundedTotal();
×
206
            }
207
        }
208

209
        auto lineError = line->GetError();
×
210
        if (!lineError.empty()) {
×
211
            event.Control->GetDevice()->GetDriver()->AccessAsync(
×
212
                [=](const PDriverTx& tx) { event.Control->SetError(tx, lineError); });
×
213
        } else {
214
            event.Control->GetDevice()->GetDriver()->AccessAsync(
×
215
                [=](const PDriverTx& tx) { event.Control->SetRawValue(tx, valueForPublishing); });
×
216
        }
217
    });
×
218
}
×
219

220
TGpioDriver::~TGpioDriver()
×
221
{
222
    Stop();
×
223
    if (EventHandlerHandle) {
×
224
        Clear();
×
225
    }
226
}
×
227

228
void TGpioDriver::Start()
×
229
{
230
    {
231
        std::lock_guard<std::mutex> lg(ActiveMutex);
×
232
        if (Active) {
×
233
            wb_throw(TGpioDriverException, "attempt to start already started driver");
×
234
        }
235
        Active = true;
×
236
    }
×
237

238
    Worker = WBMQTT::MakeThread("GPIO worker", {[this] {
×
239
                                    LOG(Info) << "Started";
×
240

241
                                    int epfd = epoll_create(1); // creating epoll for Interrupts
×
242
                                    struct epoll_event events[EPOLL_EVENT_COUNT]{};
×
243

244
                                    WB_SCOPE_EXIT(close(epfd);)
×
245

246
                                    for (const auto& chipDriver: ChipDrivers) {
×
247
                                        chipDriver->AddToEpoll(epfd);
×
248
                                    }
249

250
                                    while (Active) {
×
251
                                        bool isHandled = false;
×
252
                                        if (int count = epoll_wait(epfd, events, EPOLL_EVENT_COUNT, EPOLL_TIMEOUT_MS)) {
×
253
                                            TInterruptionContext ctx{count, events};
×
254
                                            for (const auto& chipDriver: ChipDrivers) {
×
255
                                                isHandled |= chipDriver->HandleInterrupt(ctx);
×
256
                                            }
257
                                        } else {
258
                                            for (const auto& chipDriver: ChipDrivers) {
×
259
                                                isHandled |= chipDriver->PollLines();
×
260
                                            }
261
                                        }
262

263
                                        if (!isHandled) {
×
264
                                            continue;
×
265
                                        }
266

267
                                        auto tx = MqttDriver->BeginTx();
×
268
                                        auto device = tx->GetDevice(Name);
×
269

270
                                        for (const auto& chipDriver: ChipDrivers) {
×
271
                                            FOR_EACH_LINE(chipDriver, line)
×
272
                                            {
273
                                                line->Update();
×
274

275
                                                const auto err = line->GetError();
×
276
                                                if (!err.empty()) {
×
277
                                                    device->GetControl(line->GetConfig()->Name)->SetError(tx, err);
×
278
                                                } else {
279
                                                    if (const auto& counter = line->GetCounter()) {
×
280
                                                        for (const auto& idValue:
×
281
                                                             counter->GetIdsAndValues(line->GetConfig()->Name)) {
×
282
                                                            const auto& id = idValue.first;
×
283
                                                            const auto value = idValue.second;
×
284

285
                                                            device->GetControl(id)->SetRawValue(tx, value);
×
286
                                                        }
×
287
                                                    } else {
288
                                                        device->GetControl(line->GetConfig()->Name)
×
289
                                                            ->SetValue(tx, static_cast<bool>(line->GetValue()));
×
290
                                                    }
291
                                                }
292
                                            });
×
293
                                        }
294
                                    }
×
295

296
                                    LOG(Info) << "Stopped";
×
297
                                }});
×
298
}
×
299

300
void TGpioDriver::Stop()
×
301
{
302
    {
303
        std::lock_guard<std::mutex> lg(ActiveMutex);
×
304
        if (!Active) {
×
305
            LOG(Warn) << "attempt to stop not started driver";
×
306
            return;
×
307
        }
308
        Active = false;
×
309
    }
×
310

311
    LOG(Info) << "Stopping...";
×
312

313
    if (Worker->joinable()) {
×
314
        Worker->join();
×
315
    }
316

317
    Worker.reset();
×
318
}
319

320
void TGpioDriver::Clear() noexcept
×
321
{
322
    if (Active) {
×
323
        LOG(Error) << "Unable to clear driver while it's running";
×
324
        return;
×
325
    }
326

327
    LOG(Info) << "Cleaning...";
×
328

329
    SuppressExceptions([this] { MqttDriver->RemoveEventHandler(EventHandlerHandle); }, "TGpioDriver::Clear()");
×
330

331
    SuppressExceptions([this] { MqttDriver->BeginTx()->RemoveDeviceById(Name).Sync(); }, "TGpioDriver::Clear()");
×
332

333
    SuppressExceptions([this] { ChipDrivers.clear(); }, "TGpioDriver::Clear()");
×
334

335
    EventHandlerHandle = nullptr;
×
336

337
    LOG(Info) << "Cleaned";
×
338
}
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