| 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 <termios.h> | ||
| 18 | #include <unistd.h> | ||
| 19 | |||
| 20 | #include "../Warnings.h" | ||
| 21 | #include "../common_mini.h" // FPPstrerror() -- needed directly for NOPCH builds | ||
| 22 | #include "../log.h" | ||
| 23 | |||
| 24 | #include "SerialChannelOutput.h" | ||
| 25 | #include "serialutil.h" | ||
| 26 | #include "USBRenard.h" | ||
| 27 | |||
| 28 | ///////////////////////////////////////////////////////////////////////////// | ||
| 29 | |||
| 30 | class USBRenardOutputData : public SerialChannelOutput { | ||
| 31 | public: | ||
| 32 | 2 | USBRenardOutputData() : | |
| 33 | 2 | maxChannels(0), | |
| 34 | 2 | speed(0), | |
| 35 | 2 | outputData(nullptr) { | |
| 36 | 2 | } | |
| 37 | 8 | ~USBRenardOutputData() { | |
| 38 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
4 | if (outputData) { |
| 39 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
2 | delete[] outputData; |
| 40 | } | ||
| 41 | 8 | } | |
| 42 | char* outputData; | ||
| 43 | int maxChannels; | ||
| 44 | int speed; | ||
| 45 | char parm[4]; | ||
| 46 | }; | ||
| 47 | |||
| 48 | // Assume clocks are accurate to 1%, so insert a pad byte every 100 bytes. | ||
| 49 | #define PAD_DISTANCE 100 | ||
| 50 | |||
| 51 | ///////////////////////////////////////////////////////////////////////////// | ||
| 52 | #include "Plugin.h" | ||
| 53 | class USBRenardPlugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin { | ||
| 54 | public: | ||
| 55 | 2 | USBRenardPlugin() : | |
| 56 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
6 | FPPPlugins::Plugin("USBRenard") { |
| 57 | 2 | } | |
| 58 | 2 | virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override { | |
| 59 |
2/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
4 | return new USBRenardOutput(startChannel, channelCount); |
| 60 | } | ||
| 61 | }; | ||
| 62 | |||
| 63 | extern "C" { | ||
| 64 | 2 | FPPPlugins::Plugin* createPlugin() { | |
| 65 |
2/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
4 | return new USBRenardPlugin(); |
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | /* | ||
| 70 | * Data for this was gathered from the DIYC wiki pages on Renard channels at | ||
| 71 | * 50ms refresh rate. For the newer (faster) speeds the PX1 page was | ||
| 72 | * referenced. | ||
| 73 | * | ||
| 74 | * http://www.doityourselfchristmas.com/wiki/index.php?title=Renard#Number_of_Circuits_.28Channels.29_per_Serial_Port | ||
| 75 | * http://www.doityourselfchristmas.com/wiki/index.php?title=Renard_PX1_Pixel_Controller#Maximum_Number_of_Pixels_per_Controller | ||
| 76 | * | ||
| 77 | */ | ||
| 78 | 2 | static int USBRenard_MaxChannels(USBRenardOutputData* privData) { | |
| 79 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (privData->maxChannels != 0) |
| 80 | ✗ | return privData->maxChannels; | |
| 81 | |||
| 82 |
2/8✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
|
2 | switch (privData->speed) { |
| 83 | ✗ | case 921600: | |
| 84 | ✗ | privData->maxChannels = 4584; | |
| 85 | ✗ | break; | |
| 86 | ✗ | case 460800: | |
| 87 | ✗ | privData->maxChannels = 2292; | |
| 88 | ✗ | break; | |
| 89 | ✗ | case 230400: | |
| 90 | ✗ | privData->maxChannels = 1146; | |
| 91 | ✗ | break; | |
| 92 | ✗ | case 115200: | |
| 93 | ✗ | privData->maxChannels = 574; | |
| 94 | ✗ | break; | |
| 95 | 1 | case 57600: | |
| 96 | 1 | privData->maxChannels = 286; | |
| 97 | 1 | break; | |
| 98 | ✗ | case 38400: | |
| 99 | ✗ | privData->maxChannels = 190; | |
| 100 | ✗ | break; | |
| 101 | ✗ | case 19200: | |
| 102 | ✗ | privData->maxChannels = 94; | |
| 103 | ✗ | break; | |
| 104 | } | ||
| 105 | 2 | return privData->maxChannels; | |
| 106 | } | ||
| 107 | |||
| 108 | 2 | USBRenardOutput::USBRenardOutput(unsigned int startChannel, unsigned int channelCount) : | |
| 109 | ChannelOutput(startChannel, channelCount), | ||
| 110 | 2 | data(nullptr) { | |
| 111 | 2 | } | |
| 112 | 8 | USBRenardOutput::~USBRenardOutput() { | |
| 113 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
4 | if (data) { |
| 114 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
4 | delete data; |
| 115 | } | ||
| 116 | 8 | } | |
| 117 | 2 | void USBRenardOutput::GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) { | |
| 118 | 2 | addRange(m_startChannel, m_startChannel + m_channelCount - 1); | |
| 119 | 2 | } | |
| 120 | 2 | int USBRenardOutput::Init(Json::Value config) { | |
| 121 | 2 | LogDebug(VB_CHANNELOUT, "USBRenard_Open()\n"); | |
| 122 | |||
| 123 |
2/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
2 | data = new USBRenardOutputData(); |
| 124 | 2 | strcpy(data->parm, "8N1"); | |
| 125 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (config.isMember("renardspeed")) { |
| 126 | 2 | data->speed = config["renardspeed"].asInt(); | |
| 127 | 2 | LogDebug(VB_CHANNELOUT, "Sending Renard at %d speed\n", data->speed); | |
| 128 | } | ||
| 129 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (config.isMember("renardparm")) { |
| 130 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | std::string parm = config["renardparm"].asString(); |
| 131 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (parm.length() != 3) { |
| 132 | ✗ | LogWarn(VB_CHANNELOUT, "Invalid length on serial parameters: %s\n", parm.c_str()); | |
| 133 | } else { | ||
| 134 | 2 | strcpy(data->parm, parm.c_str()); | |
| 135 | } | ||
| 136 | 2 | } | |
| 137 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!data->setupSerialPort(config, data->speed, data->parm)) { |
| 138 | ✗ | return 0; | |
| 139 | } | ||
| 140 | |||
| 141 | // SendData() refuses (and logs) every frame larger than the speed | ||
| 142 | // allows, and an unlisted speed allows none: say so once, here, and | ||
| 143 | // don't start the output | ||
| 144 | 2 | int maxChannels = USBRenard_MaxChannels(data); | |
| 145 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
2 | if (maxChannels == 0 || (int)m_channelCount > maxChannels) { |
| 146 | 1 | LogErr(VB_CHANNELOUT, "Renard at %d baud carries at most %d channels, %u configured\n", | |
| 147 | data->speed, maxChannels, m_channelCount); | ||
| 148 | 1 | return 0; | |
| 149 | } | ||
| 150 | 1 | data->outputData = new char[maxChannels]; | |
| 151 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (data->outputData == NULL) { |
| 152 | ✗ | LogErr(VB_CHANNELOUT, "Error %d allocating channel memory: %s\n", | |
| 153 | errno, FPPstrerror(errno)); | ||
| 154 | |||
| 155 | ✗ | return 0; | |
| 156 | } | ||
| 157 | 1 | bzero(data->outputData, data->maxChannels); | |
| 158 | |||
| 159 |
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); |
| 160 | } | ||
| 161 | |||
| 162 | 1 | int USBRenardOutput::Close(void) { | |
| 163 | 1 | LogDebug(VB_CHANNELOUT, "USBRenard_Close()\n"); | |
| 164 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (data) { |
| 165 | 1 | data->closeSerialPort(); | |
| 166 | } | ||
| 167 | 1 | return ChannelOutput::Close(); | |
| 168 | } | ||
| 169 | |||
| 170 | 5 | int USBRenardOutput::SendData(unsigned char* channelData) { | |
| 171 | 5 | LogDebug(VB_CHANNELDATA, "USBRenard_SendData()\n"); | |
| 172 | |||
| 173 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if (m_channelCount <= data->maxChannels) { |
| 174 | 5 | bzero(data->outputData, data->maxChannels); | |
| 175 | } else { | ||
| 176 | ✗ | LogErr(VB_CHANNELOUT, | |
| 177 | "USBRenard_SendData() tried to send %d bytes when max is %d\n", | ||
| 178 | m_channelCount, data->maxChannels); | ||
| 179 | ✗ | return 0; | |
| 180 | } | ||
| 181 | |||
| 182 | 5 | memcpy(data->outputData, channelData, m_channelCount); | |
| 183 | |||
| 184 | // Act like "Renard (modified)" and don't output special codes. There are | ||
| 185 | // 3 we need to worry about. | ||
| 186 | // 0x7D - Pad Byte - map to 0x7C | ||
| 187 | // 0x7E - Sync Byte - map to 0x7C | ||
| 188 | // 0x7F - Escape Byte - map to 0x80 | ||
| 189 | 5 | char* dptr = data->outputData; | |
| 190 | 5 | int i = 0; | |
| 191 |
2/2✓ Branch 0 taken 1430 times.
✓ Branch 1 taken 5 times.
|
1435 | for (i = 0; i < data->maxChannels; i++) { |
| 192 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1430 times.
|
1430 | if (*dptr == '\x7D') |
| 193 | ✗ | *dptr = '\x7C'; | |
| 194 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1430 times.
|
1430 | else if (*dptr == '\x7E') |
| 195 | ✗ | *dptr = '\x7C'; | |
| 196 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1430 times.
|
1430 | else if (*dptr == '\x7F') |
| 197 | ✗ | *dptr = '\x80'; | |
| 198 | 1430 | dptr++; | |
| 199 | } | ||
| 200 | |||
| 201 | // Send start of packet byte | ||
| 202 | 5 | SerialWrite(data->getFD(), "\x7E\x80", 2); | |
| 203 | 5 | dptr = data->outputData; | |
| 204 | |||
| 205 | // Assume clocks are accurate to 1%, so insert a pad byte every 100 bytes. | ||
| 206 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
|
10 | for (i = 0; i * PAD_DISTANCE < m_channelCount; i++) { |
| 207 | // Send our pad byte | ||
| 208 | 5 | SerialWrite(data->getFD(), "\x7D", 1); | |
| 209 | |||
| 210 | // Send Renard Data (Only send the channels we're given, not max) | ||
| 211 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if ((i + 1) * PAD_DISTANCE > m_channelCount) |
| 212 | 5 | SerialWrite(data->getFD(), dptr, (m_channelCount - (i * PAD_DISTANCE))); | |
| 213 | else | ||
| 214 | ✗ | SerialWrite(data->getFD(), dptr, PAD_DISTANCE); | |
| 215 | |||
| 216 | 5 | dptr += PAD_DISTANCE; | |
| 217 | } | ||
| 218 | 5 | return m_channelCount; | |
| 219 | } | ||
| 220 | |||
| 221 | 1 | void USBRenardOutput::DumpConfig(void) { | |
| 222 | 1 | ChannelOutput::DumpConfig(); | |
| 223 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!data) |
| 224 | ✗ | return; | |
| 225 | 1 | data->dumpSerialConfig(); | |
| 226 | 1 | LogDebug(VB_CHANNELOUT, " maxChannels: %i\n", data->maxChannels); | |
| 227 | 1 | LogDebug(VB_CHANNELOUT, " speed : %d\n", data->speed); | |
| 228 | 1 | LogDebug(VB_CHANNELOUT, " serial Parm: %s\n", data->parm); | |
| 229 | } | ||
| 230 |