GCC Code Coverage Report


Directory: ./
File: oled/fppoled.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 47 69 68.1%
Functions: 2 5 40.0%
Branches: 47 118 39.8%

Line Branch Exec Source
1
2 #include <signal.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <unistd.h>
7
8 #include "fpp-json.h"
9
10 #include "FPPOLEDUtils.h"
11 #include "OLEDPages.h"
12 #include "common.h"
13 #include "log.h"
14 #include "settings.h"
15
16 #if defined(PLATFORM_BBB) || defined(PLATFORM_BB64)
17 #include "util/BBBUtils.h"
18 #define PLAT_GPIO_CLASS BBBPinProvider
19 #elif defined(PLATFORM_PI)
20 #include "util/PiGPIOUtils.h"
21 #define PLAT_GPIO_CLASS PiGPIOPinProvider
22 #else
23 #include "util/TmpFileGPIO.h"
24 #define PLAT_GPIO_CLASS TmpFilePinProvider
25 #endif
26 #include <map>
27 #include <thread>
28
29 class WarningHolder {
30 static void AddWarning(const std::string& w);
31 // Mirror the real Warnings.h signature (incl. the defaulted data map) so
32 // GPIOUtils.o — compiled against the real header — links against this stub.
33 static void AddWarning(int id, const std::string& w, const std::map<std::string, std::string>& data = {});
34 };
35
36 ✗ void WarningHolder::AddWarning(const std::string& w) {
37 ✗ LogWarn(VB_GENERAL, "Warning: %s\n", w.c_str());
38 ✗ }
39 ✗ void WarningHolder::AddWarning(int id, const std::string& w, const std::map<std::string, std::string>& data) {
40 ✗ LogWarn(VB_GENERAL, "Warning: %s\n", w.c_str());
41 ✗ }
42
43 static FPPOLEDUtils* oled = nullptr;
44 // Only the flag: cleanup() frees memory and displayOff() talks I2C, neither
45 // safe in a handler (one landing inside malloc or a bus transfer deadlocked
46 // or corrupted them), and a signal before `oled` existed dereferenced null.
47 // run() sees the flag and main() tidies up.
48 ✗ void sigInteruptHandler(int sig) {
49 ✗ FPPOLEDUtils::exitSignal = SIGINT;
50 ✗ }
51
52 12 void sigTermHandler(int sig) {
53 12 FPPOLEDUtils::exitSignal = SIGTERM;
54 12 }
55
56 16 int main(int argc, char* argv[]) {
57 // "logFile" isn't a persisted setting - fppd.cpp only ever sets it in its
58 // own memory at startup, defaulting to FPP_FILE_LOG - so use that same
59 // constant directly rather than a getSetting() lookup that would just
60 // come back empty here. toStdOut=true keeps the journalctl-visible output
61 // (fppoled has no daemonize/foreground flag to check like fppd does) while
62 // also merging into the same shared fppd.log everything else writes to -
63 // see log.cpp's note that fppd.log is a multi-writer log, each line tagged
64 // with the real program name automatically. fppd asks for the stdout copy
65 // to be dropped once a line is in the file (SetSuppressDuplicateStdOut);
66 // fppoled deliberately does not - it is a handful of lines a boot, and
67 // `journalctl -u fppoled` is where they are looked for.
68 //
69 // Everything below logs rather than printf()s for the same reason: a raw
70 // printf reaches only the journal, which on these boxes is RAM-only and
71 // never reaches a Support Zip.
72
3/6
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
16 SetLogFile(FPP_FILE_LOG.c_str(), true);
73
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 LogInfo(VB_GENERAL, "FPP OLED Status Display Driver\n");
74
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
16 int lt = getRawSettingInt("LEDDisplayType", 7);
75
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 LogInfo(VB_GENERAL, " LED Display Type: %d\n", lt);
76
3/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 12 times.
16 if (!OLEDPage::InitializeDisplay(lt)) {
77 4 lt = 0;
78 }
79 16 int count = 0;
80
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 12 times.
16 if (argc > 1) {
81
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (lt != 0) {
82 ✗ OLEDPage::displayBootingNotice("FPP - File Systems");
83 }
84
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (strcmp(argv[1], "clear") == 0) {
85 4 unlink("/home/fpp/media/tmp/cape_detect_done");
86
3/8
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 4 times.
4 while (FileExists("/home/fpp/media/tmp/cape_detect_done") && count < 100) {
87 ✗ std::this_thread::sleep_for(std::chrono::milliseconds(100));
88 ✗ unlink("/home/fpp/media/tmp/cape_detect_done");
89 ✗ count++;
90 }
91 // The /var/tmp copy is a cache of the media/tmp one, kept so the
92 // cape logo can go up before cape detection has run. It has to be
93 // removed when there is no image to cache, not just skipped:
94 // detection produces no cape-image.xbm when the EEPROM is blank or
95 // its header does not match, so a "copy if it exists" alone leaves
96 // the previous cape's logo on the display forever -- readCapeImage()
97 // falls back to /var/tmp precisely when media/tmp has none.
98
3/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
4 if (FileExists("/home/fpp/media/tmp/cape-image.xbm")) {
99
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
8 CopyFileContents("/home/fpp/media/tmp/cape-image.xbm", "/var/tmp/cape-image.xbm");
100 } else {
101 2 unlink("/var/tmp/cape-image.xbm");
102 }
103 4 return 0;
104 }
105 }
106
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (lt != 0) {
107
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
24 OLEDPage::displayBootingNotice("FPP - Cape Detection");
108 }
109 // Wait for cape detection before touching any GPIO. There is no systemd
110 // ordering between fppinit and fppoled -- both are WantedBy=sysinit.target
111 // with DefaultDependencies=no -- so this file is the entire interlock, and it
112 // matters: cape detection re-muxes pins (it can borrow P9_17/P9_18 for an
113 // i2c1 probe), and a pin re-muxed out from under an already-open GPIO event
114 // request stops delivering edges until the request is remade.
115 //
116 // Giving up is still better than blocking the display forever, but it must
117 // not be silent: everything past this point is then racing cape detection,
118 // and "the OLED buttons only work after I restart fppoled" is exactly what
119 // that looks like from the outside.
120 12 count = 0;
121
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 bool capeDetectionDone = FileExists("/home/fpp/media/tmp/cape_detect_done");
122
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
12 while (!capeDetectionDone && count < 200) {
123 ✗ std::this_thread::sleep_for(std::chrono::milliseconds(100));
124 ✗ ++count;
125 ✗ capeDetectionDone = FileExists("/home/fpp/media/tmp/cape_detect_done");
126 }
127
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (!capeDetectionDone) {
128 ✗ LogWarn(VB_GENERAL, "Timed out after %dms waiting for cape detection; configuring GPIO anyway. "
129 "Pins re-muxed by a later cape detect will not deliver events until fppoled is restarted.\n",
130 count * 100);
131
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 } else if (count) {
132 ✗ LogInfo(VB_GENERAL, "Cape detection completed after %dms\n", count * 100);
133 }
134
135 // wait until after cape detection so gpio expanders are registered and available
136
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
12 if (lt < 10 || lt > 15) {
137 // The i2c1602 displays will already initialize GPIO internally
138
4/10
✓ 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 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
24 PinCapabilities::InitGPIO("FPPOLED", new PLAT_GPIO_CLASS());
139 }
140
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 LoadSettings(argv[0]);
141
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 int ledType = getRawSettingInt("LEDDisplayType", 99);
142
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 LogInfo(VB_GENERAL, " Led Type: %d\n", ledType);
143
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 fflush(stdout);
144
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (lt != ledType) {
145 ✗ if (ledType == 99) {
146 ✗ ledType = lt;
147 ✗ } else if (!OLEDPage::InitializeDisplay(ledType)) {
148 ✗ ledType = 0;
149 }
150 }
151 struct sigaction sigIntAction;
152 12 sigIntAction.sa_handler = sigInteruptHandler;
153 12 sigemptyset(&sigIntAction.sa_mask);
154 12 sigIntAction.sa_flags = 0;
155 12 sigaction(SIGINT, &sigIntAction, NULL);
156
157 struct sigaction sigTermAction;
158 12 sigTermAction.sa_handler = sigTermHandler;
159 12 sigemptyset(&sigTermAction.sa_mask);
160 12 sigTermAction.sa_flags = 0;
161 12 sigaction(SIGTERM, &sigTermAction, NULL);
162
163
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 oled = new FPPOLEDUtils(ledType);
164
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 oled->run();
165
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 oled->cleanup();
166
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 OLEDPage::displayOff();
167 12 return FPPOLEDUtils::exitSignal.load() == SIGTERM ? 0 : 1;
168 }
169