| 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 |