GCC Code Coverage Report


Directory: ./
File: channeloutput/USBRelay.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 61 102 59.8%
Functions: 10 10 100.0%
Branches: 39 116 33.6%

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 <unistd.h>
18
19 #include "../Warnings.h"
20 #include "../log.h"
21
22 #include "Plugin.h"
23 #include "USBRelay.h"
24 #include "serialutil.h"
25
26 class USBRelayPlugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin {
27 public:
28 1 USBRelayPlugin() :
29
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
3 FPPPlugins::Plugin("USBRelay") {}
30 1 virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override {
31
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 USBRelayOutput(startChannel, channelCount);
32 }
33 };
34
35 extern "C" {
36 1 FPPPlugins::Plugin* createPlugin() {
37
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 USBRelayPlugin();
38 }
39 }
40
41 /////////////////////////////////////////////////////////////////////////////
42
43 1 USBRelayOutput::USBRelayOutput(unsigned int startChannel, unsigned int channelCount) :
44
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 ChannelOutput(startChannel, channelCount), SerialChannelOutput(), m_subType(RELAY_DVC_UNKNOWN), m_relayCount(0) {
45
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_CHANNELOUT, "USBRelayOutput::USBRelayOutput(%u, %u)\n",
46 startChannel, channelCount);
47 1 }
48
49 4 USBRelayOutput::~USBRelayOutput() {
50 2 LogDebug(VB_CHANNELOUT, "USBRelayOutput::~USBRelayOutput()\n");
51 4 }
52
53 2 void USBRelayOutput::GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) {
54 2 addRange(m_startChannel, m_startChannel + m_relayCount - 1);
55 2 }
56
57 1 int USBRelayOutput::Init(Json::Value config) {
58
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogDebug(VB_CHANNELOUT, "USBRelayOutput::Init(JSON)\n");
59
60
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();
61
62
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (subType == "Bit")
63 ✗ m_subType = RELAY_DVC_BIT;
64
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 else if (subType == "ICStation")
65 ✗ m_subType = RELAY_DVC_ICSTATION;
66
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 else if (subType == "CH340")
67 1 m_subType = RELAY_DVC_CH340;
68
69
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 m_relayCount = config["channelCount"].asInt();
70
71
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (m_subType == RELAY_DVC_UNKNOWN) {
72 ✗ LogErr(VB_CHANNELOUT, "Invalid Config, missing device or invalid type\n");
73 ✗ return 0;
74 }
75
76
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (!setupSerialPort(config, 9600, "8N1")) {
77 ✗ return 0;
78 }
79
80
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (m_subType == RELAY_DVC_ICSTATION) {
81 // Send Initialization Sequence
82 ✗ unsigned char c_init = 0x50;
83 ✗ unsigned char c_reply = 0x00;
84 ✗ unsigned char c_open = 0x51;
85
86 ✗ sleep(1);
87 ✗ SerialWrite(m_fd, &c_init, 1);
88 ✗ usleep(500000);
89
90 ✗ bool foundICS = false;
91 ✗ int res = read(m_fd, &c_reply, 1);
92 ✗ if (res == 0) {
93 ✗ LogWarn(VB_CHANNELOUT, "Did not receive a response byte from ICstation relay\n");
94 ✗ } else if (c_reply == 0xAB) {
95 ✗ LogInfo(VB_CHANNELOUT, "Found a 4-channel ICStation relay module\n");
96 ✗ m_relayCount = 4;
97 ✗ foundICS = true;
98 ✗ } else if (c_reply == 0xAC) {
99 ✗ LogInfo(VB_CHANNELOUT, "Found a 8-channel ICStation relay module\n");
100 ✗ m_relayCount = 8;
101 ✗ foundICS = true;
102 ✗ } else if (c_reply == 0xAD) {
103 ✗ LogInfo(VB_CHANNELOUT, "Found a 2-channel ICStation relay module\n");
104 ✗ m_relayCount = 2;
105 ✗ foundICS = true;
106 } else {
107 ✗ LogWarn(VB_CHANNELOUT, "Warning: ICStation USB Relay response of 0x%02x doesn't match "
108 "known values. Unable to detect number of relays.\n",
109 c_reply);
110 }
111
112 ✗ if (foundICS)
113 ✗ SerialWrite(m_fd, &c_open, 1);
114
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 } else if (m_subType == RELAY_DVC_CH340) {
115
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogInfo(VB_CHANNELOUT, "Initializing CH340 USB Relay with %d channels\n", m_relayCount);
116 }
117
118 // Sized here rather than above since ICStation detection can override the
119 // configured relay count.
120
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (m_relayCount > 0) {
121
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 m_lastState.assign(m_relayCount, -1);
122
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 m_outputBuffer.resize(m_relayCount * 4);
123 }
124
125
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);
126 1 }
127
128 1 int USBRelayOutput::Close(void) {
129 1 LogDebug(VB_CHANNELOUT, "USBRelayOutput::Close()\n");
130 1 closeSerialPort();
131 1 return ChannelOutput::Close();
132 }
133
134 109 int USBRelayOutput::SendData(unsigned char* channelData) {
135 109 LogExcess(VB_CHANNELOUT, "USBRelayOutput::RawSendData(%p)\n", channelData);
136
137 109 int len = 0;
138
139
1/2
✓ Branch 0 taken 109 times.
✗ Branch 1 not taken.
109 if (m_subType == RELAY_DVC_CH340) {
140 // CH340-specific logic: Send a 4-byte command for each relay to control its state.
141 // The relays latch, and a full refresh of 8 relays at 40fps is 1280 B/s
142 // through a 9600-baud (960 B/s) port, so only changed relays are sent.
143
2/2
✓ Branch 0 taken 872 times.
✓ Branch 1 taken 109 times.
981 for (int i = 0; i < m_relayCount; i++) { // Loop over each relay channel (1 to m_relayCount)
144
2/2
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 786 times.
872 signed char state = channelData[i] ? 1 : 0;
145
2/2
✓ Branch 0 taken 862 times.
✓ Branch 1 taken 10 times.
872 if (m_lastState[i] == state)
146 862 continue;
147
148 10 unsigned char* cmd = &m_outputBuffer[len];
149 10 cmd[0] = 0xA0; // Byte 0: the start flag (0xA0)
150 10 cmd[1] = i + 1; // Byte 1: Relay number (1-based: 1 to 8 for an 8-channel relay)
151 10 cmd[2] = state; // Byte 2: Relay state (1 for ON, 0 for OFF), based on channelData[i]
152 10 cmd[3] = cmd[0] + cmd[1] + cmd[2]; // Byte 3: Checksum, sum of bytes 0, 1, and 2
153 10 len += 4;
154
155 10 m_lastState[i] = state;
156 }
157 } else {
158 // Non-CH340 (Bit or ICStation) logic: Send data as a bitstream, 8 bits per byte
159 ✗ unsigned char out = 0x00;
160 ✗ int shiftBits = 0;
161
162 ✗ for (int i = 0; i < m_relayCount; i++) {
163 ✗ if ((i > 0) && ((i % 8) == 0)) {
164 ✗ m_outputBuffer[len++] = out;
165 ✗ out = 0x00;
166 ✗ shiftBits = 0;
167 }
168
169 ✗ out |= (channelData[i] ? 1 : 0) << shiftBits;
170 ✗ shiftBits++;
171 }
172
173 ✗ if (shiftBits)
174 ✗ m_outputBuffer[len++] = out;
175 }
176
177
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 107 times.
109 if (len)
178 2 SerialWrite(m_fd, m_outputBuffer.data(), len); // Write the frame's commands to the serial device (m_fd)
179
180 109 return m_relayCount;
181 }
182
183 1 void USBRelayOutput::DumpConfig(void) {
184 1 LogDebug(VB_CHANNELOUT, "USBRelayOutput::DumpConfig()\n");
185 1 dumpSerialConfig();
186 1 ChannelOutput::DumpConfig();
187 1 }
188