| 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 | |||
| 17 | #include <cmath> | ||
| 18 | #include <fcntl.h> | ||
| 19 | #include <unistd.h> | ||
| 20 | |||
| 21 | #include "../Warnings.h" | ||
| 22 | #include "../common.h" | ||
| 23 | |||
| 24 | #include "IIOSensorSource.h" | ||
| 25 | |||
| 26 | 6 | IIOSensorSource::IIOSensorSource(Json::Value& config) : | |
| 27 | 6 | SensorSource(config) { | |
| 28 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | if (config.isMember("devId")) { |
| 29 |
3/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1 times.
|
6 | iioDevNumber = config["devId"].asInt(); |
| 30 | } else { | ||
| 31 | ✗ | int i2cBus = config["i2cBus"].asInt(); | |
| 32 | ✗ | int i2cDev = config["i2cDev"].asInt(); | |
| 33 | uint8_t buf[8]; | ||
| 34 | ✗ | snprintf((char*)buf, 8, "%04x", i2cDev); | |
| 35 | ✗ | std::string d = (char*)buf; | |
| 36 | ✗ | std::string basePath = "/sys/class/i2c-dev/i2c-" + std::to_string(i2cBus) + "/device/" + std::to_string(i2cBus) + "-" + d + "/iio:device"; | |
| 37 | ✗ | for (int x = 0; x < 16; x++) { | |
| 38 | ✗ | if (FileExists(basePath + std::to_string(x))) { | |
| 39 | ✗ | iioDevNumber = x; | |
| 40 | ✗ | break; | |
| 41 | } | ||
| 42 | } | ||
| 43 | ✗ | } | |
| 44 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | if (config.isMember("useBuffers")) { |
| 45 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | usingBuffers = config["useBuffers"].asBool(); |
| 46 | } else { | ||
| 47 | ✗ | usingBuffers = FileExists("/sys/bus/iio/devices/iio:device" + std::to_string(iioDevNumber) + "/buffer/enable") && FileExists("/dev/iio:device" + std::to_string(iioDevNumber)); | |
| 48 | } | ||
| 49 | |||
| 50 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | if (config.isMember("vScale")) { |
| 51 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | vScale = config["vScale"].asFloat(); |
| 52 | } else { | ||
| 53 | #ifdef PLATFORM_BB64 | ||
| 54 | ✗ | std::string vs = GetFileContents("/sys/bus/iio/devices/iio:device" + std::to_string(iioDevNumber) + "/in_voltage_scale"); | |
| 55 | ✗ | vScale = std::atof(vs.c_str()); | |
| 56 | // original reference is 1.8V which for 12bit would be 0.439453125. | ||
| 57 | // If the cape doesn't specify a reference scale, we'll adjust and assume | ||
| 58 | // the params are based on 1.8 ref, but we're reading to 3.3V | ||
| 59 | ✗ | vScale = vScale / 0.439453125; | |
| 60 | #else | ||
| 61 | ✗ | vScale = 1.0f; | |
| 62 | #endif | ||
| 63 | ✗ | } | |
| 64 |
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 | readIterations = config.get("readIterations", 3).asInt(); |
| 65 | // update() averages over readIterations reads: 0 divided by zero | ||
| 66 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
|
5 | if (readIterations < 1) { |
| 67 | 1 | readIterations = 1; | |
| 68 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | } else if (readIterations > 100) { |
| 69 | ✗ | readIterations = 100; | |
| 70 | } | ||
| 71 | |||
| 72 | // usingBuffers = false; | ||
| 73 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | std::string base = "/sys/bus/iio/devices/iio:device" + std::to_string(iioDevNumber) + "/in_voltage"; |
| 74 | 5 | int max = -1; | |
| 75 |
2/2✓ Branch 0 taken 80 times.
✓ Branch 1 taken 5 times.
|
85 | for (int x = 0; x < 16; x++) { |
| 76 |
5/8✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 80 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 80 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
✓ Branch 7 taken 68 times.
|
80 | if (FileExists(base + std::to_string(x) + "_raw")) { |
| 77 | 12 | max = x; | |
| 78 | } | ||
| 79 | } | ||
| 80 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if (max >= 0) { |
| 81 | 5 | ++max; | |
| 82 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | channelMapping.resize(max); |
| 83 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | values.resize(max); |
| 84 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 5 times.
|
17 | for (int x = 0; x < max; x++) { |
| 85 | 12 | channelMapping[x] = -1; | |
| 86 | 12 | values[x] = 0; | |
| 87 | } | ||
| 88 | } else { | ||
| 89 | ✗ | WarningHolder::AddWarning(53, "Could not enable IIOSensorSource"); | |
| 90 | } | ||
| 91 | 8 | } | |
| 92 | ✗ | IIOSensorSource::~IIOSensorSource() { | |
| 93 | ✗ | if (usingBuffers) { | |
| 94 | char buf[256]; | ||
| 95 | ✗ | snprintf(buf, 256, "/sys/bus/iio/devices/iio:device%d/buffer/enable", iioDevNumber); | |
| 96 | ✗ | int f = open(buf, O_WRONLY); | |
| 97 | ✗ | write(f, "0\n", 2); | |
| 98 | ✗ | close(f); | |
| 99 | |||
| 100 | ✗ | close(iioDevFile); | |
| 101 | } else { | ||
| 102 | ✗ | for (int x = 0; x < channelMapping.size(); x++) { | |
| 103 | ✗ | if (channelMapping[x] != -1) { | |
| 104 | ✗ | close(channelMapping[x]); | |
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
| 108 | ✗ | if (readBuffer) { | |
| 109 | ✗ | delete[] readBuffer; | |
| 110 | } | ||
| 111 | ✗ | } | |
| 112 | 5 | bool IIOSensorSource::isOK() const { | |
| 113 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | return FileExists("/dev/iio:device" + std::to_string(iioDevNumber)); |
| 114 | } | ||
| 115 | |||
| 116 | 6 | void IIOSensorSource::Init(std::map<int, std::function<bool(int)>>& callbacks) { | |
| 117 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (usingBuffers) { |
| 118 | ✗ | if (iioDevFile >= 0) { | |
| 119 | ✗ | return; | |
| 120 | } | ||
| 121 | char buf[256]; | ||
| 122 | ✗ | snprintf(buf, 256, "/sys/bus/iio/devices/iio:device%d/buffer/length", iioDevNumber); | |
| 123 | ✗ | int f = open(buf, O_WRONLY); | |
| 124 | ✗ | write(f, "8\n", 2); | |
| 125 | ✗ | close(f); | |
| 126 | |||
| 127 | ✗ | snprintf(buf, 256, "/sys/bus/iio/devices/iio:device%d/buffer/enable", iioDevNumber); | |
| 128 | ✗ | f = open(buf, O_WRONLY); | |
| 129 | ✗ | write(f, "1\n", 2); | |
| 130 | ✗ | close(f); | |
| 131 | |||
| 132 | ✗ | snprintf(buf, 256, "/dev/iio:device%d", iioDevNumber); | |
| 133 | ✗ | iioDevFile = open(buf, O_RDONLY | O_NONBLOCK); | |
| 134 | |||
| 135 | ✗ | int curIdx = 0; | |
| 136 | ✗ | for (int x = 0; x < channelMapping.size(); x++) { | |
| 137 | ✗ | snprintf(buf, 256, "/sys/bus/iio/devices/iio:device%d/scan_elements/in_voltage%d_en", iioDevNumber, x); | |
| 138 | ✗ | int f = open(buf, O_RDONLY); | |
| 139 | ✗ | read(f, buf, 240); | |
| 140 | ✗ | if (buf[0] == '1') { | |
| 141 | ✗ | channelMapping[curIdx] = x; | |
| 142 | ✗ | curIdx++; | |
| 143 | } | ||
| 144 | ✗ | close(f); | |
| 145 | } | ||
| 146 | ✗ | channelMapping.resize(curIdx); | |
| 147 | ✗ | callbacks[iioDevFile] = [this](int) { | |
| 148 | ✗ | update(false, true); | |
| 149 | ✗ | return false; | |
| 150 | ✗ | }; | |
| 151 | ✗ | readBufferSize = channelMapping.size() * 64 * 2; | |
| 152 | ✗ | readBuffer = new uint16_t[readBufferSize]; | |
| 153 | } | ||
| 154 | } | ||
| 155 | 314 | void IIOSensorSource::update(bool forceInstant) { | |
| 156 | 314 | update(forceInstant, false); | |
| 157 | 314 | } | |
| 158 | |||
| 159 | 314 | void IIOSensorSource::update(bool forceInstant, bool fromSelect) { | |
| 160 |
1/2✓ Branch 0 taken 314 times.
✗ Branch 1 not taken.
|
314 | std::unique_lock<std::mutex> lock(updateMutex); |
| 161 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 314 times.
|
314 | if (usingBuffers) { |
| 162 | ✗ | int i = read(iioDevFile, readBuffer, readBufferSize * sizeof(uint16_t)); | |
| 163 | ✗ | if (i > 1) { | |
| 164 | ✗ | int countRead = i / sizeof(uint16_t); | |
| 165 | ✗ | std::vector<uint32_t> sums(channelMapping.size()); | |
| 166 | ✗ | std::vector<uint16_t> mins(channelMapping.size()); | |
| 167 | ✗ | std::vector<uint16_t> maxes(channelMapping.size()); | |
| 168 | ✗ | int samples = 0; | |
| 169 | ✗ | for (int x = 0; x < channelMapping.size(); x++) { | |
| 170 | ✗ | maxes[x] = 0; | |
| 171 | ✗ | mins[x] = 0xFFFF; | |
| 172 | ✗ | sums[x] = 0; | |
| 173 | } | ||
| 174 | |||
| 175 | ✗ | for (int y = 0; y < countRead; y += channelMapping.size()) { | |
| 176 | ✗ | samples++; | |
| 177 | ✗ | for (int x = 0; x < channelMapping.size(); x++) { | |
| 178 | ✗ | uint16_t v = readBuffer[y + x]; | |
| 179 | ✗ | sums[x] += v; | |
| 180 | ✗ | maxes[x] = std::max(maxes[x], v); | |
| 181 | ✗ | mins[x] = std::min(mins[x], v); | |
| 182 | } | ||
| 183 | } | ||
| 184 | ✗ | if (samples > 3) { | |
| 185 | ✗ | for (int x = 0; x < channelMapping.size(); x++) { | |
| 186 | ✗ | sums[x] -= mins[x]; | |
| 187 | ✗ | sums[x] -= maxes[x]; | |
| 188 | ✗ | float f = sums[x]; | |
| 189 | ✗ | f /= (float)(samples - 2); | |
| 190 | ✗ | f *= vScale; | |
| 191 | |||
| 192 | ✗ | int idx = channelMapping[x]; | |
| 193 | ✗ | values[idx] = f; | |
| 194 | } | ||
| 195 | } else { | ||
| 196 | ✗ | for (int x = 0; x < channelMapping.size(); x++) { | |
| 197 | ✗ | float f = sums[x]; | |
| 198 | ✗ | f /= (float)(samples); | |
| 199 | ✗ | f *= vScale; | |
| 200 | ✗ | int idx = channelMapping[x]; | |
| 201 | ✗ | values[idx] = f; | |
| 202 | } | ||
| 203 | } | ||
| 204 | ✗ | } | |
| 205 | } else { | ||
| 206 | char buf[24]; | ||
| 207 |
2/2✓ Branch 0 taken 632 times.
✓ Branch 1 taken 314 times.
|
946 | for (int x = 0; x < channelMapping.size(); x++) { |
| 208 |
2/2✓ Branch 0 taken 622 times.
✓ Branch 1 taken 10 times.
|
632 | if (channelMapping[x] >= 0) { |
| 209 | 622 | float s = 0; | |
| 210 |
2/2✓ Branch 0 taken 1862 times.
✓ Branch 1 taken 622 times.
|
2484 | for (int m = 0; m < readIterations; m++) { |
| 211 | 1862 | lseek(channelMapping[x], 0, SEEK_SET); | |
| 212 |
1/2✓ Branch 0 taken 1862 times.
✗ Branch 1 not taken.
|
1862 | int r = read(channelMapping[x], buf, 23); |
| 213 | // a failed read (-1) wrote buf[-1] | ||
| 214 | 1862 | buf[r > 0 ? r : 0] = 0; | |
| 215 | 1862 | s += std::atoi(buf); | |
| 216 | } | ||
| 217 | 622 | s /= ((float)readIterations); | |
| 218 | 622 | s *= vScale; | |
| 219 | 622 | values[x] = std::round(s); | |
| 220 | } | ||
| 221 | } | ||
| 222 | } | ||
| 223 | 314 | } | |
| 224 | 17 | void IIOSensorSource::enable(int id) { | |
| 225 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
|
17 | if (id < 0) { |
| 226 | ✗ | return; | |
| 227 | } | ||
| 228 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
|
17 | if (usingBuffers) { |
| 229 | char buf[256]; | ||
| 230 | ✗ | snprintf(buf, 256, "/sys/bus/iio/devices/iio:device%d/scan_elements/in_voltage%d_en", iioDevNumber, id); | |
| 231 | ✗ | if (FileExists(buf)) { | |
| 232 | ✗ | int f = open(buf, O_WRONLY); | |
| 233 | ✗ | write(f, "1\n", 2); | |
| 234 | ✗ | close(f); | |
| 235 | } | ||
| 236 | } else { | ||
| 237 | // enable() is called once per consumer, not once per channel: every | ||
| 238 | // port's current monitor enables its channel again on each channel | ||
| 239 | // output reload, and a MuxSensorSource enables the same underlying | ||
| 240 | // channel for every port it multiplexes. Opening unconditionally | ||
| 241 | // overwrote the previous descriptor without closing it, leaking one | ||
| 242 | // fd per call until fppd hit EMFILE. | ||
| 243 |
6/8✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 7 times.
✓ Branch 6 taken 10 times.
✓ Branch 7 taken 7 times.
|
17 | if (id < 0 || id >= (int)channelMapping.size() || channelMapping[id] >= 0) { |
| 244 | 10 | return; | |
| 245 | } | ||
| 246 | char buf[256]; | ||
| 247 | 7 | snprintf(buf, 256, "/sys/bus/iio/devices/iio:device%d/in_voltage%d_raw", iioDevNumber, id); | |
| 248 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | channelMapping[id] = open(buf, O_RDONLY | O_CLOEXEC); |
| 249 | } | ||
| 250 | } | ||
| 251 | |||
| 252 | 552 | int32_t IIOSensorSource::getValue(int id) { | |
| 253 |
3/6✓ Branch 0 taken 552 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 552 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 552 times.
|
552 | if (id < 0 || (size_t)id >= values.size()) { |
| 254 | ✗ | return 0; | |
| 255 | } | ||
| 256 | 552 | return values[(size_t)id]; | |
| 257 | }; | ||
| 258 |