GCC Code Coverage Report


Directory: ./
File: oled/FPPOLEDUtils.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 268 387 69.3%
Functions: 16 17 94.1%
Branches: 412 1054 39.1%

Line Branch Exec Source
1 #include "fpp-pch.h"
2
3 #include "fpp-json.h"
4
5 #include <sys/ioctl.h>
6 #include <sys/mman.h>
7 #include <sys/stat.h>
8 #include <fcntl.h>
9 #include <gpiod.h>
10 #include <poll.h>
11 #include <set>
12 #include <unistd.h>
13
14 #include "I2C.h"
15
16 #include "FPPOLEDUtils.h"
17 #include "../common.h"
18 #include "../util/BBBUtils.h"
19
20 #include "FPPStatusOLEDPage.h"
21 #include "OLEDPages.h"
22
23 // Every interval in the run loop -- the 1s status refresh, button debounce,
24 // the display-off timeout -- is measured on this, never on GetTime(). The
25 // wall clock is routinely stepped during boot: the cape RTC registering with
26 // an unset 2000-01-01 time steps it back years, NTP steps it forward. A
27 // backward step left the refresh waiting for wall time to pass the pre-step
28 // timestamp, freezing the display on "Booting..." with fppd running.
29 189 static long long MonotonicMicros() {
30 struct timespec ts;
31 189 clock_gettime(CLOCK_MONOTONIC, &ts);
32 189 return ts.tv_sec * 1000000LL + ts.tv_nsec / 1000;
33 }
34
35 // shared memory area so other processes can see if the display is on
36 // as well as let fppoled know to force it off (if the pins need to be
37 // reconfigured so I2C no longer will work)
38
39 struct DisplayStatus {
40 unsigned int i2cBus;
41 volatile unsigned int displayOn;
42 volatile unsigned int forceOff;
43 };
44 static DisplayStatus* currentStatus;
45 static std::string controlPin;
46
47 extern I2C_DeviceT I2C_DEV_2;
48
49 12 FPPOLEDUtils::FPPOLEDUtils(int ledType) :
50 12 _ledType(ledType) {
51
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 int smfd = shm_open("fppoled", O_CREAT | O_RDWR, 0);
52 12 ftruncate(smfd, 1024);
53 12 currentStatus = (DisplayStatus*)mmap(0, 1024, PROT_WRITE | PROT_READ, MAP_SHARED, smfd, 0);
54
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 close(smfd);
55
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 int i2cb = OLEDPage::GetI2CBus();
56 12 currentStatus->i2cBus = i2cb;
57 12 currentStatus->displayOn = true;
58 12 currentStatus->forceOff = false;
59 12 statusPage = nullptr;
60 12 }
61 ✗ FPPOLEDUtils::~FPPOLEDUtils() {
62 ✗ cleanup();
63 ✗ delete statusPage;
64 ✗ }
65 12 void FPPOLEDUtils::cleanup() {
66
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 12 times.
21 for (auto& a : actions) {
67
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 delete a;
68 }
69 12 actions.clear();
70 12 }
71
72 std::atomic<int> FPPOLEDUtils::exitSignal{ 0 };
73
74 static const std::string EMPTY_STRING = "";
75
76 // gpio.json "rising"/"falling" entries were a single command object in FPP 9
77 // ({ "command": ..., "args": [...] }) and are an array of command objects in
78 // FPP 10 ([ { "command": ... }, ... ], possibly empty). Indexing an array value
79 // with a string key throws Json::LogicError and aborts fppoled, so look the
80 // navigation command up in a way that tolerates both shapes. Returns the argument
81 // of the first "OLED Navigation" command on the edge that has one, or "" if none.
82 18 static std::string findOLEDNavAction(const Json::Value& entry, const char* edge) {
83
3/6
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 18 times.
36 if (!JsonHas(entry, edge)) {
84 ✗ return "";
85 }
86
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 const Json::Value& v = entry[edge];
87 12 auto navArg = [](const Json::Value& cmd) -> std::string {
88
13/36
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 12 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 12 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 12 times.
✓ Branch 14 taken 12 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 12 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 12 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 12 times.
✗ Branch 21 not taken.
✓ Branch 22 taken 12 times.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✓ Branch 25 taken 12 times.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 29 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 32 not taken.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✗ Branch 35 not taken.
60 if (!cmd.isObject() || JsonString(cmd, "command") != "OLED Navigation") {
89 ✗ return "";
90 }
91 12 const Json::Value& args = cmd["args"];
92
4/8
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
12 if (args.isArray() && !args.empty() && args[0].isConvertibleTo(Json::stringValue)) {
93 12 return args[0].asString();
94 }
95 ✗ return "";
96 };
97
2/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
18 if (v.isObject()) {
98 ✗ return navArg(v);
99 }
100
2/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
18 if (v.isArray()) {
101
3/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 6 times.
18 for (Json::ArrayIndex i = 0; i < v.size(); i++) {
102
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 std::string a = navArg(v[i]);
103
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (!a.empty()) {
104 12 return a;
105 }
106
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 }
107 }
108
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
12 return "";
109 }
110
111 9 FPPOLEDUtils::InputAction::~InputAction() {
112
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 9 times.
19 for (auto& a : actions) {
113
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 delete a;
114 }
115 9 actions.clear();
116 9 }
117
118 56 bool FPPOLEDUtils::InputAction::Action::checkAction(int i, long long ntimeus) {
119
2/2
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 6 times.
56 if (gpiodPin) {
120 50 i = gpiodPin->getValue();
121 }
122
4/6
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 45 times.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
56 if (i <= actionValueMax && i >= actionValueMin && (ntimeus > nextActionTime)) {
123 // at least 10ms since last action. Should cover any debounce time
124 11 nextActionTime = ntimeus + minActionInterval;
125 11 return true;
126 }
127 45 return false;
128 }
129 52 const std::string& FPPOLEDUtils::InputAction::checkAction(int i, long long ntimeus) {
130
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 41 times.
97 for (auto& a : actions) {
131
3/4
✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 45 times.
56 if (a->checkAction(i, ntimeus)) {
132 11 return a->action;
133 }
134 }
135 41 return EMPTY_STRING;
136 }
137
138 12 bool FPPOLEDUtils::setupControlPin(const std::string& file) {
139
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (FileExists(file)) {
140 ✗ Json::Value root;
141 ✗ if (LoadJsonFromFile(file, root, JsonRoot::Object)) {
142 ✗ if (root.isMember("controls") && root["controls"].isMember("I2CEnable")) {
143 ✗ controlPin = root["controls"]["I2CEnable"].asString();
144 ✗ LogInfo(VB_GPIO, "Using control pin %s\n", controlPin.c_str());
145
146 ✗ const PinCapabilities& pin = PinCapabilities::getPinByName(controlPin);
147 ✗ pin.configPin("gpio", true);
148 ✗ pin.setValue(1);
149 ✗ return true;
150 }
151 }
152 ✗ }
153 12 return false;
154 }
155
156 6 FPPOLEDUtils::InputAction* FPPOLEDUtils::configureGPIOPin(const std::string& pinName,
157 const std::string& mode,
158 const std::string& edge,
159 const std::string& name) {
160
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 const PinCapabilities& pin = PinCapabilities::getPinByName(pinName);
161
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
6 if (pin.ptr() == nullptr) {
162 // Unresolved pin (e.g. an expander chip that hasn't probed yet). Let the
163 // caller skip it instead of building a polling action on the null pin.
164 ✗ return nullptr;
165 }
166
167
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 std::string nameDesc = "FPPOLED";
168
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (!name.empty()) {
169
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 nameDesc += "-" + name;
170 }
171
172
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 pin.configPin(mode, false, nameDesc);
173
174 // configPin() warns and carries on when the line request fails, which it does
175 // for a pin the kernel has handed to a peripheral -- under strict pinmux that
176 // is permanent for, say, a button sharing GPIO15 with an enabled uart0. Such
177 // a pin cannot be read: getValue() returns a flat 0, and for an active-low
178 // input 0 is "pressed", so registering it would fire the action continuously.
179 //
180 // This is NOT the same as having no event file. An expander line with no
181 // interrupt has no fd and is polled through getValue(), which is a working
182 // path and must keep working; the question here is only whether the line was
183 // ever acquired.
184
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
6 if (!pin.isAcquired()) {
185 ✗ LogWarn(VB_GPIO, "Skipping input %s: the line could not be acquired, so it cannot be read\n",
186 pinName.c_str());
187 ✗ return nullptr;
188 }
189
190
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
6 InputAction* action = new InputAction();
191
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 action->pin = pinName;
192
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 action->mode = mode;
193
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 action->gpiodPin = pin.ptr();
194
195 // Always request BOTH edges, even when this pin only acts on one of them.
196 // Some I2C GPIO expanders (e.g. the pca9675/pcf857x behind a device-tree
197 // interrupt line) have an irqchip that interrupts on every line change and
198 // ignores the requested edge type. If we ask the kernel for a single edge,
199 // gpiolib blindly stamps every interrupt with that edge, so a button's
200 // release gets reported as another press and the action fires twice. When
201 // both edges are requested, gpiolib samples the line level per interrupt and
202 // reports the true direction; checkAction() then filters to the wanted edge
203 // via the per-action min/max value. On regular SoC GPIOs the extra (release)
204 // edge is simply filtered out, so this is safe everywhere. The unused `edge`
205 // hint is retained in the signature for callers/back-compat.
206 (void)edge;
207
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 action->file = pin.requestEventFile(true, true);
208 6 return action;
209 6 }
210 10 void FPPOLEDUtils::setInputFlag(const std::string& action) {
211
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 7 times.
10 if (action == "Up") {
212 3 inputFlags |= 0x01;
213
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
7 } else if (action == "Down") {
214 2 inputFlags |= 0x02;
215
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 } else if (action == "Back") {
216 ✗ inputFlags |= 0x04;
217
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 } else if (action == "Enter") {
218 5 inputFlags |= 0x08;
219 ✗ } else if (action == "Test") {
220 ✗ inputFlags |= 0x10;
221 ✗ } else if (action == "Test/Down") {
222 ✗ inputFlags |= 0x10;
223 ✗ inputFlags |= 0x02;
224 }
225 10 }
226
227 12 bool FPPOLEDUtils::parseInputActionFromGPIO(const std::string& file) {
228 char vbuffer[256];
229 12 bool needsPolling = false;
230
3/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 9 times.
12 if (FileExists(file)) {
231
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 Json::Value root;
232
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (LoadJsonFromFile(file, root, JsonRoot::Array)) {
233
3/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 3 times.
9 for (int x = 0; x < root.size(); x++) {
234
8/16
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 6 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 6 times.
6 if (!root[x].isObject() || !root[x]["enabled"].asBool()) {
235 ✗ continue;
236 }
237
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
12 std::string edge = "";
238
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 std::string actionValue = "";
239
3/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 std::string mode = root[x]["mode"].asString();
240
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
6 if (mode == "") {
241 ✗ mode = "gpio";
242 }
243
3/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 std::string pinName = root[x]["pin"].asString();
244
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 std::string fallingAction = findOLEDNavAction(root[x], "falling");
245
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 std::string risingAction = findOLEDNavAction(root[x], "rising");
246
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (!fallingAction.empty()) {
247
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 edge = "falling";
248
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 setInputFlag(fallingAction);
249 }
250
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (!risingAction.empty()) {
251 ✗ if (edge == "falling") {
252 ✗ edge = "both";
253 } else {
254 ✗ edge = "rising";
255 }
256 ✗ setInputFlag(risingAction);
257 }
258
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 if (edge != "") {
259
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 InputAction* action = configureGPIOPin(pinName, mode, edge, risingAction.empty() ? fallingAction : risingAction);
260
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (action == nullptr) {
261 // pin didn't resolve to a real GPIO line -- skip rather than
262 // poll a null pin (getValue()==0) and flood with phantom presses
263 ✗ LogWarn(VB_GPIO, "Skipping input %s: GPIO line not available\n", pinName.c_str());
264 ✗ continue;
265 }
266
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
6 if (risingAction != "") {
267 ✗ LogInfo(VB_GPIO, "Configuring pin %s as input of type %s (mode: %s, file: %d)\n", action->pin.c_str(), risingAction.c_str(), action->mode.c_str(), action->file);
268 ✗ action->actions.push_back(new FPPOLEDUtils::InputAction::Action(risingAction, 1, 1, 100000, action->file == -1 ? action->gpiodPin : nullptr));
269 }
270
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 if (fallingAction != "") {
271
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 LogInfo(VB_GPIO, "Configuring pin %s as input of type %s (mode: %s, file: %d)\n", action->pin.c_str(), fallingAction.c_str(), action->mode.c_str(), action->file);
272
6/12
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 6 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
6 action->actions.push_back(new FPPOLEDUtils::InputAction::Action(fallingAction, 0, 0, 100000, action->file == -1 ? action->gpiodPin : nullptr));
273 }
274
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 actions.push_back(action);
275
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
6 if (action->file == -1) {
276 1 needsPolling = true;
277 }
278 }
279
6/12
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
6 }
280 }
281 3 }
282 12 return needsPolling;
283 }
284
285 12 bool FPPOLEDUtils::parseInputActions(const std::string& file) {
286 char vbuffer[256];
287 12 bool needsPolling = false;
288
3/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 9 times.
12 if (FileExists(file)) {
289
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 Json::Value root;
290
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (LoadJsonFromFile(file, root, JsonRoot::Object)) {
291
4/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 3 times.
6 for (int x = 0; x < root["inputs"].size(); x++) {
292
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 InputAction* action = new InputAction();
293
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
3 std::string pin = root["inputs"][x]["pin"].asString();
294
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 for (auto& a : actions) {
295 ✗ if (a->pin == pin && action) {
296 ✗ delete action;
297 ✗ action = nullptr;
298 // Built up and logged as one line: a log line is one
299 // record with its own timestamp/prefix, so the piecemeal
300 // printf() version would come out as a header line
301 // followed by an orphaned line per action.
302 ✗ std::string configured;
303 ✗ for (auto aa : a->actions) {
304 ✗ configured += aa->action;
305 ✗ configured += " ";
306 }
307 ✗ LogInfo(VB_GPIO, "Skipping %s. Already configured for actions: %s\n", pin.c_str(), configured.c_str());
308 ✗ }
309 }
310
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (action == nullptr) {
311 ✗ continue;
312 }
313
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 action->pin = pin;
314
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
3 action->mode = root["inputs"][x]["mode"].asString();
315
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (action->mode.find("gpio") != std::string::npos) {
316
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
3 std::string edge = root["inputs"][x]["edge"].asString();
317
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
3 std::string type = root["inputs"][x]["type"].asString();
318
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 int actionValue = (edge == "falling" ? 0 : 1);
319
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 const PinCapabilities& pin = PinCapabilities::getPinByName(action->pin);
320
321
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if (pin.ptr() == nullptr) {
322 // The pin didn't resolve to a real GPIO line -- e.g. an i2c
323 // expander whose chip hasn't probed yet (run() waits for this,
324 // but bail defensively if it timed out). Falling through would
325 // create a polling action on the null pin, whose getValue() is
326 // always 0, flooding the menu with phantom presses.
327 ✗ LogWarn(VB_GPIO, "Skipping input %s: GPIO line not available\n", action->pin.c_str());
328 ✗ delete action;
329 ✗ continue;
330 }
331
332
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 action->gpiodPin = pin.ptr();
333
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 std::string nameDesc = type;
334
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (type == "gpiod") {
335
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 nameDesc = "Interrupt";
336 }
337
338
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 pin.configPin(action->mode, false, nameDesc);
339 // Request BOTH edges even though each button only acts on one.
340 // I2C GPIO expanders (pca9675/pcf857x) interrupt on every line
341 // change and ignore the requested edge type; asking for a single
342 // edge makes the kernel mislabel the button release as another
343 // press, firing the action twice. With both edges requested,
344 // gpiolib reports the true direction and checkAction() (below)
345 // filters to the desired edge via actionValue. Harmless on real
346 // SoC GPIOs, where the extra release edge is just filtered out.
347
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 action->file = pin.requestEventFile(true, true);
348
349
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (type == "gpiod") {
350 // this is the mode for various gpio extenders like the pca9675 chip that
351 // use a single interrupt line (configured above) for all the buttons.
352 // The above will trigger an interrupt after which we will need to
353 // use libgpiod to get the value of each button to figure out
354 // which triggered the action. Thus, there are multiple actions
355
356
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
3 std::string label = root["inputs"][x]["chip"].asString();
357
6/10
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✓ Branch 9 taken 3 times.
7 for (int a = 0; a < root["inputs"][x]["actions"].size(); a++) {
358
6/12
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
4 std::string buttonaction = root["inputs"][x]["actions"][a]["action"].asString();
359
6/12
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
4 int gpioline = root["inputs"][x]["actions"][a]["line"].asInt();
360
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 std::string pinName = label + "-" + std::to_string(gpioline);
361
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 auto& pinCap = PinCapabilities::getPinByName(pinName);
362
363
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 if (pinCap.ptr()) {
364
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
8 pinCap.configPin("gpio", false, buttonaction);
365
366
5/12
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 4 times.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
4 action->actions.push_back(new FPPOLEDUtils::InputAction::Action(buttonaction, 0, 0, 100000, pinCap.ptr()));
367 // Record which navigation buttons exist, exactly as the
368 // one-pin-per-button branch below does. Menus that need a
369 // full Up/Down/Back/Enter set (the Network config pages)
370 // are hidden when this isn't done, so a cape whose buttons
371 // hang off an expander would silently lose them.
372
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 setInputFlag(buttonaction);
373 }
374 4 }
375
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (action->file == -1) {
376 // No interrupt line: the buttons are still readable via
377 // getValue() on each expander line, but only if we poll.
378 ✗ needsPolling = true;
379 }
380
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
3 } else if (action->file == -1) {
381 ✗ needsPolling = true;
382 ✗ action->actions.push_back(new FPPOLEDUtils::InputAction::Action(type, actionValue, actionValue, 100000, pin.ptr()));
383 } else {
384 ✗ LogInfo(VB_GPIO, "Configuring pin %s as input of type %s (mode: %s)\n", action->pin.c_str(), type.c_str(), action->mode.c_str());
385 ✗ action->actions.push_back(new FPPOLEDUtils::InputAction::Action(type, actionValue, actionValue, 100000));
386 ✗ setInputFlag(type);
387 }
388
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 actions.push_back(action);
389
2/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
3 } else if (action->mode == "ain") {
390 char path[256];
391 ✗ snprintf(path, sizeof(path), "/sys/bus/iio/devices/iio:device0/in_voltage%d_raw", root["inputs"][x]["input"].asInt());
392 ✗ if (FileExists(path)) {
393 ✗ action->file = open(path, O_RDONLY);
394 ✗ for (int a = 0; a < root["inputs"][x]["actions"].size(); a++) {
395 ✗ std::string buttonaction = root["inputs"][x]["actions"][a]["action"].asString();
396 ✗ int minValue = root["inputs"][x]["actions"][a]["minValue"].asInt();
397 ✗ int maxValue = root["inputs"][x]["actions"][a]["maxValue"].asInt();
398 ✗ LogInfo(VB_GPIO, "Configuring AIN input of type %s with range %d-%d\n", buttonaction.c_str(), minValue, maxValue);
399 ✗ action->actions.push_back(new FPPOLEDUtils::InputAction::Action(buttonaction, minValue, maxValue, 250000));
400 ✗ setInputFlag(buttonaction);
401 ✗ }
402
403 ✗ actions.push_back(action);
404 ✗ needsPolling = true;
405 }
406 }
407
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 }
408 }
409 3 }
410
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 fflush(stdout);
411 12 return needsPolling;
412 }
413
414 // Gather the GPIO input pin names referenced by the fppoled input configs so
415 // run() can wait for them to become resolvable before configuring them.
416 12 static std::set<std::string> collectConfiguredGpioPins() {
417 12 std::set<std::string> names;
418
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 Json::Value root;
419 // cape-inputs.json: { "inputs": [ { "pin", "mode":"gpio*", "type", [chip/actions] } ] }
420
3/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 9 times.
15 if (FileExists("/home/fpp/media/tmp/cape-inputs.json") &&
421
9/16
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 9 times.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 9 times.
✓ Branch 10 taken 3 times.
✓ Branch 11 taken 9 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
18 LoadJsonFromFile("/home/fpp/media/tmp/cape-inputs.json", root, JsonRoot::Object)) {
422
4/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 3 times.
6 for (int x = 0; x < root["inputs"].size(); x++) {
423
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
3 std::string mode = root["inputs"][x]["mode"].asString();
424
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (mode.find("gpio") == std::string::npos) {
425 ✗ continue;
426 }
427
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
3 std::string pin = root["inputs"][x]["pin"].asString();
428
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (!pin.empty()) {
429
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 names.insert(pin);
430 }
431
6/12
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
3 if (root["inputs"][x]["type"].asString() == "gpiod") {
432
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
3 std::string label = root["inputs"][x]["chip"].asString();
433
6/10
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✓ Branch 9 taken 3 times.
7 for (int a = 0; a < root["inputs"][x]["actions"].size(); a++) {
434
6/12
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
4 int gpioline = root["inputs"][x]["actions"][a]["line"].asInt();
435
3/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
4 names.insert(label + "-" + std::to_string(gpioline));
436 }
437 3 }
438
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 }
439 }
440 // gpio.json: [ { "enabled", "pin", "rising"/"falling": [ {"command":"OLED Navigation","args":[..]} ] } ]
441 // (FPP 9 wrote a single command object instead of an array; findOLEDNavAction accepts both)
442
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 root = Json::Value();
443
3/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 9 times.
15 if (FileExists("/home/fpp/media/config/gpio.json") &&
444
9/16
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 9 times.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 9 times.
✓ Branch 10 taken 3 times.
✓ Branch 11 taken 9 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
18 LoadJsonFromFile("/home/fpp/media/config/gpio.json", root, JsonRoot::Array)) {
445
3/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 3 times.
9 for (int x = 0; x < root.size(); x++) {
446
8/16
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 6 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 6 times.
6 if (!root[x].isObject() || !root[x]["enabled"].asBool()) {
447 ✗ continue;
448 }
449
3/8
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
12 bool nav = !findOLEDNavAction(root[x], "falling").empty() ||
450
2/12
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 6 times.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
6 !findOLEDNavAction(root[x], "rising").empty();
451
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (!nav) {
452 ✗ continue;
453 }
454
3/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 std::string pin = root[x]["pin"].asString();
455
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (!pin.empty()) {
456
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 names.insert(pin);
457 }
458 6 }
459 }
460 12 return names;
461 12 }
462
463 // Cape GPIO expanders (e.g. the pca9675 the buttons live on) are instantiated by
464 // a device-tree overlay applied during cape detection, and their I2C driver
465 // probes asynchronously -- the gpiochip (and its named lines) can appear a short
466 // time after cape_detect_done. If we configure the inputs before then, every pin
467 // resolves to the null pin and the menu floods with phantom presses. Wait, re-
468 // scanning as chips appear, until all configured input pins resolve (or we hit
469 // the timeout, after which the null-pin guards keep us from flooding).
470 12 static void waitForInputPins() {
471
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 std::set<std::string> names = collectConfiguredGpioPins();
472
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
12 if (names.empty()) {
473 6 return;
474 }
475 6 const int maxWaitMS = 15000;
476 6 int waited = 0;
477 while (true) {
478 16 std::vector<std::string> missing;
479
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 16 times.
49 for (const auto& n : names) {
480
4/6
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 33 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 23 times.
33 if (PinCapabilities::getPinByName(n).ptr() == nullptr) {
481
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 missing.push_back(n);
482 }
483 }
484
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 10 times.
16 if (missing.empty()) {
485
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
6 if (waited) {
486
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogInfo(VB_GPIO, "All %d configured input pin(s) resolved after %dms\n", (int)names.size(), waited);
487 }
488 6 return;
489 }
490
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (waited >= maxWaitMS) {
491 ✗ std::string missingNames;
492 ✗ for (const auto& n : missing) {
493 ✗ missingNames += " ";
494 ✗ missingNames += n;
495 }
496 ✗ LogWarn(VB_GPIO, "Timed out after %dms waiting for %d input pin(s) to appear:%s\n", waited, (int)missing.size(), missingNames.c_str());
497 ✗ return;
498 ✗ }
499
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 usleep(200000);
500 10 waited += 200;
501
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 PinCapabilities::RescanGPIO();
502
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 6 times.
26 }
503 12 }
504
505 12 void FPPOLEDUtils::run() {
506 char vbuffer[256];
507
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 waitForInputPins();
508
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
24 setupControlPin("/home/fpp/media/tmp/cape-info.json");
509
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
24 bool needsPolling = parseInputActionFromGPIO("/home/fpp/media/config/gpio.json");
510
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 needsPolling |= parseInputActions("/home/fpp/media/tmp/cape-inputs.json");
511
512
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
24 std::vector<struct pollfd> fdset(actions.size());
513
514
4/6
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 12 times.
12 if (actions.size() == 0 && _ledType == 0) {
515 // no display and no actions, nothing to do.
516 ✗ exit(0);
517 }
518
519 12 OLEDPage::SetHas4DirectionControls((inputFlags & 0x0F) == 0x0F);
520
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 LogInfo(VB_GPIO, "Input flags: 0x%02X (Up/Down/Back/Enter all present: %s)\n",
521 inputFlags, OLEDPage::Has4DirectionControls() ? "yes" : "no");
522
523
3/8
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 12 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
12 statusPage = new FPPStatusOLEDPage();
524
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 OLEDPage::SetCurrentPage(statusPage);
525
526 12 long long lastUpdateTime = 0;
527 12 long long ntime = MonotonicMicros();
528 12 long long lastActionTime = MonotonicMicros();
529
2/2
✓ Branch 0 taken 91 times.
✓ Branch 1 taken 12 times.
115 while (exitSignal.load() == 0) {
530 91 bool forcedOff = currentStatus->forceOff;
531
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 91 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 91 times.
91 if (OLEDPage::IsForcedOff() && !forcedOff) {
532 // turning back on
533 ✗ if (controlPin != "") {
534 ✗ LogInfo(VB_GPIO, "Re-enabling I2C Bus via pin: %s\n", controlPin.c_str());
535 ✗ const PinCapabilities& pin = PinCapabilities::getPinByName(controlPin);
536 ✗ pin.configPin("gpio", true);
537 ✗ pin.setValue(1);
538 }
539 ✗ OLEDPage::SetForcedOff(forcedOff);
540 ✗ currentStatus->displayOn = true;
541 ✗ OLEDPage::SetCurrentPage(statusPage);
542 ✗ lastActionTime = MonotonicMicros();
543
3/6
✓ Branch 0 taken 91 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 91 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 91 times.
91 } else if (!OLEDPage::IsForcedOff() && forcedOff) {
544 ✗ if (currentStatus->displayOn) {
545 ✗ if (OLEDPage::GetOLEDType() != OLEDPage::OLEDType::NONE) {
546 ✗ OLEDPage::displayOff();
547 }
548 }
549 ✗ if (controlPin != "") {
550 ✗ LogInfo(VB_GPIO, "Disabling I2C Bus via pin: %s\n", controlPin.c_str());
551 ✗ const PinCapabilities& pin = PinCapabilities::getPinByName(controlPin);
552 ✗ pin.configPin("gpio", true);
553 ✗ pin.setValue(0);
554 }
555 ✗ OLEDPage::SetForcedOff(forcedOff);
556 ✗ currentStatus->displayOn = false;
557 ✗ OLEDPage::SetCurrentPage(statusPage);
558 }
559
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 37 times.
91 if (ntime > (lastUpdateTime + 1000000)) {
560 54 bool displayOn = currentStatus->displayOn;
561
6/8
✓ Branch 0 taken 54 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 54 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 40 times.
✓ Branch 5 taken 14 times.
✓ Branch 6 taken 40 times.
✓ Branch 7 taken 14 times.
54 if (OLEDPage::GetCurrentPage() && OLEDPage::GetCurrentPage()->doIteration(displayOn)) {
562 40 lastActionTime = MonotonicMicros();
563 }
564 54 currentStatus->displayOn = displayOn;
565 54 lastUpdateTime = ntime;
566 }
567
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 68 times.
91 if (actions.empty()) {
568
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 sleep(1);
569 23 ntime = MonotonicMicros();
570 } else {
571 68 memset((void*)&fdset[0], 0, sizeof(struct pollfd) * actions.size());
572 68 int actionCount = 0;
573
2/2
✓ Branch 0 taken 97 times.
✓ Branch 1 taken 68 times.
165 for (int x = 0; x < actions.size(); x++) {
574
6/8
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 97 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✓ Branch 5 taken 34 times.
✓ Branch 6 taken 63 times.
✓ Branch 7 taken 34 times.
97 if (actions[x]->mode != "ain" && actions[x]->file >= 0) {
575 63 fdset[actionCount].fd = actions[x]->file;
576 63 fdset[actionCount].events = POLLIN | POLLPRI;
577 63 actions[x]->pollIndex = actionCount;
578 63 actionCount++;
579 }
580 }
581
582
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 34 times.
68 if (actionCount) {
583 // Wake no later than the next 1s page iteration. The status
584 // page counts iterations as seconds ("FPP Booting..." becomes
585 // "No Network" after 60), so a longer idle wait slows all of
586 // its timing down by the same factor.
587
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
34 int timeout = needsPolling ? 100 : 1000;
588 34 long long untilUpdate = (lastUpdateTime + 1000000 - MonotonicMicros()) / 1000 + 1;
589
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 25 times.
34 if (untilUpdate < timeout) {
590
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 timeout = untilUpdate < 1 ? 1 : (int)untilUpdate;
591 }
592
1/2
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
34 poll(&fdset[0], actionCount, timeout);
593 } else {
594
1/2
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
34 usleep(100000);
595 }
596 68 ntime = MonotonicMicros();
597
2/2
✓ Branch 0 taken 97 times.
✓ Branch 1 taken 68 times.
165 for (int x = 0; x < actions.size(); x++) {
598 97 std::string action;
599 // printf("%x: %s f: %d pi: %d\n", x, actions[x]->pin.c_str(), actions[x]->file, actions[x]->pollIndex);
600
2/4
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 97 times.
97 if (actions[x]->mode == "ain") {
601 ✗ lseek(actions[x]->file, 0, SEEK_SET);
602 ✗ int len = read(actions[x]->file, vbuffer, 255);
603 ✗ int v = atoi(vbuffer);
604 ✗ action = actions[x]->checkAction(v, ntime);
605
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 63 times.
97 } else if (actions[x]->file == -1) {
606
2/4
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 34 times.
✗ Branch 3 not taken.
34 action = actions[x]->checkAction(0, ntime);
607
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 45 times.
63 } else if (fdset[actions[x]->pollIndex].revents) {
608
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 int v = actions[x]->gpiodPin->readEventFromFile();
609
2/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
18 action = actions[x]->checkAction(v, ntime);
610 }
611
5/8
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 86 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 11 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 97 times.
97 if (action != "" && !currentStatus->displayOn) {
612 // just turn the display on if button is hit
613 ✗ if (!currentStatus->forceOff) {
614 ✗ currentStatus->displayOn = true;
615 ✗ OLEDPage::SetCurrentPage(statusPage);
616 ✗ lastUpdateTime = 0;
617 ✗ lastActionTime = ntime;
618 ✗ } else if ((action == "Test" || action == "Test/Down") && ((ntime - lastActionTime) > 70000)) {
619 ✗ LogDebug(VB_COMMAND, "OLED button action: %s (gap since last action: %lldus)\n", action.c_str(), (long long)(ntime - lastActionTime));
620 ✗ lastUpdateTime = 0;
621 ✗ lastActionTime = ntime;
622 ✗ if (OLEDPage::GetCurrentPage()) {
623 ✗ OLEDPage::GetCurrentPage()->doAction(action);
624 }
625 }
626
6/8
✓ Branch 0 taken 97 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 86 times.
✓ Branch 4 taken 11 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 11 times.
✓ Branch 7 taken 86 times.
97 } else if (action != "" && ((ntime - lastActionTime) > 70000)) { // account for some debounce time
627
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 LogDebug(VB_COMMAND, "OLED button action: %s (gap since last action: %lldus)\n", action.c_str(), (long long)(ntime - lastActionTime));
628 // force immediate update
629 11 lastUpdateTime = 0;
630 11 lastActionTime = ntime;
631
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 if (OLEDPage::GetCurrentPage()) {
632
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 OLEDPage::GetCurrentPage()->doAction(action);
633 }
634 }
635 97 }
636
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (ntime > (lastActionTime + 180000000)) {
637 ✗ if (OLEDPage::GetCurrentPage() != statusPage) {
638 ✗ OLEDPage::SetCurrentPage(statusPage);
639 }
640 ✗ OLEDPage::displayOff();
641 ✗ currentStatus->displayOn = false;
642 }
643 }
644 }
645 12 }
646