GCC Code Coverage Report


Directory: ./
File: OutputMonitor.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 518 744 69.6%
Functions: 45 56 80.4%
Branches: 593 1546 38.4%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the LGPL v2.1 as described in the
10 * included LICENSE.LGPL file.
11 */
12
13 #include "fpp-pch.h"
14
15 #include "fpp-json.h"
16 #include "fpphttp.h" // drogon/HTTP helpers used here; no longer pulled transitively (see fpphttp_types.h)
17 #include "fpphttp_clientip.h" // getEffectiveClientIP() - FPP-internal, not in the plugin API
18
19 #include <cinttypes>
20 #include <cmath>
21 #include <fcntl.h>
22 #include <list>
23 #include <map>
24 #include <stdint.h>
25 #include <stdlib.h>
26 #include <string>
27 #include <unistd.h>
28 #include <vector>
29
30 #include "Sequence.h"
31 #include "Timers.h"
32 #include "Warnings.h"
33 #include "common.h"
34 #include "gpio.h"
35 #include "log.h"
36 #include "settings.h"
37 #include "channeloutput/stringtesters/PixelCountStringTester.h"
38 #include "channeltester/ChannelTester.h"
39 #include "commands/Commands.h"
40 #include "sensors/Sensors.h"
41 #include "util/GPIOUtils.h"
42
43 #include "OutputMonitor.h"
44
45 OutputMonitor OutputMonitor::INSTANCE;
46
47 class CurrentMonitorBase {
48 public:
49 CurrentMonitorBase() {}
50 7 CurrentMonitorBase(const Json::Value& c) {
51 7 currentMonitorScale = c["scale"].asFloat();
52
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (c.isMember("offset")) {
53 ✗ currentMonitorOffset = c["offset"].asFloat();
54 }
55 7 }
56 14 virtual ~CurrentMonitorBase() {
57 14 }
58
59 1376 float getValue() {
60 1376 float f = getRawValue();
61 1376 f -= currentMonitorOffset;
62
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1376 times.
1376 if (f < 0) {
63 ✗ f = 0;
64 }
65 1376 f *= currentMonitorScale;
66 1376 return f;
67 }
68
69 virtual float getRawValue() = 0;
70
71 float currentMonitorScale = 1.0f;
72 float currentMonitorOffset = 0.0f;
73 };
74
75 class FileCurrentMonitor : public CurrentMonitorBase {
76 public:
77 7 FileCurrentMonitor(const Json::Value& c) :
78 7 CurrentMonitorBase(c) {
79
3/6
✓ 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.
7 currentMonitorFile = open(c["path"].asString().c_str(), O_NONBLOCK | O_RDONLY);
80 7 }
81 28 virtual ~FileCurrentMonitor() {
82
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
14 if (currentMonitorFile >= 0) {
83 14 close(currentMonitorFile);
84 }
85 28 }
86 1376 virtual float getRawValue() override {
87
1/2
✓ Branch 0 taken 1376 times.
✗ Branch 1 not taken.
1376 if (currentMonitorFile >= 0) {
88 1376 float f = 0;
89
2/2
✓ Branch 0 taken 4128 times.
✓ Branch 1 taken 1376 times.
5504 for (int x = 0; x < 3; x++) {
90 // pread: multiple status readers can sample the same monitor
91 // concurrently; a shared lseek+read would interleave offsets.
92 4128 char buf[12] = { 0 };
93
2/4
✓ Branch 0 taken 4128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4128 times.
✗ Branch 3 not taken.
4128 if (pread(currentMonitorFile, buf, sizeof(buf) - 1, 0) > 0) {
94 4128 f += atoi(buf);
95 }
96 }
97 1376 f /= 3.0;
98 1376 return f;
99 }
100 ✗ return 0;
101 }
102 int currentMonitorFile = -1;
103 };
104 class SensorCurrentMonitor : public CurrentMonitorBase {
105 public:
106 ✗ SensorCurrentMonitor(const Json::Value& c) :
107 ✗ CurrentMonitorBase(c) {
108 ✗ sensor = Sensors::INSTANCE.getSensorSource(c["sensor"].asString());
109 ✗ channel = c["channel"].asInt();
110 ✗ if (sensor) {
111 ✗ sensor->enable(channel);
112 }
113 ✗ }
114 ✗ virtual float getRawValue() override {
115 ✗ if (sensor) {
116 ✗ return sensor->getValue(channel);
117 }
118 ✗ return 0;
119 }
120
121 SensorSource* sensor = nullptr;
122 int channel = 0;
123 };
124
125 class ReceiverInfo {
126 public:
127 bool isOn = false;
128 bool hasTriggered = false;
129 bool enabled = false;
130 int pixelCount = -1;
131 int configuredCount = -1;
132 float current = 0.0f;
133
134 std::string warning;
135 int retryCount = 0;
136 int okCount = 0;
137 long long firstTriggerTime = 0;
138 long long nextRetryTime = 0;
139 };
140
141 class PortPinInfo {
142 public:
143
3/4
✓ Branch 0 taken 438 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2628 times.
✓ Branch 3 taken 438 times.
3066 PortPinInfo() {
144 438 }
145
1/2
✓ Branch 0 taken 438 times.
✗ Branch 1 not taken.
3066 ~PortPinInfo() {
146
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 438 times.
438 if (currentMonitor) {
147 ✗ delete currentMonitor;
148 }
149
2/2
✓ Branch 0 taken 2628 times.
✓ Branch 1 taken 438 times.
3504 }
150
151 580 void setConfig(const std::string& n, const Json::Value& c) {
152 580 name = n;
153 580 config = c;
154
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 580 times.
580 if (config.isMember("row")) {
155 ✗ row = config["row"].asInt();
156 }
157
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 580 times.
580 if (config.isMember("col")) {
158 ✗ col = config["col"].asInt();
159 }
160
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 580 times.
580 if (config.isMember("bank")) {
161 ✗ bankLabel = config["bank"].asString();
162 }
163 580 }
164
165 std::string name;
166 std::string bankLabel;
167 Json::Value config;
168 uint32_t group = 0;
169
170 const PinCapabilities* enablePin = nullptr;
171 bool highToEnable = true;
172 const PinCapabilities* eFusePin = nullptr;
173 int eFuseOKValue = 0;
174
175 const PinCapabilities* eFuseInterruptPin = nullptr;
176 CurrentMonitorBase* currentMonitor = nullptr;
177
178 bool isSmartReceiver = false;
179 ReceiverInfo receivers[6];
180
181 int row = -1;
182 int col = -1;
183
184 119 void reset() {
185 119 name = "";
186 119 bankLabel = "";
187
1/2
✓ Branch 0 taken 119 times.
✗ Branch 1 not taken.
119 config = Json::Value();
188 119 group = 0;
189
190 119 enablePin = nullptr;
191 119 highToEnable = true;
192 119 eFusePin = nullptr;
193 119 eFuseOKValue = 0;
194 119 eFuseInterruptPin = nullptr;
195
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 112 times.
119 if (currentMonitor) {
196
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 delete currentMonitor;
197 7 currentMonitor = nullptr;
198 }
199 119 isSmartReceiver = false;
200
2/2
✓ Branch 0 taken 714 times.
✓ Branch 1 taken 119 times.
833 for (auto& r : receivers) {
201 714 r = ReceiverInfo();
202 }
203 119 row = -1;
204 119 col = -1;
205 119 }
206
207 240 void appendTo(Json::Value& result) {
208
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
240 if (name.empty()) {
209 ✗ return;
210 }
211
1/2
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
240 Json::Value v;
212
2/4
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 240 times.
✗ Branch 3 not taken.
240 v["name"] = name;
213
214
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 188 times.
240 if (isSmartReceiver) {
215
2/4
✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 52 times.
✗ Branch 3 not taken.
52 v["smartReceivers"] = true;
216
2/2
✓ Branch 0 taken 312 times.
✓ Branch 1 taken 52 times.
364 for (int x = 0; x < 6; x++) {
217
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 312 times.
312 if (receivers[x].enabled) {
218 ✗ std::string sri(1, (char)('A' + x));
219 ✗ v[sri]["ma"] = receivers[x].current;
220 ✗ if (receivers[x].hasTriggered) {
221 ✗ v[sri]["status"] = false;
222 ✗ v[sri]["enabled"] = false;
223 } else {
224 ✗ v[sri]["enabled"] = receivers[x].isOn;
225 ✗ v[sri]["status"] = receivers[x].enabled;
226 }
227 ✗ if (receivers[x].pixelCount >= 0 && receivers[x].pixelCount < 0xFFFF) {
228 ✗ v[sri]["pixelCount"] = receivers[x].pixelCount;
229 }
230 ✗ }
231 }
232 } else {
233
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 186 times.
188 if (enablePin) {
234
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 int pv = enablePin->getValue();
235
4/12
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
2 v["enabled"] = (pv && highToEnable) || (!pv && !highToEnable);
236 }
237 // Only report a fuse status for ports that actually have a fuse pin
238 // to read. Ports with nothing but an enable (the local outputs on
239 // the K8-Pro/K16-Pro, where only the differential receivers report
240 // back) have nothing to detect, and a hard-coded "OK" there is a
241 // green light for a check that can never go red. Leaving the key
242 // out lets the UI say nothing instead of saying something false.
243
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 186 times.
188 if (eFusePin) {
244
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (receivers[0].isOn) {
245
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (receivers[0].hasTriggered) {
246 ✗ v["status"] = false;
247 } else {
248
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.
2 v["status"] = eFuseOKValue == eFusePin->getValue();
249 }
250 } else {
251 ✗ v["status"] = true;
252 }
253 }
254
255 // printf("Port %s isOn: %d hasTriggered: %d okVal: %d curVal: %d\n", name.c_str(), receivers[0].isOn, receivers[0].hasTriggered, eFuseOKValue, eFusePin->getValue());
256
1/2
✓ Branch 0 taken 188 times.
✗ Branch 1 not taken.
188 if (currentMonitor) {
257
1/2
✓ Branch 0 taken 188 times.
✗ Branch 1 not taken.
188 float f = currentMonitor->getValue();
258 188 int c = std::round(f);
259
2/4
✓ Branch 0 taken 188 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 188 times.
✗ Branch 3 not taken.
188 v["ma"] = c;
260 }
261
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 183 times.
188 if (receivers[0].pixelCount >= 0) {
262
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 v["pixelCount"] = receivers[0].pixelCount;
263 }
264 }
265
266
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 188 times.
240 if (row != -1) {
267
2/4
✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 52 times.
✗ Branch 3 not taken.
52 v["row"] = row;
268 }
269
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 188 times.
240 if (col != -1) {
270
2/4
✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 52 times.
✗ Branch 3 not taken.
52 v["col"] = col;
271 }
272
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
240 if (!bankLabel.empty()) {
273 ✗ v["bank"] = bankLabel;
274 }
275
1/2
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
240 result.append(v);
276 240 }
277 };
278
279 class FPPEnableOutputsCommand : public Command {
280 public:
281 static FPPEnableOutputsCommand INSTANCE;
282 2346 FPPEnableOutputsCommand() :
283
2/4
✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2346 times.
✗ Branch 3 not taken.
4692 Command("Outputs On") {
284 2346 }
285 2 virtual std::unique_ptr<Command::Result> run(const std::vector<std::string>& args) override {
286 2 OutputMonitor::INSTANCE.EnableOutputs();
287 2 return std::make_unique<Command::Result>("OK");
288 }
289 };
290
291 class FPPDisableOutputsCommand : public Command {
292 public:
293 static FPPDisableOutputsCommand INSTANCE;
294
295 2346 FPPDisableOutputsCommand() :
296
2/4
✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2346 times.
✗ Branch 3 not taken.
4692 Command("Outputs Off") {
297 2346 }
298 1 virtual std::unique_ptr<Command::Result> run(const std::vector<std::string>& args) override {
299 1 OutputMonitor::INSTANCE.DisableOutputs();
300 1 return std::make_unique<Command::Result>("OK");
301 }
302 };
303
304 class FPPEnablePortCommand : public Command {
305 public:
306 static FPPEnablePortCommand INSTANCE;
307 2346 FPPEnablePortCommand() :
308
2/4
✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2346 times.
✗ Branch 3 not taken.
4692 Command("Set Port Status") {
309
7/14
✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2346 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2346 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2346 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2346 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2346 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 2346 times.
✗ Branch 13 not taken.
18768 args.push_back(CommandArg("Port", "string", "Port").setContentListUrl("api/fppd/ports/list"));
310
5/10
✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2346 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2346 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2346 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2346 times.
✗ Branch 9 not taken.
14076 args.push_back(CommandArg("on", "bool", "On"));
311 2346 }
312 592 virtual std::unique_ptr<Command::Result> run(const std::vector<std::string>& args) override {
313
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 592 times.
592 if (args.size() < 2) {
314 ✗ return std::make_unique<Command::ErrorResult>("Required arguments not provided");
315 }
316
3/4
✓ Branch 0 taken 295 times.
✓ Branch 1 taken 297 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 295 times.
592 bool enable = args[1] == "true" || args[1] == "1";
317
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 592 times.
592 if (args[0] == "--ALL--") {
318 ✗ if (enable) {
319 ✗ OutputMonitor::INSTANCE.EnableOutputs();
320 } else {
321 ✗ OutputMonitor::INSTANCE.DisableOutputs();
322 }
323 } else {
324 592 OutputMonitor::INSTANCE.SetOutput(args[0], enable);
325 }
326 592 return std::make_unique<Command::Result>("OK");
327 }
328 };
329
330 class FPPCheckConfiguredPixelsCommand : public Command {
331 public:
332 static FPPCheckConfiguredPixelsCommand INSTANCE;
333
334 2346 FPPCheckConfiguredPixelsCommand() :
335
2/4
✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2346 times.
✗ Branch 3 not taken.
4692 Command("Check Pixel Count") {
336
8/16
✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2346 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2346 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2346 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2346 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2346 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 2346 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 2346 times.
✗ Branch 15 not taken.
18768 args.push_back(CommandArg("Ports", "multistring", "Ports").setContentListUrl("api/fppd/ports/list", false).setDefaultValue("--ALL--"));
337
6/12
✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2346 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2346 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2346 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2346 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2346 times.
✗ Branch 11 not taken.
14076 args.push_back(CommandArg("Sensitivity", "int", "Sensitivity").setRange(0, 20).setDefaultValue("2"));
338
7/14
✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2346 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2346 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2346 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2346 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2346 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 2346 times.
✗ Branch 13 not taken.
18768 args.push_back(CommandArg("Action", "string", "Action").setContentList({ "Warn", "Log" }).setDefaultValue("Warn"));
339 2346 }
340 ✗ virtual std::unique_ptr<Command::Result> run(const std::vector<std::string>& args) override {
341 ✗ std::vector<std::string> nargs;
342 ✗ nargs.push_back("50");
343 ✗ nargs.push_back("Output Specific");
344 ✗ nargs.push_back("--ALL--");
345 ✗ nargs.push_back("999");
346 ✗ LogDebug(VB_COMMAND, "\"Check Pixel Count\" command starting Test Mode to measure current draw\n");
347 ✗ CommandManager::INSTANCE.run("Test Start", nargs);
348 // Resolved here, against the defaults declared above, rather than
349 // indexed inside the timer: all three arguments are optional in
350 // practice, and a caller that supplied fewer than three left the
351 // lambda reading past the end of its captured vector a second later.
352 ✗ std::string ports = args.size() > 0 ? args[0] : "--ALL--";
353 ✗ int sensitivity = args.size() > 1 ? std::atoi(args[1].c_str()) : 2;
354 ✗ std::string action = args.size() > 2 ? args[2] : "Warn";
355 ✗ Timers::INSTANCE.addPeriodicTimer("CheckPixelCount", 1000, [ports, sensitivity, action]() {
356 ✗ if (CurrentBasedPixelCountPixelStringTester::INSTANCE.getCurrentStatus() == CurrentBasedPixelCountPixelStringTester::Status::Complete) {
357 ✗ OutputMonitor::INSTANCE.checkPixelCounts(ports, action, sensitivity);
358 ✗ std::vector<std::string> args;
359 ✗ LogDebug(VB_COMMAND, "\"Check Pixel Count\" command stopping Test Mode, current-based detection complete\n");
360 ✗ CommandManager::INSTANCE.run("Test Stop", args);
361 ✗ Timers::INSTANCE.stopPeriodicTimer("CheckPixelCount");
362 ✗ }
363 ✗ });
364
365 ✗ return std::make_unique<Command::Result>("OK");
366 ✗ }
367 };
368 FPPEnableOutputsCommand FPPEnableOutputsCommand::INSTANCE;
369 FPPDisableOutputsCommand FPPDisableOutputsCommand::INSTANCE;
370 FPPCheckConfiguredPixelsCommand FPPCheckConfiguredPixelsCommand::INSTANCE;
371 FPPEnablePortCommand FPPEnablePortCommand::INSTANCE;
372
373 2346 OutputMonitor::OutputMonitor() {
374 2346 }
375 458 OutputMonitor::~OutputMonitor() {
376
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 458 times.
458 for (auto pi : portPins) {
377 ✗ if (pi) {
378 ✗ delete pi;
379 }
380 }
381 458 portPins.clear();
382 458 pullHighOutputPins.clear();
383 458 pullLowOutputPins.clear();
384 458 fusePins.clear();
385 458 }
386
387 1885 void OutputMonitor::Initialize(std::map<int, std::function<bool(int)>>& callbacks) {
388 1885 eFuseRetryCount = getSettingInt("eFuseRetryCount", 0);
389 1885 eFuseRetryInterval = getSettingInt("eFuseRetryInterval", 100);
390
3/6
✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1885 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1885 times.
✗ Branch 5 not taken.
9265 registerSettingsListener("OutputMonitor", "eFuseRetryCount", [this](const std::string& value) {
391 ✗ eFuseRetryCount = getSettingInt("eFuseRetryCount", 0);
392 ✗ });
393
3/6
✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1885 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1885 times.
✗ Branch 5 not taken.
9265 registerSettingsListener("OutputMonitor", "eFuseRetryInterval", [this](const std::string& value) {
394 ✗ eFuseRetryInterval = getSettingInt("eFuseRetryInterval", 100);
395 ✗ });
396
2/4
✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1885 times.
✗ Branch 3 not taken.
3770 CommandManager::INSTANCE.addCategorizedCommand(&FPPEnableOutputsCommand::INSTANCE, "Outputs", 1);
397
2/4
✓ Branch 0 taken 1885 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1885 times.
✗ Branch 3 not taken.
1885 CommandManager::INSTANCE.addCategorizedCommand(&FPPDisableOutputsCommand::INSTANCE, "Outputs", 1);
398
2/2
✓ Branch 0 taken 69 times.
✓ Branch 1 taken 1816 times.
1885 if (!portPins.empty()) {
399
2/4
✓ Branch 0 taken 69 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 69 times.
✗ Branch 3 not taken.
138 CommandManager::INSTANCE.addCategorizedCommand(&FPPCheckConfiguredPixelsCommand::INSTANCE, "Outputs", 1);
400
2/4
✓ Branch 0 taken 69 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 69 times.
✗ Branch 3 not taken.
138 CommandManager::INSTANCE.addCategorizedCommand(&FPPEnablePortCommand::INSTANCE, "Outputs", 1);
401 }
402 1885 }
403 1867 void OutputMonitor::Cleanup() {
404
3/6
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1867 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1867 times.
✗ Branch 5 not taken.
7468 unregisterSettingsListener("OutputMonitor", "eFuseRetryCount");
405
3/6
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1867 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1867 times.
✗ Branch 5 not taken.
5601 unregisterSettingsListener("OutputMonitor", "eFuseRetryInterval");
406
1/2
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
1867 CommandManager::INSTANCE.removeCommand(&FPPEnableOutputsCommand::INSTANCE);
407
1/2
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
1867 CommandManager::INSTANCE.removeCommand(&FPPDisableOutputsCommand::INSTANCE);
408
2/2
✓ Branch 0 taken 70 times.
✓ Branch 1 taken 1797 times.
1867 if (!portPins.empty()) {
409
1/2
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
70 CommandManager::INSTANCE.removeCommand(&FPPCheckConfiguredPixelsCommand::INSTANCE);
410
1/2
✓ Branch 0 taken 70 times.
✗ Branch 1 not taken.
70 CommandManager::INSTANCE.removeCommand(&FPPEnablePortCommand::INSTANCE);
411 }
412
1/2
✓ Branch 0 taken 1867 times.
✗ Branch 1 not taken.
1867 std::unique_lock<std::shared_mutex> lock(gpioLock);
413 1867 eFuseRetries.clear();
414
2/2
✓ Branch 0 taken 459 times.
✓ Branch 1 taken 1867 times.
2326 for (auto pi : portPins) {
415
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 410 times.
459 if (pi) {
416
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 clearReceiverWarnings(pi);
417
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 delete pi;
418 }
419 }
420 1867 portPins.clear();
421 1867 pullHighOutputPins.clear();
422 1867 pullLowOutputPins.clear();
423 1867 fusePins.clear();
424 1867 }
425
426 ✗ void OutputMonitor::checkPixelCounts(const std::string& portList, const std::string& action, int sensitivty) {
427 ✗ std::unique_lock<std::shared_mutex> lock(gpioLock);
428 ✗ std::set<std::string> ports;
429 ✗ for (auto p : split(portList, ',')) {
430 ✗ if (p == "--ALL--") {
431 ✗ for (auto p : portPins) {
432 ✗ if (p) {
433 ✗ ports.insert(p->name);
434 }
435 }
436 } else {
437 ✗ ports.insert(p);
438 }
439 ✗ }
440 ✗ for (auto p : portPins) {
441 ✗ if (p && ports.find(p->name) != ports.end()) {
442 ✗ for (int x = 0; x < 6; x++) {
443 ✗ int diff = std::abs(p->receivers[x].configuredCount - p->receivers[x].pixelCount);
444 ✗ std::string newWarn;
445 ✗ if (diff > sensitivty) {
446 ✗ newWarn = p->name;
447 ✗ if (p->isSmartReceiver) {
448 ✗ newWarn += (char)('A' + x);
449 }
450 ✗ newWarn += " configured for " + std::to_string(p->receivers[x].configuredCount) + " pixels but " + std::to_string(p->receivers[x].pixelCount) + " pixels detected.";
451 }
452 ✗ if (action == "Warn" && newWarn != p->receivers[x].warning) {
453 ✗ if (!p->receivers[x].warning.empty()) {
454 ✗ WarningHolder::RemoveWarning(16, p->receivers[x].warning);
455 }
456 ✗ p->receivers[x].warning = newWarn;
457 ✗ if (!p->receivers[x].warning.empty()) {
458 ✗ WarningHolder::AddWarning(16, p->receivers[x].warning);
459 }
460 ✗ } else if (action == "Log") {
461 ✗ LogInfo(VB_CHANNELOUT, "%s\n", newWarn.c_str());
462 }
463 ✗ }
464 }
465 }
466 ✗ }
467
468 1958 void OutputMonitor::EnableOutputs() {
469
1/2
✓ Branch 0 taken 1958 times.
✗ Branch 1 not taken.
1958 std::unique_lock<std::shared_mutex> lock(gpioLock);
470
5/6
✓ Branch 0 taken 1951 times.
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1951 times.
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 1951 times.
1958 if (!pullHighOutputPins.empty() || !pullLowOutputPins.empty()) {
471
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 LogDebug(VB_CHANNELOUT, "Enabling outputs\n");
472 }
473 1958 outputsEnabled = true;
474
1/2
✓ Branch 0 taken 1958 times.
✗ Branch 1 not taken.
1958 PinCapabilities::SetMultiPinValue(pullHighOutputPins, 1);
475
1/2
✓ Branch 0 taken 1958 times.
✗ Branch 1 not taken.
1958 PinCapabilities::SetMultiPinValue(pullLowOutputPins, 0);
476
2/2
✓ Branch 0 taken 464 times.
✓ Branch 1 taken 1958 times.
2422 for (auto p : portPins) {
477
2/2
✓ Branch 0 taken 55 times.
✓ Branch 1 taken 409 times.
464 if (p) {
478
2/2
✓ Branch 0 taken 330 times.
✓ Branch 1 taken 55 times.
385 for (auto& r : p->receivers) {
479
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 317 times.
330 if (r.enabled) {
480 13 r.isOn = true;
481 13 r.hasTriggered = false;
482 }
483 }
484 }
485 }
486
2/2
✓ Branch 0 taken 464 times.
✓ Branch 1 taken 1958 times.
2422 for (auto p : portPins) {
487
8/10
✓ Branch 0 taken 55 times.
✓ Branch 1 taken 409 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 51 times.
✓ 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 taken 460 times.
464 if (p && p->eFusePin && p->eFusePin->getValue() == p->eFuseOKValue) {
488
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 clearEFuseWarning(p, 0);
489 }
490 }
491
1/2
✓ Branch 0 taken 1958 times.
✗ Branch 1 not taken.
1958 lock.unlock();
492
3/6
✓ Branch 0 taken 1958 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1958 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1958 times.
3916 if (CommandManager::INSTANCE.HasPreset("OUTPUTS_ENABLED")) {
493 ✗ CommandManager::INSTANCE.TriggerPreset("OUTPUTS_ENABLED");
494 }
495 1958 }
496 1947 void OutputMonitor::DisableOutputs() {
497
1/2
✓ Branch 0 taken 1947 times.
✗ Branch 1 not taken.
1947 std::unique_lock<std::shared_mutex> lock(gpioLock);
498
5/6
✓ Branch 0 taken 1941 times.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1941 times.
✓ Branch 4 taken 6 times.
✓ Branch 5 taken 1941 times.
1947 if (!pullHighOutputPins.empty() || !pullLowOutputPins.empty()) {
499
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 LogDebug(VB_CHANNELOUT, "Disabling outputs\n");
500 }
501 1947 outputsEnabled = false;
502
2/2
✓ Branch 0 taken 463 times.
✓ Branch 1 taken 1947 times.
2410 for (auto p : portPins) {
503
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 409 times.
463 if (p) {
504
2/2
✓ Branch 0 taken 324 times.
✓ Branch 1 taken 54 times.
378 for (auto& r : p->receivers) {
505 324 r.isOn = false;
506
2/2
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 204 times.
324 if (p->isSmartReceiver) {
507 120 r.current = 0;
508 }
509 }
510 }
511 }
512
1/2
✓ Branch 0 taken 1947 times.
✗ Branch 1 not taken.
1947 PinCapabilities::SetMultiPinValue(pullHighOutputPins, 0);
513
1/2
✓ Branch 0 taken 1947 times.
✗ Branch 1 not taken.
1947 PinCapabilities::SetMultiPinValue(pullLowOutputPins, 1);
514
1/2
✓ Branch 0 taken 1947 times.
✗ Branch 1 not taken.
1947 lock.unlock();
515
4/6
✓ Branch 0 taken 1947 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1947 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1945 times.
3894 if (CommandManager::INSTANCE.HasPreset("OUTPUTS_DISABLED")) {
516
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
4 CommandManager::INSTANCE.TriggerPreset("OUTPUTS_DISABLED");
517 }
518 1947 }
519
520 592 void OutputMonitor::SetOutput(const std::string& port, bool on) {
521 // Smart-receiver commands are collected under gpioLock and dispatched
522 // after it is released: srCallback lands in FalconV5Support, and calling
523 // out of the monitor with gpioLock held invites re-entry deadlocks.
524 struct SRAction {
525 int port;
526 int index;
527 const char* cmd;
528 };
529 592 std::vector<SRAction> srActions;
530 {
531
1/2
✓ Branch 0 taken 592 times.
✗ Branch 1 not taken.
592 std::unique_lock<std::shared_mutex> lock(gpioLock);
532 592 int pn = 0;
533
2/2
✓ Branch 0 taken 2368 times.
✓ Branch 1 taken 592 times.
2960 for (auto p : portPins) {
534
3/8
✓ Branch 0 taken 2368 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2368 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2368 times.
2368 if (p && p->name == port && p->enablePin) {
535 ✗ p->receivers[0].hasTriggered = false;
536 ✗ p->receivers[0].isOn = on;
537 ✗ int value = p->highToEnable ? on : !on;
538 ✗ clearEFuseWarning(p, 0);
539 ✗ p->enablePin->setValue(value);
540 ✗ return;
541
3/4
✓ Branch 0 taken 2368 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2332 times.
✓ Branch 3 taken 36 times.
2368 } else if (p && p->isSmartReceiver) {
542
2/2
✓ Branch 0 taken 13992 times.
✓ Branch 1 taken 2332 times.
16324 for (int x = 0; x < 6; x++) {
543
5/8
✓ Branch 0 taken 13992 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13992 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 13992 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 583 times.
✓ Branch 7 taken 13409 times.
41976 if (port == std::string(p->name) + std::string(1, 'A' + x)) {
544
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 583 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
583 bool isOn = p->receivers[x].isOn && !p->receivers[x].hasTriggered;
545
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 583 times.
583 if (p->receivers[x].hasTriggered) {
546 ✗ srActions.push_back({ pn, x, "ResetOutput" });
547
2/2
✓ Branch 0 taken 293 times.
✓ Branch 1 taken 290 times.
583 } else if (on != isOn) {
548
1/2
✓ Branch 0 taken 293 times.
✗ Branch 1 not taken.
293 srActions.push_back({ pn, x, "ToggleOutput" });
549 }
550 }
551 }
552 }
553 2368 pn++;
554 }
555
1/2
✓ Branch 0 taken 592 times.
✗ Branch 1 not taken.
592 }
556
2/2
✓ Branch 0 taken 293 times.
✓ Branch 1 taken 299 times.
592 if (!srActions.empty()) {
557 // Call through the member under srCallbackLock rather than copying it
558 // out first: a copy captures FalconV5Support's raw 'this' without
559 // owning it, so a config reload destroying that object between the
560 // copy and the call leaves us writing into freed memory.
561
1/2
✓ Branch 0 taken 293 times.
✗ Branch 1 not taken.
293 std::unique_lock<std::mutex> cbLock(srCallbackLock);
562
2/2
✓ Branch 0 taken 279 times.
✓ Branch 1 taken 14 times.
293 if (srCallback) {
563
2/2
✓ Branch 0 taken 279 times.
✓ Branch 1 taken 279 times.
558 for (auto& a : srActions) {
564
2/4
✓ Branch 0 taken 279 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 279 times.
✗ Branch 3 not taken.
837 srCallback(a.port, a.index, a.cmd);
565 }
566 }
567 293 }
568
1/2
✓ Branch 0 taken 592 times.
✗ Branch 1 not taken.
592 }
569 40 void OutputMonitor::setSmartReceiverEventCallback(std::function<void(int port, int index, const std::string& cmd)>&& f) {
570
1/2
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
40 std::unique_lock<std::mutex> lock(srCallbackLock);
571 40 srCallback = std::move(f);
572 40 }
573 506 void OutputMonitor::RemovePortConfiguration(int port, const Json::Value& config) {
574
1/2
✓ Branch 0 taken 506 times.
✗ Branch 1 not taken.
506 std::unique_lock<std::shared_mutex> lock(gpioLock);
575
1/2
✓ Branch 0 taken 506 times.
✗ Branch 1 not taken.
506 std::string name = "Port " + std::to_string(port + 1);
576
6/8
✓ Branch 0 taken 506 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 119 times.
✓ Branch 3 taken 387 times.
✓ Branch 4 taken 119 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 119 times.
✓ Branch 7 taken 387 times.
506 if (port < portPins.size() && portPins[port] && portPins[port]->name == name) {
577 119 PortPinInfo* pi = portPins[port];
578
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 100 times.
119 if (pi->enablePin) {
579 19 bool sharedWithEnabledPort = false;
580
2/2
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 11 times.
68 for (auto other : portPins) {
581
7/8
✓ Branch 0 taken 57 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 38 times.
✓ Branch 3 taken 19 times.
✓ Branch 4 taken 14 times.
✓ Branch 5 taken 24 times.
✓ Branch 6 taken 8 times.
✓ Branch 7 taken 6 times.
57 if (other && other != pi && other->enablePin == pi->enablePin && other->receivers[0].enabled) {
582 8 sharedWithEnabledPort = true;
583 8 break;
584 }
585 }
586
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 8 times.
19 if (!sharedWithEnabledPort) {
587
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 if (pi->highToEnable) {
588 11 pullHighOutputPins.remove(pi->enablePin);
589 } else {
590 ✗ pullLowOutputPins.remove(pi->enablePin);
591 }
592 }
593
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 pi->enablePin->releasePin();
594 }
595
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 119 times.
119 if (pi->eFuseInterruptPin) {
596 // One interrupt pin (and its GPIO callback) can serve several
597 // ports; only unregister it when the last port using it goes away.
598 // fusePins is keyed by the interrupt pin's name -- erasing it here
599 // is what lets a later AddPortConfiguration re-register the
600 // callback; leaving it behind means fuse trips are silently
601 // ignored after a config reload. getPinByName() matches on name
602 // exactly (there are no aliases), so the name here is the same
603 // string the add side keyed on.
604 ✗ bool sharedInterrupt = false;
605 ✗ for (auto other : portPins) {
606 ✗ if (other && other != pi && other->eFuseInterruptPin == pi->eFuseInterruptPin) {
607 ✗ sharedInterrupt = true;
608 ✗ break;
609 }
610 }
611 ✗ if (!sharedInterrupt) {
612 ✗ GPIOManager::INSTANCE.RemoveGPIOCallback(pi->eFuseInterruptPin);
613 ✗ fusePins.erase(pi->eFuseInterruptPin->name);
614 ✗ pi->eFuseInterruptPin->releasePin();
615 }
616 ✗ if (pi->eFusePin) {
617 ✗ pi->eFusePin->releasePin();
618 }
619
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 116 times.
119 } else if (pi->eFusePin) {
620
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 GPIOManager::INSTANCE.RemoveGPIOCallback(pi->eFusePin);
621
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 pi->eFusePin->releasePin();
622 }
623 // reset() drops the pin pointers, so a retry still queued against this
624 // port would take processRetries() through a null eFusePin/enablePin.
625 // The timer stops itself once the list empties.
626 119 eFuseRetries.remove(pi);
627 // reset() also drops each receiver's stored warning text, which is
628 // the only handle RemoveWarning() can match on -- take the eFuse and
629 // pixel-count banners down first or they outlive the port.
630
1/2
✓ Branch 0 taken 119 times.
✗ Branch 1 not taken.
119 clearReceiverWarnings(pi);
631
1/2
✓ Branch 0 taken 119 times.
✗ Branch 1 not taken.
119 pi->reset();
632 }
633 506 }
634
635 508 void OutputMonitor::AddPortConfiguration(int port, const Json::Value& pinConfig, bool enabled) {
636
1/2
✓ Branch 0 taken 508 times.
✗ Branch 1 not taken.
508 std::unique_lock<std::shared_mutex> lock(gpioLock);
637
1/2
✓ Branch 0 taken 508 times.
✗ Branch 1 not taken.
508 std::string name = "Port " + std::to_string(port + 1);
638
2/2
✓ Branch 0 taken 308 times.
✓ Branch 1 taken 200 times.
508 if (port >= portPins.size()) {
639
1/2
✓ Branch 0 taken 308 times.
✗ Branch 1 not taken.
308 portPins.resize(port + 1);
640 }
641
6/6
✓ Branch 0 taken 97 times.
✓ Branch 1 taken 411 times.
✓ Branch 2 taken 72 times.
✓ Branch 3 taken 25 times.
✓ Branch 4 taken 72 times.
✓ Branch 5 taken 436 times.
508 if (portPins[port] && portPins[port]->name == name) {
642
1/2
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
72 portPins[port]->setConfig(name, pinConfig);
643 72 return;
644 }
645 436 PortPinInfo* pi = portPins[port];
646
2/2
✓ Branch 0 taken 411 times.
✓ Branch 1 taken 25 times.
436 if (!pi) {
647
3/8
✓ Branch 0 taken 411 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 411 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 411 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
411 pi = new PortPinInfo();
648 }
649
1/2
✓ Branch 0 taken 436 times.
✗ Branch 1 not taken.
436 pi->setConfig(name, pinConfig);
650 436 bool hasInfo = false;
651 436 pi->receivers[0].enabled = true;
652
3/4
✓ Branch 0 taken 436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✓ Branch 3 taken 417 times.
436 if (pinConfig.isMember("enablePin")) {
653
3/6
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 19 times.
✗ Branch 5 not taken.
19 std::string ep = pinConfig.get("enablePin", "").asString();
654
2/4
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
19 if (ep != "") {
655 19 pi->highToEnable = (ep[0] != '!');
656
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 pi->enablePin = addOutputPinInternal(name, ep, enabled);
657
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 if (!pi->enablePin) {
658 ✗ enabled = false;
659 }
660
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 13 times.
19 if (!enabled) {
661 6 pi->receivers[0].enabled = false;
662 }
663 19 hasInfo = true;
664 }
665 19 }
666
2/4
✓ Branch 0 taken 436 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 436 times.
436 if (pinConfig.isMember("group")) {
667 ✗ int g = pinConfig["group"].asInt();
668 ✗ pi->group = g;
669 ✗ if (g >= numGroups) {
670 ✗ numGroups = g + 1;
671 }
672 }
673
3/4
✓ Branch 0 taken 436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 433 times.
436 if (pinConfig.isMember("eFusePin")) {
674
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 if (pinConfig.isMember("eFuseInterruptPin")) {
675 ✗ std::string eFuseInterruptPin = pinConfig.get("eFuseInterruptPin", "").asString();
676 // A leading '!' is stripped but carries no meaning here, and there
677 // is deliberately no inverted-interrupt flag to go with it: the
678 // callback below ignores the edge value it is handed and re-reads
679 // every affected port's eFusePin, so which edge announced the trip
680 // cannot change the outcome. (The pin is registered for BOTH edges
681 // anyway -- some i2c expanders mislabel a single-edge request -- so
682 // a polarity flag would have nothing to act on.)
683 ✗ if (eFuseInterruptPin[0] == '!') {
684 ✗ eFuseInterruptPin = eFuseInterruptPin.substr(1);
685 }
686 ✗ std::string postFix = "";
687 ✗ if (eFuseInterruptPin[0] == '-') {
688 ✗ postFix = "_pd";
689 ✗ eFuseInterruptPin = eFuseInterruptPin.substr(1);
690 ✗ } else if (eFuseInterruptPin[0] == '+') {
691 ✗ postFix = "_pu";
692 ✗ eFuseInterruptPin = eFuseInterruptPin.substr(1);
693 }
694 ✗ pi->eFuseInterruptPin = PinCapabilities::getPinByName(eFuseInterruptPin).ptr();
695 ✗ if (!pi->eFuseInterruptPin) {
696 ✗ LogWarn(VB_CHANNELOUT, "Could not find pin " + eFuseInterruptPin + " to handle fuse interrupts for output " + name + "\n");
697 ✗ WarningHolder::AddWarning(51, "Could not find pin " + eFuseInterruptPin + " to handle fuse interrupts for output " + name);
698 } else {
699 ✗ hasInfo = true;
700 ✗ if (fusePins[eFuseInterruptPin] == nullptr) {
701 ✗ pi->eFuseInterruptPin->configPin("gpio" + postFix, false, "eFuseInterrupt");
702 ✗ fusePins[eFuseInterruptPin] = pi->eFuseInterruptPin;
703 // Capture the pin, not the registering port: several ports
704 // share one interrupt pin, and the port that happened to
705 // register it can be reset by a config reload while the
706 // callback (and the other ports) live on.
707 ✗ const PinCapabilities* intPin = pi->eFuseInterruptPin;
708 ✗ GPIOManager::INSTANCE.AddGPIOCallback(intPin, [this, intPin](int v) {
709 ✗ std::unique_lock<std::shared_mutex> lock(gpioLock);
710 ✗ for (auto a : portPins) {
711 ✗ if (a && a->eFuseInterruptPin == intPin && a->eFusePin) {
712 ✗ int v = a->eFusePin->getValue();
713 ✗ if (v != a->eFuseOKValue) {
714 ✗ if (a->enablePin) {
715 // make sure the port is turned off
716 ✗ a->enablePin->setValue(a->highToEnable ? 0 : 1);
717 }
718 ✗ if (a->receivers[0].isOn && !a->receivers[0].hasTriggered) {
719 // Output SHOULD be on, but the fuse triggered. That's a warning.
720 ✗ a->receivers[0].hasTriggered = true;
721 ✗ if (checkEFuseRetry(a)) {
722 ✗ addEFuseWarning(a, 0);
723 }
724 }
725 }
726 }
727 }
728 ✗ lock.unlock();
729 ✗ triggerEFusePresets();
730 ✗ return true;
731 ✗ });
732 }
733 }
734 ✗ }
735
3/6
✓ 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.
3 std::string eFusePin = pinConfig.get("eFusePin", "").asString();
736 3 bool eFuseHigh = false;
737
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (eFusePin[0] == '!') {
738 ✗ eFuseHigh = true;
739 ✗ eFusePin = eFusePin.substr(1);
740 }
741
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 std::string postFix = "";
742
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (eFusePin[0] == '-') {
743 ✗ postFix = "_pd";
744 ✗ eFusePin = eFusePin.substr(1);
745
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 } else if (eFusePin[0] == '+') {
746 ✗ postFix = "_pu";
747 ✗ eFusePin = eFusePin.substr(1);
748 }
749
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 pi->eFusePin = PinCapabilities::getPinByName(eFusePin).ptr();
750
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 pi->eFuseOKValue = eFuseHigh ? 1 : 0;
751
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (pi->eFusePin == nullptr) {
752 ✗ LogWarn(VB_CHANNELOUT, "Could not find pin " + eFusePin + " to handle fuse for output " + name + "\n");
753 ✗ WarningHolder::AddWarning(51, "Could not find pin " + eFusePin + " to handle fuse for output " + name);
754 } else {
755
3/6
✓ 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.
3 pi->eFusePin->configPin("gpio" + postFix, false, "eFuse" + std::to_string(port + 1));
756
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (pi->eFuseInterruptPin == nullptr) {
757
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 GPIOManager::INSTANCE.AddGPIOCallback(pi->eFusePin, [this, pi](int v) {
758
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 std::unique_lock<std::shared_mutex> lock(gpioLock);
759 // The callback is unregistered before a reload resets pi,
760 // but a dispatch already in flight can still land here.
761
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!pi->eFusePin) {
762 ✗ return true;
763 }
764
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 v = pi->eFusePin->getValue();
765
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (v != pi->eFuseOKValue) {
766
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (pi->enablePin) {
767 // make sure the port is turned off
768
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 pi->enablePin->setValue(pi->highToEnable ? 0 : 1);
769 }
770
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (pi->receivers[0].isOn) {
771 2 pi->receivers[0].hasTriggered = true;
772
3/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 if (checkEFuseRetry(pi)) {
773
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 addEFuseWarning(pi, 0);
774 }
775 }
776 }
777
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 lock.unlock();
778
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 triggerEFusePresets();
779 2 return true;
780 2 });
781 }
782 3 hasInfo = true;
783 }
784 3 }
785
3/4
✓ Branch 0 taken 436 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 429 times.
436 if (pinConfig.isMember("currentSensor")) {
786
3/6
✓ 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.
7 if (pinConfig["currentSensor"].isMember("path")) {
787 7 hasInfo = true;
788
4/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 not taken.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
7 pi->currentMonitor = new FileCurrentMonitor(pinConfig["currentSensor"]);
789 ✗ } else if (pinConfig["currentSensor"].isMember("sensor")) {
790 ✗ hasInfo = true;
791 ✗ pi->currentMonitor = new SensorCurrentMonitor(pinConfig["currentSensor"]);
792 }
793 }
794
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 413 times.
436 if (hasInfo) {
795 // AddOutputPin() above brought the enable pin up in whatever state the
796 // monitor is in, so the port bookkeeping has to agree with it. It is
797 // what decides whether an eFuse trip on this port is reported as a
798 // trip or silently swallowed, and what the status API reports.
799
4/4
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 3 times.
23 pi->receivers[0].isOn = outputsEnabled && pi->receivers[0].enabled;
800 23 portPins[port] = pi;
801
3/4
✓ Branch 0 taken 413 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✓ Branch 3 taken 389 times.
413 } else if (pinConfig.isMember("falconV5Listener")) {
802 // Rows are 1-based (the page addresses them with nth-child), so a cape
803 // with no monitored local ports still starts its first block on row 1.
804 24 int mr = 0;
805 24 bool hasSR = false;
806
2/2
✓ Branch 0 taken 109 times.
✓ Branch 1 taken 24 times.
133 for (auto a : portPins) {
807
2/2
✓ Branch 0 taken 100 times.
✓ Branch 1 taken 9 times.
109 if (a) {
808 100 mr = std::max(mr, a->row);
809 100 hasSR |= a->isSmartReceiver;
810 }
811 }
812
813 // A new block goes beside the previous block, so the column comes from
814 // the last receiver port before this one. Not portPins.back(): the
815 // resize above just made that this port's own, still unplaced, slot.
816 24 int col = 1;
817
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 19 times.
24 for (int p = port - 1; p >= 0; --p) {
818
3/6
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
5 if (portPins[p] && portPins[p]->isSmartReceiver) {
819 5 col = portPins[p]->col;
820 5 break;
821 }
822 }
823
824 24 pi->isSmartReceiver = true;
825 24 pi->group = -1;
826 24 pi->receivers[0].enabled = false;
827 24 portPins[port] = pi;
828
829
4/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 3 times.
24 if (!hasSR || col == 8) {
830 21 col = 1;
831
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 19 times.
21 mr += hasSR ? 8 : 1;
832 } else {
833 3 col = 5;
834 }
835
836
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 if (pi->row == -1) {
837 24 pi->col = col;
838 24 pi->row = mr;
839 }
840 24 int mc = pi->col;
841 24 mr = pi->row;
842
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 15 times.
24 if ((port + 4) > portPins.size()) {
843
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 portPins.resize(port + 4);
844 }
845
2/2
✓ Branch 0 taken 72 times.
✓ Branch 1 taken 24 times.
96 for (int x = 1; x < 4; x++) {
846
1/2
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
72 std::string name = "Port " + std::to_string(port + 1 + x);
847 // after a reload the slot still holds the entry the last
848 // configuration placed, reset by RemovePortConfiguration()
849 72 pi = portPins[port + x];
850
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 45 times.
72 if (!pi) {
851
3/8
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 27 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
27 pi = new PortPinInfo();
852 }
853
1/2
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
72 pi->setConfig(name, pinConfig);
854
1/2
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
72 if (pi->row == -1) {
855 72 pi->row = mr;
856 72 pi->col = mc + x;
857 }
858 72 pi->isSmartReceiver = true;
859 72 pi->group = -1;
860 72 portPins[port + x] = pi;
861 72 }
862 } else {
863
1/2
✓ Branch 0 taken 389 times.
✗ Branch 1 not taken.
389 clearReceiverWarnings(pi);
864
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 389 times.
389 if (portPins[port] == pi) {
865 ✗ pi->reset();
866 } else {
867
1/2
✓ Branch 0 taken 389 times.
✗ Branch 1 not taken.
389 delete pi;
868 }
869 }
870
4/4
✓ Branch 0 taken 436 times.
✓ Branch 1 taken 72 times.
✓ Branch 2 taken 436 times.
✓ Branch 3 taken 72 times.
580 }
871
872 ✗ const PinCapabilities* OutputMonitor::AddOutputPin(const std::string& name, const std::string& pinName, bool addToList) {
873 ✗ std::unique_lock<std::shared_mutex> lock(gpioLock);
874 ✗ return addOutputPinInternal(name, pinName, addToList);
875 ✗ }
876 // gpioLock must be held exclusively.
877 19 const PinCapabilities* OutputMonitor::addOutputPinInternal(const std::string& name, const std::string& pinName, bool addToList) {
878
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 std::string pin = pinName;
879 19 bool highToEnable = true;
880
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 if (pin[0] == '!') {
881 ✗ pin = pin.substr(1);
882 ✗ highToEnable = false;
883 }
884
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 auto& op = highToEnable ? pullHighOutputPins : pullLowOutputPins;
885
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 9 times.
19 for (auto& pc : op) {
886
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 if (pc->name == pin) {
887 10 return pc;
888 }
889 }
890
891
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
9 const PinCapabilities* pc = PinCapabilities::getPinByName(pin).ptr();
892
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 if (!pc) {
893 ✗ LogWarn(VB_CHANNELOUT, "Could not find pin " + pin + " to enable output " + name + "\n");
894 ✗ WarningHolder::AddWarning(51, "Could not find pin " + pin + " to enable output " + name);
895 ✗ return nullptr;
896 }
897
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 2 times.
9 if (addToList) {
898
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 op.push_back(pc);
899 }
900
3/6
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
21 pc->configPin("gpio", true, "Enable-" + name);
901 // Only pins that go on the enable lists follow the monitor's state; a port
902 // with nothing configured on it is left off even while output is running.
903
4/4
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 4 times.
9 bool on = addToList && outputsEnabled;
904
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
11 pc->setValue(on ? highToEnable : !highToEnable);
905 9 return pc;
906 21 }
907
908 1956 void OutputMonitor::AutoEnableOutputs() {
909 1956 int i = getSettingInt("AutoEnableOutputs", 1);
910
1/2
✓ Branch 0 taken 1956 times.
✗ Branch 1 not taken.
1956 if (i) {
911 1956 EnableOutputs();
912 }
913 1956 }
914 1946 void OutputMonitor::AutoDisableOutputs() {
915 1946 int i = getSettingInt("AutoEnableOutputs", 1);
916
1/2
✓ Branch 0 taken 1946 times.
✗ Branch 1 not taken.
1946 if (i) {
917 1946 DisableOutputs();
918 }
919 1946 }
920
921 6 void OutputMonitor::lockToGroup(int i) {
922 6 curGroup = i;
923 6 Sensors::INSTANCE.lockToGroup(i);
924 6 }
925 3 bool OutputMonitor::isPortInGroup(int group, int port) {
926
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 std::shared_lock<std::shared_mutex> lock(gpioLock);
927
3/6
✓ 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.
6 return ((port < portPins.size()) && portPins[port] && (group == portPins[port]->group));
928 3 }
929
930 1188 std::vector<float> OutputMonitor::GetPortCurrentValues() {
931
1/2
✓ Branch 0 taken 1188 times.
✗ Branch 1 not taken.
1188 Sensors::INSTANCE.updateSensorSources(true);
932
1/2
✓ Branch 0 taken 1188 times.
✗ Branch 1 not taken.
1188 std::shared_lock<std::shared_mutex> lock(gpioLock);
933 1188 std::vector<float> ret;
934
1/2
✓ Branch 0 taken 1188 times.
✗ Branch 1 not taken.
1188 ret.reserve(portPins.size());
935
2/2
✓ Branch 0 taken 1188 times.
✓ Branch 1 taken 1188 times.
2376 for (auto a : portPins) {
936
4/8
✓ Branch 0 taken 1188 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1188 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1188 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1188 times.
✗ Branch 7 not taken.
1188 if (a && a->currentMonitor && ((curGroup == -1) || (curGroup == a->group))) {
937
2/4
✓ Branch 0 taken 1188 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1188 times.
✗ Branch 3 not taken.
1188 ret.push_back(a->currentMonitor->getValue());
938 ✗ } else if (a) {
939 ✗ ret.push_back(a->receivers[0].current);
940 }
941 }
942 1188 return ret;
943 1188 }
944 4 void OutputMonitor::SetPixelCount(int port, int pc, int cc) {
945
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 std::unique_lock<std::shared_mutex> lock(gpioLock);
946
5/10
✓ 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.
4 if (port < portPins.size() && portPins[port] && ((curGroup == -1) || (curGroup == portPins[port]->group))) {
947 4 portPins[port]->receivers[0].pixelCount = pc;
948 4 portPins[port]->receivers[0].configuredCount = cc;
949 }
950 4 }
951 ✗ int OutputMonitor::GetPixelCount(int port) {
952 ✗ std::shared_lock<std::shared_mutex> lock(gpioLock);
953 ✗ if (port < portPins.size() && portPins[port]) {
954 ✗ return portPins[port]->receivers[0].pixelCount;
955 }
956 ✗ return 0;
957 ✗ }
958
959 193 void OutputMonitor::GetCurrentPortStatusJson(Json::Value& result) {
960 // The sensor refresh does its own locking; keep it outside ours.
961
1/2
✓ Branch 0 taken 193 times.
✗ Branch 1 not taken.
193 Sensors::INSTANCE.updateSensorSources();
962
1/2
✓ Branch 0 taken 193 times.
✗ Branch 1 not taken.
193 std::shared_lock<std::shared_mutex> lock(gpioLock);
963
2/2
✓ Branch 0 taken 240 times.
✓ Branch 1 taken 193 times.
433 for (auto a : portPins) {
964
1/2
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
240 if (a) {
965
1/2
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
240 a->appendTo(result);
966 }
967 }
968 193 }
969
970 1 void OutputMonitor::addEFuseWarning(PortPinInfo* pi, int rec) {
971
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (pi && rec < 6) {
972
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 std::string name = pi->name;
973
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (pi->isSmartReceiver) {
974 ✗ name += std::string(1, 'A' + rec);
975 }
976
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 std::string warn = "eFUSE Triggered for " + name;
977
978
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!pi->receivers[rec].warning.starts_with(warn)) {
979
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (sequence->IsSequenceRunning()) {
980 ✗ std::string seq = sequence->GetSeqFilenameCopy();
981 ✗ int sTime = sequence->m_seqMSElapsed / 1000;
982 ✗ warn += " (" + seq + "/" + std::to_string(sTime / 60) + ":" + std::to_string(sTime % 60) + ")";
983 ✗ }
984
985
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 LogWarn(VB_CHANNELOUT, warn + "\n");
986 // Output SHOULD be on, but the fuse triggered. That's a warning.
987
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 WarningHolder::AddWarning(16, warn);
988
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 pi->receivers[rec].warning = warn;
989
990 // Presets run commands synchronously, and an EFUSE_TRIGGERED
991 // preset that flips outputs ("Outputs Off", "Set Port Status")
992 // re-enters this class -- firing it here, with gpioLock held,
993 // self-deadlocks. Queue it; every caller drains the queue via
994 // triggerEFusePresets() once it has released the lock.
995
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 pendingEFusePresets.push_back(name);
996 }
997 1 }
998 1 }
999 6 void OutputMonitor::triggerEFusePresets() {
1000 // Must be called WITHOUT gpioLock held.
1001 6 std::vector<std::string> ports;
1002 {
1003
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 std::unique_lock<std::shared_mutex> lock(gpioLock);
1004 6 ports.swap(pendingEFusePresets);
1005 6 }
1006
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 6 times.
7 for (auto& name : ports) {
1007
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
2 if (CommandManager::INSTANCE.HasPreset("EFUSE_TRIGGERED")) {
1008 ✗ std::map<std::string, std::string> keywords;
1009 ✗ keywords["PORT"] = name;
1010 ✗ CommandManager::INSTANCE.TriggerPreset("EFUSE_TRIGGERED", keywords);
1011 ✗ }
1012 }
1013 6 }
1014 3346 void OutputMonitor::clearEFuseWarning(PortPinInfo* port, int rec) {
1015
2/4
✓ Branch 0 taken 3346 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3346 times.
✗ Branch 3 not taken.
3346 if (port && rec < 6) {
1016
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3345 times.
3346 if (!port->receivers[rec].warning.empty()) {
1017
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 WarningHolder::RemoveWarning(16, port->receivers[rec].warning);
1018 1 port->receivers[rec].warning.clear();
1019 }
1020 3346 port->receivers[rec].retryCount = 0;
1021 }
1022 3346 }
1023 // Every receiver's warning (eFuse trip or pixel-count mismatch, both id 16)
1024 // is retracted by its stored text, so a PortPinInfo must not be reset or
1025 // deleted while one is still up. gpioLock must be held exclusively.
1026 557 void OutputMonitor::clearReceiverWarnings(PortPinInfo* port) {
1027
2/2
✓ Branch 0 taken 3342 times.
✓ Branch 1 taken 557 times.
3899 for (int r = 0; r < 6; r++) {
1028 3342 clearEFuseWarning(port, r);
1029 }
1030 557 }
1031 2 bool OutputMonitor::checkEFuseRetry(PortPinInfo* port) {
1032
3/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 if (port && port->receivers[0].retryCount < eFuseRetryCount) {
1033 1 LogDebug(VB_CHANNELOUT, "eFuse triggered for %s. Attempting reset retry #%d\n", port->name.c_str(), port->receivers[0].retryCount + 1);
1034
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (std::find(eFuseRetries.begin(), eFuseRetries.end(), port) == eFuseRetries.end()) {
1035 1 eFuseRetries.push_back(port);
1036 }
1037
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!retryTimerRunning) {
1038
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
3 Timers::INSTANCE.addPeriodicTimer("eFuse Retry", 100, [this]() {
1039 4 processRetries();
1040 4 });
1041 1 retryTimerRunning = true;
1042 }
1043 1 return false;
1044 }
1045 1 return true;
1046 }
1047 4 void OutputMonitor::processRetries() {
1048
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 std::unique_lock<std::shared_mutex> lock(gpioLock);
1049
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 long long ft = GetMonotonicTimeMS(); // retry timing: must not step with the wall clock
1050 4 std::list<PortPinInfo*> toRemove;
1051
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
8 for (auto p : eFuseRetries) {
1052
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (!p->eFusePin || !p->enablePin) {
1053 // A port with an eFuse but no enable pin has no way to retry, and
1054 // a port reset by a config reload has neither.
1055 ✗ toRemove.push_back(p);
1056
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 } else if (!p->receivers[0].isOn) {
1057 // Turned off (Outputs Off, or the port switched off) while it was
1058 // retrying. Pulsing the enable pin now would power it back up
1059 // behind the user's back, and leave it on with isOn false, where
1060 // a later trip is ignored.
1061
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 toRemove.push_back(p);
1062
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
3 } else if (p->eFusePin->getValue() == p->eFuseOKValue) {
1063 ✗ p->receivers[0].okCount++;
1064 // must be good for at least two seconds
1065 ✗ long long resetTime = p->receivers[0].nextRetryTime;
1066 ✗ resetTime += 2000;
1067 ✗ if (ft > resetTime) {
1068 ✗ p->receivers[0].retryCount = 0;
1069 ✗ toRemove.push_back(p);
1070 }
1071
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 } else if (p->receivers[0].retryCount > eFuseRetryCount) {
1072 // still not good after all the retries, mark it bad and send the warning
1073 ✗ toRemove.push_back(p);
1074 ✗ addEFuseWarning(p);
1075
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
3 } else if (p->receivers[0].retryCount == 0 || ft > p->receivers[0].nextRetryTime) {
1076 1 p->receivers[0].enabled = true;
1077 1 p->receivers[0].hasTriggered = false;
1078
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (p->receivers[0].retryCount == 0) {
1079 1 p->receivers[0].firstTriggerTime = ft;
1080 1 p->receivers[0].nextRetryTime = ft;
1081 }
1082 1 p->receivers[0].nextRetryTime += eFuseRetryInterval;
1083 1 p->receivers[0].retryCount++;
1084
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 p->enablePin->setValue(p->highToEnable ? 0 : 1);
1085
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 p->enablePin->setValue(p->highToEnable ? 1 : 0);
1086 }
1087 }
1088
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 for (auto p : toRemove) {
1089 1 eFuseRetries.remove(p);
1090 }
1091
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 if (eFuseRetries.empty()) {
1092
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 Timers::INSTANCE.stopPeriodicTimer("eFuse Retry");
1093 1 retryTimerRunning = false;
1094 }
1095
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 lock.unlock();
1096
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 triggerEFusePresets();
1097 4 }
1098 ✗ void OutputMonitor::setSmartReceiverInfo(int port, int index, bool enabled, bool tripped, int current, int pixelCount) {
1099 // printf(" %d %c: Current: %d PC: %d EN: %d TR: %d\n", port + 1, (char)('A' + index), current, pixelCount, enabled, tripped);
1100 // both come decoded off the wire from a receiver's reply; receivers[] has 6
1101 ✗ if (port < 0 || index < 0 || index >= 6) {
1102 ✗ return;
1103 }
1104 ✗ std::unique_lock<std::shared_mutex> lock(gpioLock);
1105 ✗ if (port < portPins.size() && portPins[port]) {
1106 ✗ portPins[port]->receivers[index].enabled = true;
1107 ✗ portPins[port]->receivers[index].isOn = enabled;
1108 ✗ portPins[port]->receivers[index].hasTriggered = tripped;
1109 ✗ portPins[port]->receivers[index].pixelCount = pixelCount;
1110 ✗ portPins[port]->receivers[index].current = current;
1111
1112 ✗ if (tripped) {
1113 ✗ addEFuseWarning(portPins[port], index);
1114 } else {
1115 ✗ clearEFuseWarning(portPins[port], index);
1116 }
1117 }
1118 ✗ lock.unlock();
1119 ✗ triggerEFusePresets();
1120 ✗ }
1121
1122 // --------------------------------------------------------------------------
1123 // OpenAPI docs for the /fppd/ports/* endpoints handled below.
1124 // --------------------------------------------------------------------------
1125
1126 /**
1127 * Get the current status of every output port (current draw, smart-receiver
1128 * data, sensor readings, etc.). Each port only reports what its hardware can
1129 * measure: "ma" is present only for ports with a current sensor, "status" only
1130 * for ports with an eFuse, and "enabled" only for ports with an enable pin.
1131 *
1132 * @route GET /api/fppd/ports
1133 * @response 200 Array of port status objects.
1134 */
1135
1136 /**
1137 * List the names of all configured output ports.
1138 *
1139 * @route GET /api/fppd/ports/list
1140 * @response 200 Array of port names (first entry is "--ALL--").
1141 * ```json
1142 * ["--ALL--", "Port 1", "Port 2"]
1143 * ```
1144 */
1145
1146 /**
1147 * Start a pixel-count test on all ports, then return current port status.
1148 *
1149 * @route GET /api/fppd/ports/pixelCount
1150 * @response 200 Array of port status objects.
1151 */
1152
1153 /**
1154 * Stop a running pixel-count test, then return current port status.
1155 * Any other test is left running.
1156 *
1157 * @route GET /api/fppd/ports/stop
1158 * @response 200 Array of port status objects.
1159 */
1160 213 HttpResponsePtr OutputMonitor::render_GET(const HttpRequestPtr& req) {
1161
2/4
✓ Branch 0 taken 213 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 213 times.
✗ Branch 3 not taken.
213 auto parts = getPathPieces(req->path());
1162 213 int plen = parts.size();
1163
4/8
✓ Branch 0 taken 213 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 213 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 213 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 213 times.
✗ Branch 7 not taken.
213 if (plen > 1 && parts[1] == "ports") {
1164
7/8
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 193 times.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 15 times.
✓ Branch 6 taken 5 times.
✓ Branch 7 taken 208 times.
213 if (plen > 2 && parts[2] == "list") {
1165
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 Json::Value result;
1166
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 result.append("--ALL--");
1167
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 std::shared_lock<std::shared_mutex> lock(gpioLock);
1168
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 for (auto a : portPins) {
1169 ✗ if (a && a->isSmartReceiver) {
1170 ✗ for (int x = 0; x < 6; x++) {
1171 ✗ if (a->receivers[x].enabled) {
1172 ✗ result.append(a->name + std::string(1, 'A' + x));
1173 }
1174 }
1175 ✗ } else if (a) {
1176 ✗ result.append(a->name);
1177 }
1178 }
1179
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 lock.unlock();
1180
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
10 std::string resultStr = SaveJsonToString(result);
1181
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 return makeStringResponse(resultStr, 200, "application/json");
1182 5 }
1183 {
1184
1/2
✓ Branch 0 taken 208 times.
✗ Branch 1 not taken.
208 std::shared_lock<std::shared_mutex> lock(gpioLock);
1185
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 193 times.
208 if (portPins.empty()) {
1186
3/6
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
60 return makeStringResponse("[]", 200, "application/json");
1187 }
1188
2/2
✓ Branch 0 taken 193 times.
✓ Branch 1 taken 15 times.
208 }
1189
7/8
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 188 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 190 times.
193 if (plen > 2 && parts[2] == "pixelCount") {
1190 3 std::vector<std::string> args;
1191
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
6 args.push_back("50");
1192
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
6 args.push_back("Output Specific");
1193
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
6 args.push_back("--ALL--");
1194
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 args.push_back("999");
1195
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 LogDebug(VB_COMMAND, "GET /api/fppd/ports/pixelCount from %s starting Test Mode\n", getEffectiveClientIP(req).c_str());
1196
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 CommandManager::INSTANCE.run("Test Start", args);
1197 3 }
1198
7/8
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 188 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 191 times.
193 if (plen > 2 && parts[2] == "stop") {
1199 // Only the pixel count the "pixelCount" route starts. The Port
1200 // Status page calls this as it is left, and any other test running
1201 // then was started elsewhere (Channel Outputs, the Display Testing
1202 // page) and is not the page's to end.
1203
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 Json::Value test = ChannelTester::INSTANCE.GetStatusJson();
1204
17/44
✓ 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.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 2 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 2 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 2 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 2 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 2 times.
✗ Branch 21 not taken.
✓ Branch 22 taken 2 times.
✗ Branch 23 not taken.
✓ Branch 24 taken 2 times.
✗ Branch 25 not taken.
✓ Branch 26 taken 2 times.
✗ Branch 27 not taken.
✓ Branch 28 taken 2 times.
✗ Branch 29 not taken.
✓ Branch 30 taken 2 times.
✗ Branch 31 not taken.
✓ Branch 32 taken 2 times.
✗ Branch 33 not taken.
✗ Branch 34 not taken.
✗ Branch 35 not taken.
✗ Branch 36 not taken.
✗ Branch 37 not taken.
✗ Branch 38 not taken.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
✗ Branch 41 not taken.
✗ Branch 42 not taken.
✗ Branch 43 not taken.
2 if (test.isObject() && test.get("mode", "").asString() == "Outputs" && test.get("type", 0).asInt() == 999) {
1205 2 std::vector<std::string> args;
1206
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 LogDebug(VB_COMMAND, "GET /api/fppd/ports/stop from %s stopping Test Mode\n", getEffectiveClientIP(req).c_str());
1207
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 CommandManager::INSTANCE.run("Test Stop", args);
1208 2 }
1209 2 }
1210
1/2
✓ Branch 0 taken 193 times.
✗ Branch 1 not taken.
193 Sensors::INSTANCE.updateSensorSources();
1211
1/2
✓ Branch 0 taken 193 times.
✗ Branch 1 not taken.
193 Json::Value result = Json::arrayValue;
1212
1/2
✓ Branch 0 taken 193 times.
✗ Branch 1 not taken.
193 GetCurrentPortStatusJson(result);
1213
2/4
✓ Branch 0 taken 193 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 193 times.
✗ Branch 3 not taken.
386 std::string resultStr = SaveJsonToString(result);
1214
2/4
✓ Branch 0 taken 193 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 193 times.
✗ Branch 3 not taken.
193 return makeStringResponse(resultStr, 200, "application/json");
1215 193 }
1216 ✗ return makeStringResponse("Not Found", 404, "text/plain");
1217 213 }
1218