| 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 |