| 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 GPL v2 as described in the | ||
| 10 | * included LICENSE.GPL file. | ||
| 11 | */ | ||
| 12 | |||
| 13 | #include "fpp-pch.h" | ||
| 14 | |||
| 15 | #include "fpp-json.h" | ||
| 16 | |||
| 17 | #include <cinttypes> | ||
| 18 | #include <cmath> | ||
| 19 | #include <thread> | ||
| 20 | |||
| 21 | #include "PCA9685.h" | ||
| 22 | #include "Plugin.h" | ||
| 23 | #include "../Sequence.h" | ||
| 24 | #include "../common.h" | ||
| 25 | #include "../log.h" | ||
| 26 | |||
| 27 | #include "non-gpl/CapeUtils/CapeUtils.h" | ||
| 28 | |||
| 29 | #include "util/GPIOUtils.h" | ||
| 30 | #include "commands/Commands.h" | ||
| 31 | |||
| 32 | class PCA9685Plugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin { | ||
| 33 | public: | ||
| 34 | 5 | PCA9685Plugin() : | |
| 35 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
15 | FPPPlugins::Plugin("PCA9685") { |
| 36 | 5 | } | |
| 37 | 6 | virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override { | |
| 38 |
2/6✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
12 | return new PCA9685Output(startChannel, channelCount); |
| 39 | } | ||
| 40 | }; | ||
| 41 | |||
| 42 | extern "C" { | ||
| 43 | 5 | FPPPlugins::Plugin* createPlugin() { | |
| 44 |
2/6✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
10 | return new PCA9685Plugin(); |
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | /* | ||
| 49 | * | ||
| 50 | */ | ||
| 51 | // MODE1 when its real value can't be read: ALLCALL and auto-increment, as the | ||
| 52 | // driver programs it. EXTCLK is sticky in the chip, so writing it as 0 leaves | ||
| 53 | // an external clock that was turned on alone. | ||
| 54 | static constexpr int MODE1_SAFE = 0x21; | ||
| 55 | |||
| 56 | 6 | PCA9685Output::PCA9685Output(unsigned int startChannel, unsigned int channelCount) : | |
| 57 | ChannelOutput(startChannel, channelCount), | ||
| 58 | 6 | i2c(nullptr), | |
| 59 |
3/6✓ Branch 0 taken 96 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
102 | m_servoCommand(nullptr) { |
| 60 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | LogDebug(VB_CHANNELOUT, "PCA9685Output::PCA9685Output(%u, %u)\n", |
| 61 | startChannel, channelCount); | ||
| 62 |
0/2✗ Branch 0 not taken.
✗ Branch 1 not taken.
|
6 | } |
| 63 | |||
| 64 | /* | ||
| 65 | * | ||
| 66 | */ | ||
| 67 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
216 | PCA9685Output::~PCA9685Output() { |
| 68 | 12 | LogDebug(VB_CHANNELOUT, "PCA9685Output::~PCA9685Output()\n"); | |
| 69 | |||
| 70 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
12 | if (m_servoCommand) { |
| 71 | // By pointer: an output reload registers the new output's command | ||
| 72 | // before this runs, and removing by name unregistered that one. | ||
| 73 | 12 | CommandManager::INSTANCE.removeCommand(m_servoCommand); | |
| 74 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
12 | delete m_servoCommand; |
| 75 | 12 | m_servoCommand = nullptr; | |
| 76 | } | ||
| 77 | |||
| 78 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
12 | if (i2c) { |
| 79 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
12 | delete i2c; |
| 80 | } | ||
| 81 |
2/2✓ Branch 0 taken 96 times.
✓ Branch 1 taken 6 times.
|
240 | } |
| 82 | |||
| 83 | 24 | static PCA9685Output::ZeroType decodeZTE(int i) { | |
| 84 |
1/5✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
24 | switch (i) { |
| 85 | 24 | case 0: | |
| 86 | 24 | return PCA9685Output::ZeroType::HOLD; | |
| 87 | ✗ | case 1: | |
| 88 | ✗ | return PCA9685Output::ZeroType::NORMAL; | |
| 89 | ✗ | case 2: | |
| 90 | ✗ | return PCA9685Output::ZeroType::CENTER; | |
| 91 | ✗ | case 3: | |
| 92 | ✗ | return PCA9685Output::ZeroType::OFF; | |
| 93 | } | ||
| 94 | ✗ | return PCA9685Output::ZeroType::HOLD; | |
| 95 | } | ||
| 96 | |||
| 97 | 2 | static PCA9685Output::ZeroType decodeZTE(const std::string& i) { | |
| 98 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (i == "Hold") { |
| 99 | 2 | return PCA9685Output::ZeroType::HOLD; | |
| 100 | } | ||
| 101 | ✗ | if (i == "Min") { | |
| 102 | ✗ | return PCA9685Output::ZeroType::MIN; | |
| 103 | } | ||
| 104 | ✗ | if (i == "Max") { | |
| 105 | ✗ | return PCA9685Output::ZeroType::MAX; | |
| 106 | } | ||
| 107 | ✗ | if (i == "Normal") { | |
| 108 | ✗ | return PCA9685Output::ZeroType::NORMAL; | |
| 109 | } | ||
| 110 | ✗ | if (i == "Center") { | |
| 111 | ✗ | return PCA9685Output::ZeroType::CENTER; | |
| 112 | } | ||
| 113 | ✗ | return PCA9685Output::ZeroType::OFF; | |
| 114 | } | ||
| 115 | 2 | static PCA9685Output::PWMType decodePWMType(const std::string& t) { | |
| 116 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (t == "LED") { |
| 117 | ✗ | return PCA9685Output::PWMType::LED; | |
| 118 | } | ||
| 119 | 2 | return PCA9685Output::PWMType::SERVO; | |
| 120 | } | ||
| 121 | 2 | static PCA9685Output::DataType decodeDataType(const std::string& i) { | |
| 122 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (i == "Scaled") { |
| 123 | ✗ | return PCA9685Output::DataType::SCALED; | |
| 124 | } | ||
| 125 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | if (i == "2x Absolute") { |
| 126 | 1 | return PCA9685Output::DataType::ABSOLUTE_DOUBLE; | |
| 127 | } | ||
| 128 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (i == "1/2 Absolute") { |
| 129 | ✗ | return PCA9685Output::DataType::ABSOLUTE_HALF; | |
| 130 | } | ||
| 131 | 1 | return PCA9685Output::DataType::ABSOLUTE; | |
| 132 | } | ||
| 133 | 1 | static int decodeFrequency(const std::string& fr) { | |
| 134 | 1 | int it = atoi(fr.c_str()); | |
| 135 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (it > 0) { |
| 136 | 1 | return it; | |
| 137 | } | ||
| 138 | ✗ | return 50; | |
| 139 | } | ||
| 140 | |||
| 141 | ✗ | unsigned short PCA9685Output::PCA9685Port::mapLEDValue(unsigned short val) { | |
| 142 | // LED | ||
| 143 | ✗ | float f = val; | |
| 144 | ✗ | if (m_reverse) { | |
| 145 | ✗ | if (is16Bit) { | |
| 146 | ✗ | f = 65535 - f; | |
| 147 | } else { | ||
| 148 | ✗ | f = 255 - f; | |
| 149 | } | ||
| 150 | } | ||
| 151 | ✗ | float bf = brightness; | |
| 152 | ✗ | float maxB = bf * 40.95f; | |
| 153 | ✗ | if (is16Bit) { | |
| 154 | ✗ | f = maxB * pow(f / 65535.0f, gamma); | |
| 155 | } else { | ||
| 156 | ✗ | f = maxB * pow(f / 255.0f, gamma); | |
| 157 | } | ||
| 158 | ✗ | val = std::round(f); | |
| 159 | ✗ | return val; | |
| 160 | } | ||
| 161 | |||
| 162 | 240 | unsigned short PCA9685Output::PCA9685Port::readValue(unsigned char* channelData, float frequency, int port) { | |
| 163 | 240 | uint32_t val = channelData[startChannel]; | |
| 164 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
|
240 | if (is16Bit) { |
| 165 | ✗ | uint32_t t = channelData[startChannel + 1]; | |
| 166 | ✗ | val = val << 8; | |
| 167 | ✗ | val += t; | |
| 168 | } | ||
| 169 |
1/2✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
|
240 | if (type == PWMType::SERVO) { |
| 170 |
2/2✓ Branch 0 taken 227 times.
✓ Branch 1 taken 13 times.
|
240 | if (val == 0) { |
| 171 |
1/5✓ Branch 0 taken 227 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
|
227 | switch (m_zeroBehavior) { |
| 172 | 227 | case PCA9685Output::ZeroType::HOLD: | |
| 173 | 227 | val = m_lastValue; | |
| 174 | 227 | break; | |
| 175 | ✗ | case PCA9685Output::ZeroType::CENTER: | |
| 176 | ✗ | val = m_center; | |
| 177 | ✗ | break; | |
| 178 | ✗ | case PCA9685Output::ZeroType::MIN: | |
| 179 | ✗ | if (m_reverse) { | |
| 180 | ✗ | val = m_max; | |
| 181 | } else { | ||
| 182 | ✗ | val = m_min; | |
| 183 | } | ||
| 184 | ✗ | break; | |
| 185 | ✗ | case PCA9685Output::ZeroType::MAX: | |
| 186 | ✗ | if (m_reverse) { | |
| 187 | ✗ | val = m_min; | |
| 188 | } else { | ||
| 189 | ✗ | val = m_max; | |
| 190 | } | ||
| 191 | ✗ | break; | |
| 192 | } | ||
| 193 | } else { | ||
| 194 | 13 | float z = val; | |
| 195 | 13 | DataType dt = m_dataType; | |
| 196 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 10 times.
|
13 | if (dt == PCA9685Output::DataType::ABSOLUTE_DOUBLE) { |
| 197 | 3 | z /= 2.0f; | |
| 198 | 3 | dt = PCA9685Output::DataType::ABSOLUTE; | |
| 199 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 7 times.
|
10 | } else if (dt == PCA9685Output::DataType::ABSOLUTE) { |
| 200 | 3 | z *= 2.0f; | |
| 201 | 3 | dt = PCA9685Output::DataType::ABSOLUTE; | |
| 202 | } | ||
| 203 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 7 times.
|
13 | if (dt == PCA9685Output::DataType::ABSOLUTE) { |
| 204 | 6 | z *= frequency; | |
| 205 | 6 | z *= 4096.0f; | |
| 206 | 6 | z /= 1000000.0f; | |
| 207 | 6 | val = std::round(z); | |
| 208 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (!is16Bit) { |
| 209 | 6 | val += m_min; | |
| 210 | } | ||
| 211 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | } else if (m_dataType == PCA9685Output::DataType::SCALED) { |
| 212 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | if (is16Bit) { |
| 213 | ✗ | if (val >= 32768) { | |
| 214 | ✗ | float scale = m_max - m_center + 1; | |
| 215 | ✗ | val -= 32768.0f; | |
| 216 | ✗ | scale *= val; | |
| 217 | ✗ | scale /= 32768.0f; | |
| 218 | ✗ | val = std::round(scale); | |
| 219 | ✗ | val += m_center; | |
| 220 | } else { | ||
| 221 | ✗ | float scale = m_center - m_min + 1; | |
| 222 | ✗ | scale *= val; | |
| 223 | ✗ | scale /= 32767.0f; | |
| 224 | ✗ | val = std::round(scale); | |
| 225 | ✗ | val += m_min; | |
| 226 | } | ||
| 227 | } else { | ||
| 228 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | if (val >= 128) { |
| 229 | 7 | float scale = m_max - m_center + 1; | |
| 230 | 7 | val -= 128.0f; | |
| 231 | 7 | scale *= val; | |
| 232 | 7 | scale /= 128.0f; | |
| 233 | 7 | val = std::round(scale); | |
| 234 | 7 | val += m_center; | |
| 235 | } else { | ||
| 236 | ✗ | float scale = m_center - m_min + 1; | |
| 237 | ✗ | scale *= val; | |
| 238 | ✗ | scale /= 128.0f; | |
| 239 | ✗ | val = std::round(scale); | |
| 240 | ✗ | val += m_min; | |
| 241 | } | ||
| 242 | } | ||
| 243 | } | ||
| 244 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
|
13 | if (val > m_max) { |
| 245 | ✗ | val = m_max; | |
| 246 | } | ||
| 247 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
|
13 | if (val < m_min) { |
| 248 | ✗ | val = m_min; | |
| 249 | } | ||
| 250 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
|
13 | if (m_reverse) { |
| 251 | ✗ | int offset = val - m_min; | |
| 252 | ✗ | val = m_max - offset; | |
| 253 | } | ||
| 254 | } | ||
| 255 | } else { | ||
| 256 | ✗ | val = mapLEDValue(val); | |
| 257 | } | ||
| 258 | 240 | m_nextValue = val; | |
| 259 | 240 | return val; | |
| 260 | } | ||
| 261 | |||
| 262 | /* | ||
| 263 | * | ||
| 264 | */ | ||
| 265 | 6 | int PCA9685Output::Init(Json::Value config) { | |
| 266 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | LogDebug(VB_CHANNELOUT, "PCA9685Output::Init(JSON)\n"); |
| 267 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | origConfig = config; |
| 268 | |||
| 269 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | Json::Value portConfigs; |
| 270 | |||
| 271 | 6 | uint32_t clock = 25000000; | |
| 272 | 6 | bool externalClock = false; | |
| 273 | |||
| 274 |
3/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1 times.
|
6 | if (config.isMember("deviceID")) { |
| 275 | // config from "Other" tab | ||
| 276 |
3/6✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
|
5 | if (config["deviceID"].isString()) { |
| 277 | ✗ | m_deviceID = std::atoi(config["deviceID"].asString().c_str()); | |
| 278 | } else { | ||
| 279 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | m_deviceID = config["deviceID"].asInt(); |
| 280 | } | ||
| 281 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | m_i2cDevice = config["device"].asString(); |
| 282 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
|
5 | if (m_i2cDevice == "") { |
| 283 | ✗ | m_i2cDevice = "i2c-1"; | |
| 284 | } | ||
| 285 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | m_frequency = config["frequency"].asInt(); |
| 286 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | portConfigs = config["ports"]; |
| 287 | } else { | ||
| 288 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | std::string subType = config["subType"].asString(); |
| 289 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | Json::Value capeConfig; |
| 290 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
1 | if (!CapeUtils::INSTANCE.getPWMConfig(subType, capeConfig)) { |
| 291 | ✗ | LogErr(VB_CHANNELOUT, "Could not get cape config for %s\n", subType.c_str()); | |
| 292 | ✗ | return 0; | |
| 293 | } | ||
| 294 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | m_i2cDevice = capeConfig["bus"].asString(); |
| 295 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | m_deviceID = capeConfig["address"].asInt(); |
| 296 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
1 | if (capeConfig.isMember("clock")) { |
| 297 | ✗ | clock = capeConfig["clock"].asInt(); | |
| 298 | ✗ | externalClock = true; | |
| 299 | } | ||
| 300 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | portConfigs = config["outputs"]; |
| 301 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | m_frequency = decodeFrequency(config["frequency"].asString()); |
| 302 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | } |
| 303 | |||
| 304 |
3/8✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
6 | i2c = new I2CUtils(m_i2cDevice.c_str(), m_deviceID); |
| 305 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (!i2c->isOk()) { |
| 306 | ✗ | LogErr(VB_CHANNELOUT, "Error opening I2C device for PCA9685 output\n"); | |
| 307 | ✗ | return 0; | |
| 308 | } | ||
| 309 | |||
| 310 | 6 | float fs = clock; | |
| 311 | 6 | float ofs = fs; | |
| 312 | 6 | fs /= 4096.f; | |
| 313 | 6 | fs /= m_frequency; | |
| 314 | |||
| 315 | 6 | int fsi = (int)(fs + 0.5f); | |
| 316 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (fsi < 3) { |
| 317 | ✗ | fsi = 3; | |
| 318 | } | ||
| 319 | |||
| 320 | 6 | m_actualFrequency = ofs / (fsi * 4096.0f); | |
| 321 | |||
| 322 | // printf("%0.3f fsi: %d of: %0.2f\n", fs, fsi, m_actualFrequency); | ||
| 323 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | LogDebug(VB_CHANNELOUT, "PCA9685 using actual frequency of: %0.2f\n", m_actualFrequency); |
| 324 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | restoreOriginalConfig(); |
| 325 | |||
| 326 | // Initialize | ||
| 327 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | int oldmode = i2c->readByteData(0x00); |
| 328 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
|
6 | if (oldmode < 0) { |
| 329 | // a failed read is -1, which as a register value would set every | ||
| 330 | // bit - EXTCLK included, which only a power cycle clears | ||
| 331 | 1 | oldmode = MODE1_SAFE; | |
| 332 | } | ||
| 333 | 6 | int m0 = (oldmode & 0x7f) | 0x10; | |
| 334 | // set sleep bit | ||
| 335 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | i2c->writeByteData(0x00, m0); |
| 336 | |||
| 337 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | i2c->writeByteData(0x01, 0x04); // ON STOP |
| 338 | // i2c->writeByteData(0x01, 0x0C); // ON ACK | ||
| 339 | |||
| 340 | // turn on the external clock | ||
| 341 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (externalClock) { |
| 342 | ✗ | oldmode |= 0x40; | |
| 343 | ✗ | m0 |= 0x40; | |
| 344 | ✗ | i2c->writeByteData(0x00, m0); | |
| 345 | } | ||
| 346 | |||
| 347 | // set the pre-scaler | ||
| 348 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | i2c->writeByteData(0xFE, fsi); |
| 349 | |||
| 350 | // auto increment bit | ||
| 351 | 6 | oldmode |= 0x20; | |
| 352 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | i2c->writeByteData(0x00, oldmode); |
| 353 | |||
| 354 | // clear sleep bit if set to restart oscillator | ||
| 355 | 6 | oldmode &= 0x6F; | |
| 356 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | i2c->writeByteData(0x00, oldmode); |
| 357 | |||
| 358 | // sleep for at least 500uS for the oscillator to stabilize | ||
| 359 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); |
| 360 | |||
| 361 | // Check if any servo ports are enabled and register command | ||
| 362 | 6 | bool hasServoPort = false; | |
| 363 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | for (uint32_t x = 0; x < numPorts; x++) { |
| 364 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (m_ports[x].type == PWMType::SERVO) { |
| 365 | 6 | hasServoPort = true; | |
| 366 | 6 | break; | |
| 367 | } | ||
| 368 | } | ||
| 369 | |||
| 370 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (!m_servoCommand) { |
| 371 |
3/8✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
6 | m_servoCommand = new ServoPositionCommand(this); |
| 372 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | CommandManager::INSTANCE.addCategorizedCommand(m_servoCommand, "Outputs", 1); |
| 373 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | LogDebug(VB_CHANNELOUT, "PCA9685: Servo Position command registered\n"); |
| 374 | } | ||
| 375 | |||
| 376 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | return ChannelOutput::Init(config); |
| 377 | 6 | } | |
| 378 | |||
| 379 | 6 | void PCA9685Output::restoreOriginalConfig() { | |
| 380 |
6/10✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
|
6 | Json::Value portConfigs = origConfig.isMember("deviceID") ? origConfig["ports"] : origConfig["outputs"]; |
| 381 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | loadPortConfig(portConfigs); |
| 382 | 6 | } | |
| 383 | 6 | void PCA9685Output::loadPortConfig(const Json::Value& portConfigs) { | |
| 384 | 6 | uint32_t sc = m_startChannel; | |
| 385 | 6 | bool asUsec = true; | |
| 386 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
|
6 | if (origConfig.isMember("deviceID")) { |
| 387 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if (origConfig.isMember("asUsec")) { |
| 388 | 5 | asUsec = origConfig["asUsec"].asInt(); | |
| 389 | } | ||
| 390 | } | ||
| 391 | // the chip has 16 ports; m_ports and data[] are sized for exactly that | ||
| 392 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | numPorts = std::min<int>(portConfigs.size(), 16); |
| 393 | |||
| 394 |
2/2✓ Branch 0 taken 26 times.
✓ Branch 1 taken 6 times.
|
32 | for (int x = 0; x < numPorts; x++) { |
| 395 |
3/6✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
|
26 | m_ports[x].description = portConfigs[x]["description"].asString(); |
| 396 | 26 | m_ports[x].m_min = portConfigs[x]["min"].asInt(); | |
| 397 | 26 | m_ports[x].m_max = portConfigs[x]["max"].asInt(); | |
| 398 |
2/2✓ Branch 0 taken 24 times.
✓ Branch 1 taken 2 times.
|
26 | if (portConfigs[x].isMember("center")) { |
| 399 | 24 | m_ports[x].m_center = portConfigs[x]["center"].asInt(); | |
| 400 | } else { | ||
| 401 | 2 | m_ports[x].m_center = (m_ports[x].m_min + m_ports[x].m_max) / 2; | |
| 402 | } | ||
| 403 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 24 times.
|
26 | if (portConfigs[x].isMember("type")) { |
| 404 |
4/8✓ 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.
|
2 | m_ports[x].type = decodePWMType(portConfigs[x]["type"].asString()); |
| 405 | } else { | ||
| 406 | 24 | m_ports[x].type = PCA9685Output::PWMType::SERVO; | |
| 407 | } | ||
| 408 | |||
| 409 |
1/2✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
|
26 | if (asUsec) { |
| 410 | 26 | float z = m_ports[x].m_min; | |
| 411 | 26 | z *= m_actualFrequency; | |
| 412 | 26 | z *= 4096; | |
| 413 | 26 | z /= 1000000; | |
| 414 | 26 | m_ports[x].m_min = std::round(z); | |
| 415 | |||
| 416 | 26 | z = m_ports[x].m_max; | |
| 417 | 26 | z *= m_actualFrequency; | |
| 418 | 26 | z *= 4096; | |
| 419 | 26 | z /= 1000000; | |
| 420 | 26 | m_ports[x].m_max = std::round(z); | |
| 421 | |||
| 422 | 26 | z = m_ports[x].m_center; | |
| 423 | 26 | z *= m_actualFrequency; | |
| 424 | 26 | z *= 4096; | |
| 425 | 26 | z /= 1000000; | |
| 426 | 26 | m_ports[x].m_center = std::round(z); | |
| 427 | 26 | LogDebug(VB_CHANNELOUT, "PCA9685 pulse ranges for output %d: %d %d %d Steps: %d\n", x, m_ports[x].m_min, m_ports[x].m_center, m_ports[x].m_max, m_ports[x].m_max - m_ports[x].m_min); | |
| 428 | // printf("PCA9685 pulse ranges for output %d: %d %d %d Steps: %d\n", x, m_ports[x].m_min, m_ports[x].m_center, m_ports[x].m_max, m_ports[x].m_max - m_ports[x].m_min); | ||
| 429 | } | ||
| 430 | |||
| 431 |
1/2✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
|
26 | if (m_ports[x].type == PCA9685Output::PWMType::SERVO) { |
| 432 |
2/2✓ Branch 0 taken 24 times.
✓ Branch 1 taken 2 times.
|
26 | if (portConfigs[x].isMember("zeroBehavior")) { |
| 433 | // co-other config | ||
| 434 | 24 | m_ports[x].m_zeroBehavior = decodeZTE(portConfigs[x]["zeroBehavior"].asInt()); | |
| 435 | 24 | int dt = portConfigs[x]["dataType"].asInt(); | |
| 436 |
1/7✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
|
24 | switch (dt) { |
| 437 | 24 | case 0: | |
| 438 | 24 | m_ports[x].m_dataType = PCA9685Output::DataType::SCALED; | |
| 439 | 24 | m_ports[x].is16Bit = false; | |
| 440 | 24 | m_ports[x].m_reverse = false; | |
| 441 | 24 | break; | |
| 442 | ✗ | case 1: | |
| 443 | ✗ | m_ports[x].m_dataType = PCA9685Output::DataType::SCALED; | |
| 444 | ✗ | m_ports[x].is16Bit = false; | |
| 445 | ✗ | m_ports[x].m_reverse = true; | |
| 446 | ✗ | break; | |
| 447 | ✗ | case 2: | |
| 448 | ✗ | m_ports[x].m_dataType = PCA9685Output::DataType::SCALED; | |
| 449 | ✗ | m_ports[x].is16Bit = true; | |
| 450 | ✗ | m_ports[x].m_reverse = false; | |
| 451 | ✗ | break; | |
| 452 | ✗ | case 3: | |
| 453 | ✗ | m_ports[x].m_dataType = PCA9685Output::DataType::SCALED; | |
| 454 | ✗ | m_ports[x].is16Bit = true; | |
| 455 | ✗ | m_ports[x].m_reverse = true; | |
| 456 | ✗ | break; | |
| 457 | ✗ | case 4: | |
| 458 | ✗ | m_ports[x].m_dataType = PCA9685Output::DataType::ABSOLUTE; | |
| 459 | ✗ | m_ports[x].is16Bit = false; | |
| 460 | ✗ | m_ports[x].m_reverse = false; | |
| 461 | ✗ | break; | |
| 462 | ✗ | case 5: | |
| 463 | ✗ | m_ports[x].m_dataType = PCA9685Output::DataType::ABSOLUTE; | |
| 464 | ✗ | m_ports[x].is16Bit = true; | |
| 465 | ✗ | m_ports[x].m_reverse = false; | |
| 466 | ✗ | break; | |
| 467 | } | ||
| 468 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
|
24 | if (m_ports[x].m_zeroBehavior == PCA9685Output::ZeroType::NORMAL) { |
| 469 | ✗ | if (m_ports[x].m_dataType == PCA9685Output::DataType::ABSOLUTE) { | |
| 470 | ✗ | m_ports[x].m_zeroBehavior = PCA9685Output::ZeroType::OFF; | |
| 471 | } else { | ||
| 472 | ✗ | m_ports[x].m_zeroBehavior = PCA9685Output::ZeroType::MIN; | |
| 473 | } | ||
| 474 | } | ||
| 475 | } else { | ||
| 476 | // co-pwm config | ||
| 477 |
4/8✓ 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.
|
2 | m_ports[x].m_zeroBehavior = decodeZTE(portConfigs[x]["zero"].asString()); |
| 478 |
4/8✓ 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.
|
2 | m_ports[x].m_dataType = decodeDataType(portConfigs[x]["dataType"].asString()); |
| 479 | 2 | m_ports[x].m_reverse = portConfigs[x]["reverse"].asInt(); | |
| 480 | 2 | m_ports[x].is16Bit = portConfigs[x]["is16bit"].asInt(); | |
| 481 | } | ||
| 482 | } else { | ||
| 483 | ✗ | m_ports[x].gamma = portConfigs[x]["gamma"].asFloat(); | |
| 484 | ✗ | m_ports[x].brightness = portConfigs[x]["brightness"].asInt(); | |
| 485 | ✗ | m_ports[x].is16Bit = portConfigs[x]["is16bit"].asInt(); | |
| 486 | ✗ | m_ports[x].m_reverse = portConfigs[x]["reverse"].asInt(); | |
| 487 | ✗ | if (m_ports[x].brightness > 100 || m_ports[x].brightness < 0) { | |
| 488 | ✗ | m_ports[x].brightness = 100; | |
| 489 | } | ||
| 490 | ✗ | if (m_ports[x].gamma < 0.01) { | |
| 491 | ✗ | m_ports[x].gamma = 0.01; | |
| 492 | } | ||
| 493 | ✗ | if (m_ports[x].gamma > 50.0f) { | |
| 494 | ✗ | m_ports[x].gamma = 50.0f; | |
| 495 | } | ||
| 496 | } | ||
| 497 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 24 times.
|
26 | if (portConfigs[x].isMember("startChannel")) { |
| 498 | 2 | m_ports[x].startChannel = portConfigs[x]["startChannel"].asInt(); | |
| 499 | } else { | ||
| 500 | 24 | m_ports[x].startChannel = sc; | |
| 501 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
|
24 | sc += m_ports[x].is16Bit ? 2 : 1; |
| 502 | } | ||
| 503 | // PrepData reads and the output test writes the sequence buffer at | ||
| 504 | // this channel. A per-port start only comes from a hand-edited config | ||
| 505 | // or the output test's config over the API; a negative one arrives | ||
| 506 | // here as a huge unsigned value. | ||
| 507 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 26 times.
|
26 | if (m_ports[x].startChannel + (m_ports[x].is16Bit ? 1 : 0) >= FPPD_MAX_CHANNELS) { |
| 508 | ✗ | LogErr(VB_CHANNELOUT, "PCA9685 port %d start channel %u is out of range, ignoring it and the ports after it\n", | |
| 509 | x + 1, m_ports[x].startChannel); | ||
| 510 | ✗ | numPorts = x; | |
| 511 | ✗ | break; | |
| 512 | } | ||
| 513 | } | ||
| 514 | 6 | } | |
| 515 | |||
| 516 | /* | ||
| 517 | * | ||
| 518 | */ | ||
| 519 | 6 | int PCA9685Output::Close(void) { | |
| 520 | 6 | LogDebug(VB_CHANNELOUT, "PCA9685Output::Close()\n"); | |
| 521 | |||
| 522 | 6 | return ChannelOutput::Close(); | |
| 523 | } | ||
| 524 | |||
| 525 | 6 | PCA9685Output::ServoPositionCommand::ServoPositionCommand(PCA9685Output* output) : | |
| 526 | Command("Servo Position", "Set servo position by percentage"), | ||
| 527 |
3/6✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
|
24 | m_output(output) { |
| 528 |
5/10✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
|
36 | args.push_back(CommandArg("ServoIndex", "int", "Servo index (1-16)").setRange(1, 16)); |
| 529 |
5/10✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
|
36 | args.push_back(CommandArg("Position", "int", "Position percentage (0-100)").setRange(0, 100)); |
| 530 | 6 | } | |
| 531 | |||
| 532 | 24 | PCA9685Output::ServoPositionCommand::~ServoPositionCommand() { | |
| 533 | 12 | LogDebug(VB_CHANNELOUT, "ServoPositionCommand destroyed\n"); | |
| 534 | 24 | } | |
| 535 | |||
| 536 | 6 | std::unique_ptr<Command::Result> PCA9685Output::ServoPositionCommand::run(const std::vector<std::string>& args) { | |
| 537 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (args.size() != 2) { |
| 538 | ✗ | return std::make_unique<Command::ErrorResult>("Servo Position requires 2 arguments: ServoIndex (1-16) and Position (0-100)"); | |
| 539 | } | ||
| 540 | |||
| 541 | int servo_index; | ||
| 542 | int position_percent; | ||
| 543 | try { | ||
| 544 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | servo_index = std::stoi(args[0]); |
| 545 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | position_percent = std::stoi(args[1]); |
| 546 | ✗ | } catch (const std::exception& e) { | |
| 547 | ✗ | return std::make_unique<Command::ErrorResult>(std::string("Parse error: ") + e.what()); | |
| 548 | ✗ | } | |
| 549 | |||
| 550 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
|
6 | if (servo_index < 1 || servo_index > 16) { |
| 551 | ✗ | return std::make_unique<Command::ErrorResult>("ServoIndex must be between 1 and 16"); | |
| 552 | } | ||
| 553 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
|
6 | if (position_percent < 0 || position_percent > 100) { |
| 554 | ✗ | return std::make_unique<Command::ErrorResult>("Position must be between 0 and 100"); | |
| 555 | } | ||
| 556 | |||
| 557 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (!m_output) { |
| 558 | ✗ | return std::make_unique<Command::ErrorResult>("PCA9685 output is not available"); | |
| 559 | } | ||
| 560 | |||
| 561 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | std::lock_guard<std::recursive_mutex> lk(m_output->m_portsLock); |
| 562 | 6 | int internal_index = servo_index - 1; | |
| 563 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
|
6 | if (internal_index < 0 || (uint32_t)internal_index >= m_output->numPorts) { |
| 564 | ✗ | return std::make_unique<Command::ErrorResult>("Servo index is out of configured ports range"); | |
| 565 | } | ||
| 566 | |||
| 567 | 6 | PCA9685Output::PCA9685Port& port = m_output->m_ports[internal_index]; | |
| 568 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (port.type != PCA9685Output::PWMType::SERVO) { |
| 569 | ✗ | return std::make_unique<Command::ErrorResult>("Selected port is not configured as SERVO"); | |
| 570 | } | ||
| 571 | |||
| 572 | 6 | uint32_t targetValue = 0; | |
| 573 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (port.m_dataType == PCA9685Output::DataType::SCALED) { |
| 574 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (port.is16Bit) { |
| 575 | ✗ | if (position_percent <= 50) { | |
| 576 | ✗ | targetValue = static_cast<uint32_t>(std::round(port.m_min + (port.m_center - port.m_min) * (position_percent / 50.0f))); | |
| 577 | } else { | ||
| 578 | ✗ | targetValue = static_cast<uint32_t>(std::round(port.m_center + (port.m_max - port.m_center) * ((position_percent - 50) / 50.0f))); | |
| 579 | } | ||
| 580 | } else { | ||
| 581 | // 8-bit range storage still in pulse ticks after config conversion | ||
| 582 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | if (position_percent <= 50) { |
| 583 | 3 | targetValue = static_cast<uint32_t>(std::round(port.m_min + (port.m_center - port.m_min) * (position_percent / 50.0f))); | |
| 584 | } else { | ||
| 585 | 3 | targetValue = static_cast<uint32_t>(std::round(port.m_center + (port.m_max - port.m_center) * ((position_percent - 50) / 50.0f))); | |
| 586 | } | ||
| 587 | } | ||
| 588 | } else { | ||
| 589 | ✗ | if (position_percent <= 50) { | |
| 590 | ✗ | targetValue = static_cast<uint32_t>(std::round(port.m_min + (port.m_center - port.m_min) * (position_percent / 50.0f))); | |
| 591 | } else { | ||
| 592 | ✗ | targetValue = static_cast<uint32_t>(std::round(port.m_center + (port.m_max - port.m_center) * ((position_percent - 50) / 50.0f))); | |
| 593 | } | ||
| 594 | } | ||
| 595 | |||
| 596 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (port.m_reverse) { |
| 597 | ✗ | if (targetValue >= port.m_min && targetValue <= port.m_max) { | |
| 598 | ✗ | targetValue = port.m_max - (targetValue - port.m_min); | |
| 599 | } | ||
| 600 | } | ||
| 601 | |||
| 602 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (targetValue < port.m_min) targetValue = port.m_min; |
| 603 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (targetValue > port.m_max) targetValue = port.m_max; |
| 604 | |||
| 605 | 6 | port.m_nextValue = targetValue; | |
| 606 | 6 | port.m_lastValue = targetValue; | |
| 607 | |||
| 608 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | m_output->SendData(nullptr); |
| 609 | |||
| 610 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | LogInfo(VB_CHANNELOUT, "ServoPosition: set servo %d to %d%% -> raw %u\n", servo_index, position_percent, targetValue); |
| 611 | |||
| 612 |
7/14✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 6 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 6 times.
✗ Branch 13 not taken.
|
6 | return std::make_unique<Command::Result>("Servo " + std::to_string(servo_index) + " set to " + std::to_string(position_percent) + "% (raw " + std::to_string(targetValue) + ")"); |
| 613 | 6 | } | |
| 614 | |||
| 615 | 6 | void PCA9685Output::StartingOutput() { | |
| 616 | 6 | int b = i2c->readByteData(0x00); | |
| 617 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
|
6 | if (b < 0) { |
| 618 | 1 | b = MODE1_SAFE; | |
| 619 | } | ||
| 620 | 6 | i2c->writeByteData(0x00, b & 0x6F); | |
| 621 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); |
| 622 | 6 | } | |
| 623 | 6 | void PCA9685Output::StoppingOutput() { | |
| 624 | 6 | uint8_t allOff[4] = { 0x00, 0x00, 0x00, 0x10 }; | |
| 625 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | i2c->writeRawI2CBlockData(0xFA, allOff, 4); |
| 626 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | std::this_thread::sleep_for(std::chrono::milliseconds(3)); |
| 627 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | int b = i2c->readByteData(0x00); |
| 628 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
|
6 | if (b < 0) { |
| 629 | 1 | b = MODE1_SAFE; | |
| 630 | } | ||
| 631 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | i2c->writeByteData(0x00, b | 0x10); |
| 632 | 6 | } | |
| 633 | |||
| 634 | 50 | void PCA9685Output::PrepData(unsigned char* channelData) { | |
| 635 |
1/2✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
|
50 | std::lock_guard<std::recursive_mutex> lk(m_portsLock); |
| 636 |
2/2✓ Branch 0 taken 240 times.
✓ Branch 1 taken 50 times.
|
290 | for (int x = 0; x < numPorts; x++) { |
| 637 |
1/2✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
|
240 | m_ports[x].readValue(channelData, m_actualFrequency, x); |
| 638 | } | ||
| 639 | 50 | } | |
| 640 | |||
| 641 | /* | ||
| 642 | * | ||
| 643 | */ | ||
| 644 | 44 | int PCA9685Output::SendData(unsigned char* channelData) { | |
| 645 |
1/2✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
|
44 | LogExcess(VB_CHANNELOUT, "PCA9685Output::SendData(%p)\n", channelData); |
| 646 |
1/2✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
|
44 | std::lock_guard<std::recursive_mutex> lk(m_portsLock); |
| 647 |
1/2✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
|
44 | lastFrameTime = GetTimeMicros(); |
| 648 | |||
| 649 |
2/2✓ Branch 0 taken 200 times.
✓ Branch 1 taken 44 times.
|
244 | for (int x = 0; x < numPorts; x++) { |
| 650 | 200 | data[x * 2] = 0; | |
| 651 | 200 | data[x * 2 + 1] = m_ports[x].m_nextValue; | |
| 652 | 200 | m_ports[x].m_lastValue = m_ports[x].m_nextValue; | |
| 653 | } | ||
| 654 |
1/2✓ Branch 0 taken 44 times.
✗ Branch 1 not taken.
|
44 | i2c->writeRawI2CBlockData(0x06, (uint8_t*)&(data[0]), numPorts * 4); |
| 655 | 44 | return m_channelCount; | |
| 656 | 44 | } | |
| 657 | |||
| 658 | 13 | void PCA9685Output::GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) { | |
| 659 |
2/2✓ Branch 0 taken 54 times.
✓ Branch 1 taken 13 times.
|
67 | for (int x = 0; x < numPorts; x++) { |
| 660 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 54 times.
|
54 | addRange(m_ports[x].startChannel, m_ports[x].startChannel + (m_ports[x].is16Bit ? 1 : 0)); |
| 661 | } | ||
| 662 | 13 | } | |
| 663 | ✗ | static inline void writeChannelValue(unsigned char* channelData, uint32_t sc, bool is16bit, uint32_t value) { | |
| 664 | ✗ | if (is16bit) { | |
| 665 | ✗ | channelData[sc] = (value >> 8) & 0xFF; | |
| 666 | ✗ | channelData[sc + 1] = value & 0xFF; | |
| 667 | } else { | ||
| 668 | ✗ | channelData[sc] = value; | |
| 669 | } | ||
| 670 | ✗ | } | |
| 671 | |||
| 672 | ✗ | void PCA9685Output::OverlayTestData(unsigned char* channelData, int cycleNum, float percentOfCycle, int testType, const Json::Value& config) { | |
| 673 | ✗ | std::lock_guard<std::recursive_mutex> lk(m_portsLock); | |
| 674 | ✗ | if (!origConfig.isMember("deviceID")) { | |
| 675 | ✗ | if (testType != lastTestType) { | |
| 676 | ✗ | restoreOriginalConfig(); | |
| 677 | } | ||
| 678 | ✗ | lastTestType = testType; | |
| 679 | ✗ | if (testType == 1) { | |
| 680 | ✗ | loadPortConfig(config["outputs"]); | |
| 681 | ✗ | int testingPort = config.isMember("manipulation") ? config["manipulation"]["port"].asInt() : -1; | |
| 682 | ✗ | uint32_t tvalue = config["manipulation"]["value"].asInt(); | |
| 683 | ✗ | bool isMin = config["manipulation"]["isMin"].asBool(); | |
| 684 | ✗ | for (int x = 0; x < numPorts; x++) { | |
| 685 | ✗ | if (m_ports[x].type == PCA9685Output::PWMType::SERVO) { | |
| 686 | ✗ | uint32_t value = m_ports[x].is16Bit ? 32768 : 128; | |
| 687 | ✗ | if (x == testingPort) { | |
| 688 | ✗ | if (m_ports[x].m_reverse) { | |
| 689 | ✗ | isMin = !isMin; | |
| 690 | } | ||
| 691 | ✗ | value = isMin ? 1 : (m_ports[x].is16Bit ? 65535 : 255); | |
| 692 | ✗ | } else if (m_ports[x].m_zeroBehavior == PCA9685Output::ZeroType::MIN) { | |
| 693 | ✗ | value = 0; | |
| 694 | ✗ | } else if (m_ports[x].m_zeroBehavior == PCA9685Output::ZeroType::MAX) { | |
| 695 | ✗ | value = m_ports[x].is16Bit ? 65535 : 255; | |
| 696 | } | ||
| 697 | ✗ | m_ports[x].m_dataType = PCA9685Output::DataType::SCALED; | |
| 698 | ✗ | writeChannelValue(channelData, m_ports[x].startChannel, m_ports[x].is16Bit, value); | |
| 699 | } else { | ||
| 700 | // LED | ||
| 701 | ✗ | uint32_t max = m_ports[x].is16Bit ? 65535 : 255; | |
| 702 | ✗ | uint32_t value = max; | |
| 703 | ✗ | value *= 100; | |
| 704 | ✗ | value *= percentOfCycle; | |
| 705 | ✗ | if (cycleNum % 2 == 0) { | |
| 706 | ✗ | value = max - value; | |
| 707 | } | ||
| 708 | ✗ | value = m_ports[x].mapLEDValue(value); | |
| 709 | ✗ | writeChannelValue(channelData, m_ports[x].startChannel, m_ports[x].is16Bit, value); | |
| 710 | } | ||
| 711 | } | ||
| 712 | } | ||
| 713 | } | ||
| 714 | ✗ | } | |
| 715 | ✗ | bool PCA9685Output::SupportsTesting() const { | |
| 716 | ✗ | return !origConfig.isMember("deviceID"); | |
| 717 | } | ||
| 718 | |||
| 719 | /* | ||
| 720 | * | ||
| 721 | */ | ||
| 722 | 6 | void PCA9685Output::DumpConfig(void) { | |
| 723 | 6 | LogDebug(VB_CHANNELOUT, "PCA9685Outputs::DumpConfig()\n"); | |
| 724 | |||
| 725 | 6 | LogDebug(VB_CHANNELOUT, " deviceID: %X\n", m_deviceID); | |
| 726 | 6 | LogDebug(VB_CHANNELOUT, " Frequency: %d\n", m_frequency); | |
| 727 |
2/2✓ Branch 0 taken 26 times.
✓ Branch 1 taken 6 times.
|
32 | for (int x = 0; x < numPorts; x++) { |
| 728 | 26 | LogDebug(VB_CHANNELOUT, " Port %d: %s\n", x, m_ports[x].description.c_str()); | |
| 729 | 26 | LogDebug(VB_CHANNELOUT, " Min: %d\n", m_ports[x].m_min); | |
| 730 | 26 | LogDebug(VB_CHANNELOUT, " Center: %d\n", m_ports[x].m_center); | |
| 731 | 26 | LogDebug(VB_CHANNELOUT, " Max: %d\n", m_ports[x].m_max); | |
| 732 | 26 | LogDebug(VB_CHANNELOUT, " DataType: %d\n", m_ports[x].m_dataType); | |
| 733 | 26 | LogDebug(VB_CHANNELOUT, " ZeroType: %d\n", m_ports[x].m_zeroBehavior); | |
| 734 | } | ||
| 735 | |||
| 736 | 6 | ChannelOutput::DumpConfig(); | |
| 737 | 6 | } | |
| 738 |