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