GCC Code Coverage Report


Directory: ./
File: channeloutput/PCF8574.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 64 87 73.6%
Functions: 9 9 100.0%
Branches: 34 74 45.9%

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 /*
14 * This driver requires a PCF8574 I2C chip with the following connections:
15 *
16 * MCP Pin Connection
17 * ---------- ------------------------
18 * 16 (VCC) - Pi Pin 4 (5V)
19 * 8 (GND) - Pi Pin 6 (Ground)
20 * 14 (SCL) - Pi Pin 5 (SCL)
21 * 15 (SDA) - Pi Pin 3 (SDA)
22 *
23 */
24
25 /*
26 * Sample channeloutputs.json config
27 *
28 * {
29 * "channelOutputs": [
30 * {
31 * "type": "PCF8574",
32 * "enabled": 1,
33 * "deviceID": 64,
34 * "startChannel": 1,
35 * "channelCount": 8,
36 * "pinOrderingInvert": false,
37 * "ports":[
38 * {
39 * "invert": true,
40 * "pinMode": 0,
41 * "threshold": 128,
42 * "hysteresisUpper":192,
43 * "hysteresisLower": 64,
44 * "description": ""
45 * },
46 * {
47 * "invert": true,
48 * ...
49 * }...
50 * ]
51 * }
52 * }
53 * ]
54 * }
55 *
56 *
57 *
58 */
59 #include "fpp-pch.h"
60
61 #include "fpp-json.h"
62
63 #include "../log.h"
64
65 #include "PCF8574.h"
66
67 #define BYTETOBINARYPATTERN "%d%d%d%d%d%d%d%d"
68 #define BYTETOBINARY(byte) \
69 (byte & 0x80 ? 1 : 0), \
70 (byte & 0x40 ? 1 : 0), \
71 (byte & 0x20 ? 1 : 0), \
72 (byte & 0x10 ? 1 : 0), \
73 (byte & 0x08 ? 1 : 0), \
74 (byte & 0x04 ? 1 : 0), \
75 (byte & 0x02 ? 1 : 0), \
76 (byte & 0x01 ? 1 : 0)
77
78 #include "Plugin.h"
79 class PCF8574Plugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin {
80 public:
81 1 PCF8574Plugin() :
82
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
3 FPPPlugins::Plugin("PCF8574") {
83 1 }
84 1 virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override {
85
2/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 return new PCF8574Output(startChannel, channelCount);
86 }
87 };
88
89 extern "C" {
90 1 FPPPlugins::Plugin* createPlugin() {
91
2/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
2 return new PCF8574Plugin();
92 }
93 }
94
95 /*
96 *
97 */
98 1 PCF8574Output::PCF8574Output(unsigned int startChannel, unsigned int channelCount) :
99 ChannelOutput(startChannel, channelCount),
100
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 times.
8 i2c(nullptr) {
101
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_CHANNELOUT, "PCF8574Output::PCF8574Output(%u, %u)\n",
102 startChannel, channelCount);
103 1 }
104
105 /*
106 *
107 */
108 4 PCF8574Output::~PCF8574Output() {
109 2 LogDebug(VB_CHANNELOUT, "PCF8574Output::~PCF8574Output()\n");
110
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
2 if (i2c) {
111
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
2 delete i2c;
112 }
113 4 }
114
115 /*
116 *
117 */
118 1 int PCF8574Output::Init(Json::Value config) {
119 1 LogDebug(VB_CHANNELOUT, "PCF8574Output::Init(JSON)\n");
120
121
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (config["deviceID"].isString()) {
122 ✗ m_deviceID = std::atoi(config["deviceID"].asString().c_str());
123 } else {
124 1 m_deviceID = config["deviceID"].asInt();
125 }
126
127
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (m_deviceID < 0x20 || m_deviceID > 0x27) {
128 ✗ LogErr(VB_CHANNELOUT, "Invalid PCF8574 Address: %X\n", m_deviceID);
129 ✗ return 0;
130 }
131
132 // one channel per pin; SendData indexes m_ports[8] by channel
133
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (m_channelCount > 8) {
134 1 LogWarn(VB_CHANNELOUT, "PCF8574 has 8 pins, using 8 of the %u channels configured\n", m_channelCount);
135 1 m_channelCount = 8;
136 }
137
138
2/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 i2c = new I2CUtils(1, m_deviceID);
139
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!i2c->isOk()) {
140 ✗ LogErr(VB_CHANNELOUT, "Error opening I2C device for PCF8574 output\n");
141 ✗ return 0;
142 } else {
143 1 LogDebug(VB_CHANNELOUT, "PCF8574Output opened I2C device ok\n");
144 }
145
146
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 times.
9 for (int x = 0; x < 8; x++) {
147 8 LogDebug(VB_CHANNELOUT, "PCF8574Output configure port %d\n", x);
148
149
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (config["ports"][x].isMember("invert")) {
150 ✗ m_ports[x].m_invert = config["ports"][x]["invert"].asInt();
151 }
152
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (config["ports"][x].isMember("pinMode")) {
153 ✗ m_ports[x].m_pinmode = config["ports"][x]["pinMode"].asInt();
154 }
155
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (config["ports"][x].isMember("threshold")) {
156 ✗ m_ports[x].m_threshold = config["ports"][x]["threshold"].asInt();
157 }
158
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (config["ports"][x].isMember("hysteresisUpper")) {
159 ✗ m_ports[x].m_hysteresisUpper = config["ports"][x]["hysteresisUpper"].asInt();
160 }
161
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (config["ports"][x].isMember("hysteresisLower")) {
162 ✗ m_ports[x].m_hysteresisLower = config["ports"][x]["hysteresisLower"].asInt();
163 }
164 }
165 1 LogDebug(VB_CHANNELOUT, "PCF8574Output config ports ok\n");
166
167
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (config.isMember("pinOrderingInvert")) {
168 1 m_pinOrderingInvert = config["pinOrderingInvert"].asInt();
169 }
170
171 1 m_lastVal = 0xFF;
172
173
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 return ChannelOutput::Init(config);
174 }
175
176 /*
177 *
178 */
179 1 int PCF8574Output::Close(void) {
180 1 LogDebug(VB_CHANNELOUT, "PCF8574Output::Close()\n");
181
182 1 return ChannelOutput::Close();
183 }
184
185 /*
186 *
187 */
188 12 int PCF8574Output::SendData(unsigned char* channelData) {
189 12 LogExcess(VB_CHANNELOUT, "PCF8574Output::SendData(%p)\n", channelData);
190
191 12 unsigned char* c = channelData;
192 12 unsigned char byte1 = 0;
193
194
2/2
✓ Branch 0 taken 96 times.
✓ Branch 1 taken 12 times.
108 for (int x = 0; x < m_channelCount; x++, c++) {
195 unsigned char pinValue;
196
197
1/4
✓ Branch 0 taken 96 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
96 switch (m_ports[x].m_pinmode) {
198 96 case 0: // Simple trigger
199 96 pinValue = ((*c) > 0);
200 96 break;
201
202 ✗ case 1: // Threshold trigger
203 ✗ pinValue = ((*c) > m_ports[x].m_threshold);
204 ✗ break;
205
206 ✗ case 2: // Hysteresis trigger
207 ✗ if (m_ports[x].m_lastState == 0) {
208 ✗ pinValue = ((*c) > m_ports[x].m_hysteresisUpper);
209
210 } else {
211 ✗ pinValue = ((*c) > m_ports[x].m_hysteresisLower);
212 }
213 ✗ m_ports[x].m_lastState = pinValue;
214 ✗ break;
215
216 ✗ default:
217 ✗ pinValue = ((*c) > 0);
218 ✗ break;
219 }
220
221 96 pinValue ^= (m_ports[x].m_invert);
222
223
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 96 times.
96 if (m_pinOrderingInvert) {
224 ✗ byte1 |= (pinValue << x);
225 } else {
226 96 byte1 |= (pinValue << (7 - x));
227 }
228 }
229
230 12 LogExcess(VB_CHANNELOUT,
231 "Byte: 0b" BYTETOBINARYPATTERN "\n",
232 BYTETOBINARY(byte1));
233
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 9 times.
12 if (byte1 != m_lastVal) {
234 3 m_lastVal = byte1;
235 3 i2c->writeByte(byte1);
236 }
237
238 12 return m_channelCount;
239 }
240
241 /*
242 *
243 */
244 1 void PCF8574Output::DumpConfig(void) {
245 1 LogDebug(VB_CHANNELOUT, "PCF8574Output::DumpConfig()\n");
246
247 1 LogDebug(VB_CHANNELOUT, " deviceID: %X\n", m_deviceID);
248
249 1 ChannelOutput::DumpConfig();
250 1 }
251