• 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

55.72
/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),
3✔
21
      Offset(config.Offset),
3✔
22
      Fd(-1),
3✔
23
      TimerFd(-1),
3✔
24
      Value(0),
3✔
25
      ValueUnfiltered(0),
3✔
26
      InterruptSupport(EInterruptSupport::UNKNOWN)
3✔
27
{
28
    Config = WBMQTT::MakeUnique<TGpioLineConfig>(config);
3✔
29

30
    if (!config.Type.empty()) {
3✔
31
        Counter = WBMQTT::MakeUnique<TGpioCounter>(config);
×
32
    }
33

34
    if (chip->IsValid())
3✔
35
        UpdateInfo();
×
36
    else if (Config->Direction == EGpioDirection::Output)
3✔
37
        Flags = GPIOLINE_FLAG_IS_OUT;
1✔
38
}
3✔
39

40
TGpioLine::TGpioLine(const TGpioLineConfig& config)
19✔
41
    : Chip(PGpioChip()),
19✔
42
      Offset(config.Offset),
19✔
43
      Fd(-1),
19✔
44
      TimerFd(-1),
19✔
45
      Value(0),
19✔
46
      ValueUnfiltered(0),
19✔
47
      InterruptSupport(EInterruptSupport::UNKNOWN)
38✔
48
{
49
    Name = "Dummy gpio line";
19✔
50
    Flags = GPIOLINE_FLAG_IS_OUT;
19✔
51
    Consumer = "null";
19✔
52
    Config = WBMQTT::MakeUnique<TGpioLineConfig>(config);
19✔
53

54
    if (!config.Type.empty()) {
19✔
55
        Counter = WBMQTT::MakeUnique<TGpioCounter>(config);
17✔
56
    }
57
}
19✔
58

59
TGpioLine::~TGpioLine()
22✔
60
{
61
    if (TimerFd > -1) {
22✔
62
        close(TimerFd);
×
63
    }
64
}
22✔
65

66
void TGpioLine::UpdateInfo()
×
67
{
68
    gpioline_info info{};
×
69

70
    info.line_offset = Offset;
×
71

72
    int retVal = ioctl(AccessChip()->GetFd(), GPIO_GET_LINEINFO_IOCTL, &info);
×
73
    if (retVal < 0) {
×
74
        LOG(Error) << "Unable to load " << Describe() << ": GPIO_GET_LINEINFO_IOCTL failed: " << strerror(errno);
×
75
        SetError("r");
×
76
        return;
×
77
    }
78

79
    Name = info.name;
×
80
    Flags = info.flags;
×
81
    Consumer = info.consumer;
×
82
}
83

84
std::string TGpioLine::DescribeShort() const
1✔
85
{
86
    std::string chipNum;
1✔
87
    if (AccessChip()->IsValid())
1✔
88
        chipNum = to_string(AccessChip()->GetNumber());
×
89
    else
90
        chipNum = "disconnected";
1✔
91

92
    ostringstream ss;
1✔
93
    ss << "GPIO line " << chipNum << ":" << Offset;
1✔
94
    if (Config) {
1✔
95
        ss << " (" << Config->Name << ")";
1✔
96
    }
97
    return ss.str();
2✔
98
}
1✔
99

100
std::string TGpioLine::Describe() const
×
101
{
102
    ostringstream ss;
×
103
    ss << "GPIO line ";
×
104

105
    if (Name.empty()) {
×
106
        ss << "(offset " << Offset << ")";
×
107
    } else {
108
        ss << "'" << Name << "'";
×
109
    }
110

111
    ss << " of " << AccessChip()->Describe();
×
112

113
    return ss.str();
×
114
}
×
115

116
std::string TGpioLine::DescribeVerbose() const
×
117
{
118
    ostringstream ss;
×
119
    ss << "GPIO line ";
×
120

121
    if (Name.empty()) {
×
122
        ss << "(offset " << Offset << ")";
×
123
    } else {
124
        ss << "'" << Name << "'";
×
125
    }
126

127
    ss << " of " << AccessChip()->Describe() << endl;
×
128
    ss << "\tConsumer: '" << Consumer << "'" << endl;
×
129
    ss << "\tIsOutput: '" << IsOutput() << "'" << endl;
×
130
    ss << "\tIsActiveLow: '" << IsActiveLow() << "'" << endl;
×
131
    ss << "\tIsUsed: '" << IsUsed() << "'" << endl;
×
132
    ss << "\tIsOpenDrain: '" << IsOpenDrain() << "'" << endl;
×
133
    ss << "\tIsOpenSource: '" << IsOpenSource() << "'" << endl;
×
134

135
    return ss.str();
×
136
}
×
137

138
const std::string& TGpioLine::GetName() const
11✔
139
{
140
    return Name;
11✔
141
}
142

143
const std::string& TGpioLine::GetConsumer() const
×
144
{
145
    return Consumer;
×
146
}
147

148
uint32_t TGpioLine::GetOffset() const
2✔
149
{
150
    return Offset;
2✔
151
}
152

153
uint32_t TGpioLine::GetFlags() const
×
154
{
155
    return Flags;
×
156
}
157

158
bool TGpioLine::IsOutput() const
×
159
{
160
    return Flags & GPIOLINE_FLAG_IS_OUT;
×
161
}
162

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

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

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

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

183
uint8_t TGpioLine::GetValue() const
62✔
184
{
185
    return Value.Get();
62✔
186
}
187

188
uint8_t TGpioLine::GetValueUnfiltered() const
38✔
189
{
190
    return ValueUnfiltered.Get();
38✔
191
}
192

193
void TGpioLine::SetValue(uint8_t value)
×
194
{
195
    if (!GetError().empty()) {
×
196
        LOG(Warn) << DescribeShort() << " has error " << GetError() << "; Will not set value " << to_string(value);
×
197
        SetError("w");
×
198
        return;
×
199
    }
200

201
    LOG(Debug) << DescribeShort() << " = " << static_cast<int>(value);
×
202
    gpiohandle_data data{};
×
203

204
    data.values[0] = value;
×
205

206
    if (ioctl(Fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data) < 0) {
×
207
        LOG(Error) << "Set " << to_string((int)value) << " to: " << DescribeShort()
×
208
                   << " GPIOHANDLE_SET_LINE_VALUES_IOCTL failed: " << strerror(errno);
×
209
        SetError("w");
×
210
        return;
×
211
    }
212

213
    SetCachedValue(value);
×
214
}
215

216
void TGpioLine::SetCachedValue(uint8_t value)
70✔
217
{
218
    Value.Set(value);
70✔
219
}
70✔
220

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

226
PGpioChip TGpioLine::AccessChip() const
1✔
227
{
228
    auto chip = Chip.lock();
1✔
229

230
    assert(chip);
1✔
231

232
    return chip;
1✔
233
}
234

235
bool TGpioLine::IsHandled() const
×
236
{
237
    return Fd > -1;
×
238
}
239

240
void TGpioLine::SetFd(int fd)
×
241
{
242
    Fd = fd;
×
243
    UpdateInfo();
×
244
}
×
245

246
void TGpioLine::SetError(const std::string& err)
5✔
247
{
248
    if (Error.find(err) == std::string::npos)
5✔
249
        Error += err;
3✔
250
}
5✔
251

252
void TGpioLine::ClearError()
×
253
{
254
    Error.clear();
×
255
}
×
256

257
const std::string& TGpioLine::GetError() const
3✔
258
{
259
    return Error;
3✔
260
}
261

262
int TGpioLine::GetFd() const
5✔
263
{
264
    return Fd;
5✔
265
}
266

267
void TGpioLine::SetTimerFd(int fd)
×
268
{
269
    if (TimerFd > -1) {
×
270
        close(TimerFd);
×
271
    }
272

273
    TimerFd = fd;
×
274
}
×
275

276
int TGpioLine::GetTimerFd() const
×
277
{
278
    assert(TimerFd > -1);
×
279

280
    return TimerFd;
×
281
}
282

283
const TTimePoint& TGpioLine::GetInterruptionTimepoint() const
14✔
284
{
285
    return PreviousInterruptionTimePoint;
14✔
286
}
287

288
EGpioEdge TGpioLine::GetInterruptEdge() const
22✔
289
{
290
    return Counter ? Counter->GetInterruptEdge() : EGpioEdge::BOTH;
22✔
291
}
292

293
std::chrono::microseconds TGpioLine::GetIntervalFromPreviousInterrupt(const TTimePoint& interruptTimePoint) const
14✔
294
{
295
    return chrono::duration_cast<chrono::microseconds>(interruptTimePoint - GetInterruptionTimepoint());
14✔
296
}
297

298
void TGpioLine::HandleInterrupt(const TTimePoint& interruptTimePoint)
18✔
299
{
300
    PreviousInterruptionTimePoint = interruptTimePoint;
18✔
301
}
18✔
302

303
void TGpioLine::Update()
×
304
{
305
    if (Counter) {
×
306
        Counter->Update(
×
307
            chrono::duration_cast<chrono::microseconds>(std::chrono::steady_clock::now() - GetInterruptionTimepoint()));
×
308
    }
309
}
×
310

311
const PUGpioCounter& TGpioLine::GetCounter() const
102✔
312
{
313
    return Counter;
102✔
314
}
315

316
const PUGpioLineConfig& TGpioLine::GetConfig() const
18✔
317
{
318
    assert(Config);
18✔
319

320
    return Config;
18✔
321
}
322

323
void TGpioLine::SetInterruptSupport(EInterruptSupport interruptSupport)
3✔
324
{
325
    InterruptSupport = interruptSupport;
3✔
326
}
3✔
327

328
EInterruptSupport TGpioLine::GetInterruptSupport() const
4✔
329
{
330
    return InterruptSupport;
4✔
331
}
332

333
bool TGpioLine::UpdateIfStable(const TTimePoint& checkTimePoint)
14✔
334
{
335
    auto fromLastTs = GetIntervalFromPreviousInterrupt(checkTimePoint);
14✔
336
    if (fromLastTs <= GetConfig()->DebounceTimeout) {
14✔
337
        return false;
3✔
338
    }
339

340
    // The line has held a steady level for the whole debounce window, so the
341
    // settled level is real (not bounce/noise). Commit it as the filtered value.
342
    bool previousStable = GetValue();
11✔
343
    bool newStable = GetValueUnfiltered();
11✔
344
    SetCachedValue(newStable);
11✔
345
    LOG(Debug) << "Value (" << newStable << ") on (" << GetName() << " is stable for " << fromLastTs.count() << "us";
11✔
346

347
    const auto& gpioCounter = GetCounter();
11✔
348
    if (gpioCounter) {
11✔
349
        // Count only when the held level is a real transition in the configured
350
        // direction.
351
        bool counted;
352
        switch (GetInterruptEdge()) {
11✔
353
            case EGpioEdge::RISING:
5✔
354
                counted = (!previousStable && newStable);
5✔
355
                break;
5✔
356
            case EGpioEdge::FALLING:
3✔
357
                counted = (previousStable && !newStable);
3✔
358
                break;
3✔
359
            default: // BOTH
3✔
360
                counted = (previousStable != newStable);
3✔
361
                break;
3✔
362
        }
363

364
        if (counted) {
11✔
365
            auto fromLastStableValTs =
366
                chrono::duration_cast<chrono::microseconds>(checkTimePoint - PreviousStableValAcquiredTimePoint);
6✔
367
            gpioCounter->HandleInterrupt(GetInterruptEdge(), fromLastStableValTs);
6✔
368
            PreviousStableValAcquiredTimePoint = checkTimePoint;
6✔
369
        }
370
    }
371
    return true;
11✔
372
}
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