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

wirenboard / wb-mqtt-gpio / 1

22 Aug 2025 09:07AM UTC coverage: 39.772%. First build
1

push

github

web-flow
Fix counters rising and falling edge interrupt bug

355 of 948 branches covered (37.45%)

1 of 14 new or added lines in 2 files covered. (7.14%)

698 of 1755 relevant lines covered (39.77%)

2.77 hits per line

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

47.57
/src/gpio_line.cpp
1
#include "gpio_line.h"
2
#include "exceptions.h"
3
#include "gpio_chip.h"
4
#include "gpio_counter.h"
5
#include "log.h"
6

7
#include <sys/ioctl.h>
8
#include <wblib/utils.h>
9

10
#include <cassert>
11
#include <sstream>
12
#include <string.h>
13
#include <unistd.h>
14

15
#define LOG(logger) ::logger.Log() << "[gpio line] "
16

17
using namespace std;
18

19
TGpioLine::TGpioLine(const PGpioChip& chip, const TGpioLineConfig& config)
3✔
20
    : Chip(chip),
21
      Offset(config.Offset),
3✔
22
      Fd(-1),
23
      TimerFd(-1),
24
      Value(0),
25
      ValueUnfiltered(0),
26
      InterruptSupport(EInterruptSupport::UNKNOWN),
27
      SkipInterrupt(false)
3✔
28
{
29
    Config = WBMQTT::MakeUnique<TGpioLineConfig>(config);
3✔
30

31
    if (!config.Type.empty()) {
3✔
32
        Counter = WBMQTT::MakeUnique<TGpioCounter>(config);
×
33
        // set skip interrupt flag to prevent false service startup interrupts when using gpiochip0
NEW
34
        if (Counter->GetInterruptEdge() != EGpioEdge::BOTH && AccessChip()->GetNumber() == 0) {
×
NEW
35
            SkipInterrupt = true;
×
36
        }
37
    }
38

39
    if (chip->IsValid())
3✔
40
        UpdateInfo();
×
41
    else if (Config->Direction == EGpioDirection::Output)
3✔
42
        Flags = GPIOLINE_FLAG_IS_OUT;
1✔
43
}
3✔
44

45
TGpioLine::TGpioLine(const TGpioLineConfig& config)
11✔
46
    : Chip(PGpioChip()),
11✔
47
      Offset(config.Offset),
11✔
48
      Fd(-1),
49
      TimerFd(-1),
50
      Value(0),
51
      ValueUnfiltered(0),
52
      InterruptSupport(EInterruptSupport::UNKNOWN)
22✔
53
{
54
    Name = "Dummy gpio line";
11✔
55
    Flags = GPIOLINE_FLAG_IS_OUT;
11✔
56
    Consumer = "null";
11✔
57
    Config = WBMQTT::MakeUnique<TGpioLineConfig>(config);
11✔
58

59
    if (!config.Type.empty()) {
11✔
60
        Counter = WBMQTT::MakeUnique<TGpioCounter>(config);
10✔
61
    }
62
}
11✔
63

64
TGpioLine::~TGpioLine()
14✔
65
{
66
    if (TimerFd > -1) {
14✔
67
        close(TimerFd);
×
68
    }
69
}
14✔
70

71
void TGpioLine::UpdateInfo()
×
72
{
73
    gpioline_info info{};
×
74

75
    info.line_offset = Offset;
×
76

77
    int retVal = ioctl(AccessChip()->GetFd(), GPIO_GET_LINEINFO_IOCTL, &info);
×
78
    if (retVal < 0) {
×
79
        LOG(Error) << "Unable to load " << Describe() << ": GPIO_GET_LINEINFO_IOCTL failed: " << strerror(errno);
×
80
        SetError("r");
×
81
        return;
×
82
    }
83

84
    Name = info.name;
×
85
    Flags = info.flags;
×
86
    Consumer = info.consumer;
×
87
}
88

89
std::string TGpioLine::DescribeShort() const
1✔
90
{
91
    std::string chipNum;
2✔
92
    if (AccessChip()->IsValid())
1✔
93
        chipNum = to_string(AccessChip()->GetNumber());
×
94
    else
95
        chipNum = "disconnected";
1✔
96

97
    ostringstream ss;
2✔
98
    ss << "GPIO line " << chipNum << ":" << Offset;
1✔
99
    if (Config) {
1✔
100
        ss << " (" << Config->Name << ")";
1✔
101
    }
102
    return ss.str();
2✔
103
}
104

105
std::string TGpioLine::Describe() const
×
106
{
107
    ostringstream ss;
×
108
    ss << "GPIO line ";
×
109

110
    if (Name.empty()) {
×
111
        ss << "(offset " << Offset << ")";
×
112
    } else {
113
        ss << "'" << Name << "'";
×
114
    }
115

116
    ss << " of " << AccessChip()->Describe();
×
117

118
    return ss.str();
×
119
}
120

121
std::string TGpioLine::DescribeVerbose() const
×
122
{
123
    ostringstream ss;
×
124
    ss << "GPIO line ";
×
125

126
    if (Name.empty()) {
×
127
        ss << "(offset " << Offset << ")";
×
128
    } else {
129
        ss << "'" << Name << "'";
×
130
    }
131

132
    ss << " of " << AccessChip()->Describe() << endl;
×
133
    ss << "\tConsumer: '" << Consumer << "'" << endl;
×
134
    ss << "\tIsOutput: '" << IsOutput() << "'" << endl;
×
135
    ss << "\tIsActiveLow: '" << IsActiveLow() << "'" << endl;
×
136
    ss << "\tIsUsed: '" << IsUsed() << "'" << endl;
×
137
    ss << "\tIsOpenDrain: '" << IsOpenDrain() << "'" << endl;
×
138
    ss << "\tIsOpenSource: '" << IsOpenSource() << "'" << endl;
×
139

140
    return ss.str();
×
141
}
142

143
const std::string& TGpioLine::GetName() const
3✔
144
{
145
    return Name;
3✔
146
}
147

148
const std::string& TGpioLine::GetConsumer() const
×
149
{
150
    return Consumer;
×
151
}
152

153
uint32_t TGpioLine::GetOffset() const
2✔
154
{
155
    return Offset;
2✔
156
}
157

158
uint32_t TGpioLine::GetFlags() const
×
159
{
160
    return Flags;
×
161
}
162

163
bool TGpioLine::IsOutput() const
×
164
{
165
    return Flags & GPIOLINE_FLAG_IS_OUT;
×
166
}
167

168
bool TGpioLine::IsActiveLow() const
×
169
{
170
    return Flags & GPIOLINE_FLAG_ACTIVE_LOW;
×
171
}
172

173
bool TGpioLine::IsUsed() const
×
174
{
175
    return Flags & GPIOLINE_FLAG_KERNEL;
×
176
}
177

178
bool TGpioLine::IsOpenDrain() const
×
179
{
180
    return Flags & GPIOLINE_FLAG_OPEN_DRAIN;
×
181
}
182

183
bool TGpioLine::IsOpenSource() const
×
184
{
185
    return Flags & GPIOLINE_FLAG_OPEN_SOURCE;
×
186
}
187

188
uint8_t TGpioLine::GetValue() const
26✔
189
{
190
    return Value.Get();
26✔
191
}
192

193
uint8_t TGpioLine::GetValueUnfiltered() const
16✔
194
{
195
    return ValueUnfiltered.Get();
16✔
196
}
197

198
void TGpioLine::SetValue(uint8_t value)
×
199
{
200
    if (!GetError().empty()) {
×
201
        LOG(Warn) << DescribeShort() << " has error " << GetError() << "; Will not set value " << to_string(value);
×
202
        SetError("w");
×
203
        return;
×
204
    }
205

206
    LOG(Debug) << DescribeShort() << " = " << static_cast<int>(value);
×
207
    gpiohandle_data data{};
×
208

209
    data.values[0] = value;
×
210

211
    if (ioctl(Fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data) < 0) {
×
212
        LOG(Error) << "Set " << to_string((int)value) << " to: " << DescribeShort()
×
213
                   << " GPIOHANDLE_SET_LINE_VALUES_IOCTL failed: " << strerror(errno);
×
214
        SetError("w");
×
215
        return;
×
216
    }
217

218
    SetCachedValue(value);
×
219
}
220

221
void TGpioLine::SetCachedValue(uint8_t value)
27✔
222
{
223
    Value.Set(value);
27✔
224
}
27✔
225

226
void TGpioLine::SetCachedValueUnfiltered(uint8_t value)
10✔
227
{
228
    ValueUnfiltered.Set(value);
10✔
229
}
10✔
230

231
PGpioChip TGpioLine::AccessChip() const
1✔
232
{
233
    auto chip = Chip.lock();
1✔
234

235
    assert(chip);
1✔
236

237
    return chip;
1✔
238
}
239

240
bool TGpioLine::IsHandled() const
×
241
{
242
    return Fd > -1;
×
243
}
244

245
void TGpioLine::SetFd(int fd)
×
246
{
247
    Fd = fd;
×
248
    UpdateInfo();
×
249
}
250

251
void TGpioLine::SetError(const std::string& err)
5✔
252
{
253
    if (Error.find(err) == std::string::npos)
5✔
254
        Error += err;
3✔
255
}
5✔
256

257
void TGpioLine::ClearError()
×
258
{
259
    Error.clear();
×
260
}
261

262
const std::string& TGpioLine::GetError() const
3✔
263
{
264
    return Error;
3✔
265
}
266

267
int TGpioLine::GetFd() const
5✔
268
{
269
    return Fd;
5✔
270
}
271

272
void TGpioLine::SetTimerFd(int fd)
×
273
{
274
    if (TimerFd > -1) {
×
275
        close(TimerFd);
×
276
    }
277

278
    TimerFd = fd;
×
279
}
280

281
int TGpioLine::GetTimerFd() const
×
282
{
283
    assert(TimerFd > -1);
×
284

285
    return TimerFd;
×
286
}
287

288
const TTimePoint& TGpioLine::GetInterruptionTimepoint() const
6✔
289
{
290
    return PreviousInterruptionTimePoint;
6✔
291
}
292

293
EGpioEdge TGpioLine::GetInterruptEdge() const
14✔
294
{
295
    return Counter ? Counter->GetInterruptEdge() : EGpioEdge::BOTH;
14✔
296
}
297

298
std::chrono::microseconds TGpioLine::GetIntervalFromPreviousInterrupt(const TTimePoint& interruptTimePoint) const
6✔
299
{
300
    return chrono::duration_cast<chrono::microseconds>(interruptTimePoint - GetInterruptionTimepoint());
12✔
301
}
302

303
EInterruptStatus TGpioLine::HandleInterrupt(EGpioEdge edge, const TTimePoint& interruptTimePoint)
6✔
304
{
305
    assert(edge != EGpioEdge::BOTH);
6✔
306

307
    auto interruptEdge = GetInterruptEdge();
6✔
308
    if (interruptEdge != EGpioEdge::BOTH && interruptEdge != edge) {
6✔
309
        if (Debug.IsEnabled()) {
×
310
            LOG(Debug) << DescribeShort() << " handle interrupt. Edge: " << GpioEdgeToString(edge)
×
311
                       << " interval: " << GetIntervalFromPreviousInterrupt(interruptTimePoint).count() << " us [skip]";
×
312
        }
313
        return EInterruptStatus::SKIP;
×
314
    }
315

316
    PreviousInterruptionTimePoint = interruptTimePoint;
6✔
317
    return EInterruptStatus::Handled;
6✔
318
}
319

320
void TGpioLine::Update()
×
321
{
322
    if (Counter) {
×
323
        Counter->Update(
×
324
            chrono::duration_cast<chrono::microseconds>(std::chrono::steady_clock::now() - GetInterruptionTimepoint()));
×
325
    }
326
}
327

328
const PUGpioCounter& TGpioLine::GetCounter() const
50✔
329
{
330
    return Counter;
50✔
331
}
332

333
const PUGpioLineConfig& TGpioLine::GetConfig() const
8✔
334
{
335
    assert(Config);
8✔
336

337
    return Config;
8✔
338
}
339

340
void TGpioLine::SetInterruptSupport(EInterruptSupport interruptSupport)
×
341
{
342
    InterruptSupport = interruptSupport;
×
343
}
344

345
EInterruptSupport TGpioLine::GetInterruptSupport() const
×
346
{
347
    return InterruptSupport;
×
348
}
349

NEW
350
bool TGpioLine::GetSkipInterrupt() const
×
351
{
NEW
352
    return SkipInterrupt;
×
353
}
354

NEW
355
void TGpioLine::ClearSkipInterrupt()
×
356
{
NEW
357
    SkipInterrupt = false;
×
358
}
359

360
bool TGpioLine::UpdateIfStable(const TTimePoint& checkTimePoint)
6✔
361
{
362
    auto fromLastTs = GetIntervalFromPreviousInterrupt(checkTimePoint);
6✔
363
    if (fromLastTs > GetConfig()->DebounceTimeout) {
6✔
364
        SetCachedValue(GetValueUnfiltered());
3✔
365
        LOG(Debug) << "Value (" << static_cast<bool>(GetValueUnfiltered()) << ") on (" << GetName() << " is stable for "
3✔
366
                   << fromLastTs.count() << "us";
3✔
367

368
        const auto& gpioCounter = GetCounter();
3✔
369
        if (gpioCounter) {
3✔
370
            auto fromLastStableValTs =
371
                chrono::duration_cast<chrono::microseconds>(checkTimePoint - PreviousStableValAcquiredTimePoint);
3✔
372
            gpioCounter->HandleInterrupt(GetInterruptEdge(), fromLastStableValTs);
3✔
373
        }
374

375
        PreviousStableValAcquiredTimePoint = checkTimePoint;
3✔
376
        return true;
3✔
377
    } else {
378
        return false;
3✔
379
    }
380
}
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