GCC Code Coverage Report


Directory: ./
File: channeloutput/MQTTOutput.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 140 143 97.9%
Functions: 12 12 100.0%
Branches: 114 186 61.3%

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 <cmath>
18
19 #include "../common.h"
20 #include "../log.h"
21 #include "../mqtt.h"
22
23 #include "MQTTOutput.h"
24
25 /////////////////////////////////////////////////////////////////////////////
26 #include "Plugin.h"
27 class MQTTPlugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin {
28 public:
29 15 MQTTPlugin() :
30
2/4
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✗ Branch 3 not taken.
45 FPPPlugins::Plugin("MQTT") {
31 15 }
32 15 virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override {
33
2/6
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
30 return new MQTTOutput(startChannel, channelCount);
34 }
35 };
36
37 extern "C" {
38 15 FPPPlugins::Plugin* createPlugin() {
39
2/6
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
30 return new MQTTPlugin();
40 }
41 }
42 /*
43 *
44 */
45 15 MQTTOutput::MQTTOutput(unsigned int startChannel, unsigned int channelCount) :
46 ThreadedChannelOutput(startChannel, channelCount),
47
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
30 m_payload("%R%,%G%,%B%"),
48 15 m_type(OutputType::THREE_CHAN),
49 15 m_r(0x00),
50 15 m_g(0x00),
51 15 m_b(0x00),
52 15 m_w(0x00) {
53
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 LogDebug(VB_CHANNELOUT, "MQTTOutput::MQTTOutput(%u, %u)\n",
54 startChannel, channelCount);
55
56 15 m_useDoubleBuffer = 1;
57 15 }
58
59 /*
60 *
61 */
62 60 MQTTOutput::~MQTTOutput() {
63 30 LogDebug(VB_CHANNELOUT, "MQTTOutput::~MQTTOutput()\n");
64 60 }
65
66 15 int MQTTOutput::Init(Json::Value config) {
67 15 LogDebug(VB_CHANNELOUT, "MQTTOutput::Init()\n");
68
69
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 14 times.
15 if (!mqtt) {
70 1 LogWarn(VB_CHANNELOUT, "MQTT Not Configured, cannot start MQTT Output");
71 1 return false;
72 }
73
74
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 if (config.isMember("topic")) {
75
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14 times.
✗ Branch 3 not taken.
14 m_topic = config["topic"].asString();
76 }
77 // A publish topic may not contain the subscription wildcards: the library
78 // refused every publish, and each refusal was logged, up to once a frame.
79
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 11 times.
14 if (m_topic.find_first_of("+#") != std::string::npos) {
80 3 LogErr(VB_CHANNELOUT, "MQTT Output topic '%s' contains a wildcard (+ or #); not starting it\n", m_topic.c_str());
81 3 return false;
82 }
83
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 5 times.
11 if (config.isMember("payload")) {
84
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 auto payload = config["payload"].asString();
85
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
6 if (!payload.empty()) {
86
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 m_payload = payload;
87 }
88 6 }
89
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 5 times.
11 if (config.isMember("channelType")) {
90
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
6 auto type = config["channelType"].asString();
91
3/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 5 times.
6 if (type == "RGBW") {
92 1 m_type = OutputType::FOUR_CHAN;
93
3/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 1 times.
5 } else if (type == "RGB") {
94 4 m_type = OutputType::THREE_CHAN;
95
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 } else if (type == "Single") {
96 1 m_type = OutputType::ONE_CHAN;
97 }
98 6 }
99
100
2/4
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
11 return ThreadedChannelOutput::Init(config);
101 }
102
103 22 void MQTTOutput::GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) {
104 22 addRange(m_startChannel, m_startChannel + (int)m_type - 1);
105 22 }
106
107 /*
108 *
109 */
110 11 int MQTTOutput::Close(void) {
111 11 LogDebug(VB_CHANNELOUT, "MQTTOutput::Close()\n");
112
113 11 return ThreadedChannelOutput::Close();
114 }
115
116 /*
117 *
118 */
119 1069 int MQTTOutput::RawSendData(unsigned char* channelData) {
120
1/2
✓ Branch 0 taken 1069 times.
✗ Branch 1 not taken.
1069 LogExcess(VB_CHANNELOUT, "MQTTOutput::RawSendData(%p)\n", channelData);
121
122
6/8
✓ Branch 0 taken 1069 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1069 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1068 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 1068 times.
1069 if (!mqtt || !mqtt->IsConnected()) {
123 1 return 0;
124 }
125
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1068 times.
1068 if (m_topic.empty()) {
126 ✗ return 0;
127 }
128
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1068 times.
1068 if (m_payload.empty()) {
129 ✗ return 0;
130 }
131
132 1068 uint8_t r = 0x00;
133 1068 uint8_t g = 0x00;
134 1068 uint8_t b = 0x00;
135 1068 uint8_t w = 0x00;
136
137 // The double buffer that ThreadedOutputBase performs only copies/buffers
138 // data from the startChannel for the required length. The channelData
139 // passed in is already starting at startChannel
140
3/4
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 128 times.
✓ Branch 2 taken 812 times.
✗ Branch 3 not taken.
1068 switch (m_type) {
141 128 case OutputType::ONE_CHAN:
142 128 w = channelData[0];
143 128 break;
144 128 case OutputType::FOUR_CHAN:
145 128 w = channelData[3];
146 940 case OutputType::THREE_CHAN:
147 940 r = channelData[0];
148 940 g = channelData[1];
149 940 b = channelData[2];
150 940 break;
151 }
152
153
8/8
✓ Branch 0 taken 1020 times.
✓ Branch 1 taken 48 times.
✓ Branch 2 taken 1017 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 1016 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1007 times.
✓ Branch 7 taken 9 times.
1068 if (r == m_r && g == m_g && b == m_b && w == m_w) {
154 1007 return m_channelCount;
155 }
156
157 61 m_r = r;
158 61 m_g = g;
159 61 m_b = b;
160 61 m_w = w;
161
162
1/2
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
61 std::string payload = m_payload;
163
164
1/2
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
61 ReplaceValues(payload, r, g, b, w);
165
166
1/2
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
61 mqtt->PublishRaw(m_topic, payload);
167
168 61 return m_channelCount;
169 }
170
171 /*
172 *
173 */
174 22 void MQTTOutput::DumpConfig(void) {
175 22 LogDebug(VB_CHANNELOUT, "MQTTOutput::DumpConfig()\n");
176
177 22 LogDebug(VB_CHANNELOUT, " Topic : %s\n", m_topic.c_str());
178 22 LogDebug(VB_CHANNELOUT, " Payload : %s\n", m_payload.c_str());
179 22 LogDebug(VB_CHANNELOUT, " Type : %d\n", (int)m_type);
180
181 22 ThreadedChannelOutput::DumpConfig();
182 22 }
183
184 61 void MQTTOutput::ReplaceValues(std::string& valueStr, uint8_t r, uint8_t g, uint8_t b, uint8_t w) {
185
2/4
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
183 replaceAll(valueStr, "%R%", std::to_string(r));
186
2/4
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
183 replaceAll(valueStr, "%G%", std::to_string(g));
187
2/4
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
183 replaceAll(valueStr, "%B%", std::to_string(b));
188
2/4
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
183 replaceAll(valueStr, "%W%", std::to_string(w));
189
190 61 int rScale = (r * 100) / 255;
191 61 int gScale = (g * 100) / 255;
192 61 int bScale = (b * 100) / 255;
193 61 int wScale = (w * 100) / 255;
194
195
2/4
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
183 replaceAll(valueStr, "%RS%", std::to_string(rScale));
196
2/4
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
183 replaceAll(valueStr, "%GS%", std::to_string(gScale));
197
2/4
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
183 replaceAll(valueStr, "%BS%", std::to_string(bScale));
198
2/4
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
183 replaceAll(valueStr, "%WS%", std::to_string(wScale));
199
200
8/8
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 48 times.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 45 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 43 times.
61 if (r > 254 || g > 254 || b > 254 || w > 254) {
201
3/6
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
72 replaceAll(valueStr, "%SW%", "ON");
202 } else {
203
3/6
✓ Branch 0 taken 43 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 43 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 43 times.
✗ Branch 5 not taken.
172 replaceAll(valueStr, "%SW%", "OFF");
204 }
205
206 float h, si, sv, i, v;
207
208 // 0..1 floats: integer division made every channel 0 (or 1 at full)
209
1/2
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
61 RGBtoHSIV(r / 255.0f, g / 255.0f, b / 255.0f, h, si, sv, i, v);
210
211 61 int ih = (h);
212 61 int isi = (si * 100);
213 61 int isv = (sv * 100);
214 61 int ii = (i * 100);
215 61 int iv = (v * 100);
216
217
2/4
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
183 replaceAll(valueStr, "%H%", std::to_string(ih));
218
2/4
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
183 replaceAll(valueStr, "%S%", std::to_string(isi));
219
2/4
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
183 replaceAll(valueStr, "%SI%", std::to_string(isi));
220
2/4
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
183 replaceAll(valueStr, "%SV%", std::to_string(isv));
221
2/4
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
183 replaceAll(valueStr, "%I%", std::to_string(ii));
222
2/4
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
183 replaceAll(valueStr, "%V%", std::to_string(iv));
223 61 }
224
225 61 void MQTTOutput::RGBtoHSIV(float fR, float fG, float fB, float& fH, float& fSI, float& fSV, float& fI, float& fV) {
226 61 float M = std::max(std::max(fR, fG), fB);
227 61 float m = std::min(std::min(fR, fG), fB);
228 61 float c = M - m;
229 61 fV = M;
230 // fL = (1.0/2.0)*(M+m);
231 61 fI = (1.0 / 3.0) * (fR + fG + fB);
232
233
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 35 times.
61 if (c == 0) {
234 26 fH = 0.0;
235 26 fSI = 0.0;
236 } else {
237
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 18 times.
35 if (M == fR) {
238 17 fH = fmod(((fG - fB) / c), 6.0);
239
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 12 times.
18 } else if (M == fG) {
240 6 fH = (fB - fR) / c + 2.0;
241
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 } else if (M == fB) {
242 12 fH = (fR - fG) / c + 4.0;
243 }
244 35 fH *= 60.0;
245
1/2
✓ Branch 0 taken 35 times.
✗ Branch 1 not taken.
35 if (fI != 0) {
246 35 fSI = 1.0 - (m / fI);
247 } else {
248 ✗ fSI = 0.0;
249 }
250 }
251
252
2/2
✓ Branch 0 taken 47 times.
✓ Branch 1 taken 14 times.
61 fSV = M == 0 ? 0 : (M - m) / M;
253
254
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 58 times.
61 if (fH < 0.0)
255 3 fH += 360.0;
256 61 }
257