| 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 <fcntl.h> | ||
| 18 | #include <termios.h> | ||
| 19 | #include <unistd.h> | ||
| 20 | |||
| 21 | #include "../common_mini.h" // FPPstrerror() -- needed directly for NOPCH builds | ||
| 22 | #include "../log.h" | ||
| 23 | |||
| 24 | #include "USBDMX.h" | ||
| 25 | #include "serialutil.h" | ||
| 26 | #include "ChannelOutputSetup.h" | ||
| 27 | |||
| 28 | #define DMX_MAX_CHANNELS 512 | ||
| 29 | |||
| 30 | #include "Plugin.h" | ||
| 31 | class USBDMXPlugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin { | ||
| 32 | public: | ||
| 33 | 7 | USBDMXPlugin() : | |
| 34 |
2/4✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
21 | FPPPlugins::Plugin("USBDMX") { |
| 35 | 7 | } | |
| 36 | 8 | virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override { | |
| 37 |
2/6✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
16 | return new USBDMXOutput(startChannel, channelCount); |
| 38 | } | ||
| 39 | }; | ||
| 40 | |||
| 41 | extern "C" { | ||
| 42 | 7 | FPPPlugins::Plugin* createPlugin() { | |
| 43 |
2/6✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
14 | return new USBDMXPlugin(); |
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | ///////////////////////////////////////////////////////////////////////////// | ||
| 48 | |||
| 49 | 8 | USBDMXOutput::USBDMXOutput(unsigned int startChannel, unsigned int channelCount) : | |
| 50 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | ThreadedChannelOutput(startChannel, channelCount), SerialChannelOutput(), m_dongleType(DMX_DVC_UNKNOWN) { |
| 51 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | LogDebug(VB_CHANNELOUT, "USBDMXOutput::USBDMXOutput(%u, %u)\n", |
| 52 | startChannel, channelCount); | ||
| 53 | |||
| 54 | 8 | m_useDoubleBuffer = 1; | |
| 55 | // DMX protocol requires data to be sent at least every 250ms | ||
| 56 | 8 | m_maxWait = 250; | |
| 57 | 8 | m_dataOffset = 1; | |
| 58 | 8 | memset(m_outputData, 0, sizeof(m_outputData)); | |
| 59 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
|
8 | if (m_channelCount > DMX_MAX_CHANNELS) { |
| 60 |
3/6✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
|
2 | AddOutputConfigWarning(28, "USBDMX: Invalid Config. Channel count of " + std::to_string(m_channelCount) + " exceeds the DMX universe size of 512"); |
| 61 | 2 | m_channelCount = DMX_MAX_CHANNELS; | |
| 62 | } | ||
| 63 | 8 | } | |
| 64 | |||
| 65 | 32 | USBDMXOutput::~USBDMXOutput() { | |
| 66 | 16 | LogDebug(VB_CHANNELOUT, "USBDMXOutput::~USBDMXOutput()\n"); | |
| 67 | 32 | } | |
| 68 | 2 | void USBDMXOutput::GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) { | |
| 69 | 2 | addRange(m_startChannel, m_startChannel + m_channelCount - 1); | |
| 70 | 2 | } | |
| 71 | |||
| 72 | 8 | int USBDMXOutput::Init(Json::Value config) { | |
| 73 | 8 | LogDebug(VB_CHANNELOUT, "USBDMXOutput::Init()\n"); | |
| 74 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if (config.isMember("type")) { |
| 75 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | std::string type = config["type"].asString(); |
| 76 |
3/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 5 times.
|
8 | if (type == "DMX-Open") { |
| 77 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | LogDebug(VB_CHANNELOUT, "Treating device as DMX-Open compatible\n"); |
| 78 | 3 | m_dongleType = DMX_DVC_OPEN; | |
| 79 |
2/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
|
5 | } else if (type == "DMX-Pro") { |
| 80 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | LogDebug(VB_CHANNELOUT, "Treating device as DMX-Pro compatible\n"); |
| 81 | 5 | m_dongleType = DMX_DVC_PRO; | |
| 82 | } | ||
| 83 | 8 | } | |
| 84 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 5 times.
|
8 | if (m_dongleType == DMX_DVC_OPEN) { |
| 85 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
|
3 | if (!setupSerialPort(config, 250000, "8N2")) { |
| 86 | 1 | return 0; | |
| 87 | } | ||
| 88 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | } else if (m_dongleType == DMX_DVC_PRO) { |
| 89 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | if (!setupSerialPort(config, 115200, "8N1")) { |
| 90 | 5 | return 0; | |
| 91 | } | ||
| 92 | } else { | ||
| 93 | ✗ | LogErr(VB_CHANNELOUT, "Invalid Config. Unknown dongle type.\n"); | |
| 94 | ✗ | AddOutputConfigWarning(28, "USBDMX: Invalid Config. Unknown dongle type."); | |
| 95 | ✗ | return 0; | |
| 96 | } | ||
| 97 | |||
| 98 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (m_dongleType == DMX_DVC_OPEN) { |
| 99 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | if (SerialResetRTS(m_fd) != 0) { |
| 100 | 1 | LogErr(VB_CHANNELOUT, "Error %d resetting RTS on %s: %s\n", | |
| 101 | errno, m_deviceName.c_str(), FPPstrerror(errno)); | ||
| 102 | 1 | return 0; | |
| 103 | } | ||
| 104 | 1 | m_dataLen = m_channelCount + 1; | |
| 105 | ✗ | } else if (m_dongleType == DMX_DVC_PRO) { | |
| 106 | ✗ | m_outputData[0] = 0x7E; | |
| 107 | ✗ | m_outputData[1] = 0x06; | |
| 108 | ✗ | m_outputData[2] = (m_channelCount + 1) & 0xFF; | |
| 109 | ✗ | m_outputData[3] = ((m_channelCount + 1) >> 8) & 0xFF; | |
| 110 | ✗ | m_outputData[4] = 0x00; | |
| 111 | ✗ | m_dataOffset = 5; | |
| 112 | |||
| 113 | ✗ | m_outputData[m_channelCount + 5] = 0xE7; | |
| 114 | ✗ | m_dataLen = m_channelCount + 6; | |
| 115 | } | ||
| 116 | |||
| 117 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | return ThreadedChannelOutput::Init(config); |
| 118 | } | ||
| 119 | |||
| 120 | 1 | int USBDMXOutput::Close(void) { | |
| 121 | 1 | LogDebug(VB_CHANNELOUT, "USBDMXOutput::Close()\n"); | |
| 122 | 1 | closeSerialPort(); | |
| 123 | 1 | return ThreadedChannelOutput::Close(); | |
| 124 | } | ||
| 125 | |||
| 126 | 7 | int USBDMXOutput::RawSendData(unsigned char* channelData) { | |
| 127 | 7 | LogExcess(VB_CHANNELOUT, "USBDMXOutput::RawSendData(%p)\n", channelData); | |
| 128 | 7 | memcpy(m_outputData + m_dataOffset, channelData, m_channelCount); | |
| 129 | 7 | WaitTimedOut(); | |
| 130 | 7 | return m_channelCount; | |
| 131 | } | ||
| 132 | 7 | void USBDMXOutput::WaitTimedOut() { | |
| 133 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | if (m_dongleType == DMX_DVC_OPEN) { |
| 134 | // TIOCSBRK does not wait for output to drain: a break sent while the | ||
| 135 | // previous frame (~23ms for 513 slots) is still going out lands in | ||
| 136 | // the middle of it | ||
| 137 | 7 | SerialWaitDrained(m_fd, 50); | |
| 138 | 7 | SerialSendBreak(m_fd, 200); | |
| 139 | 7 | usleep(20); | |
| 140 | } | ||
| 141 | 7 | SerialWriteAll(m_fd, m_outputData, m_dataLen, 500); | |
| 142 | 7 | } | |
| 143 | |||
| 144 | 2 | void USBDMXOutput::DumpConfig(void) { | |
| 145 | 2 | LogDebug(VB_CHANNELOUT, "USBDMXOutput::DumpConfig()\n"); | |
| 146 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | LogDebug(VB_CHANNELOUT, " Dongle Type: %s\n", |
| 147 | m_dongleType == DMX_DVC_PRO ? "Pro" : m_dongleType == DMX_DVC_OPEN ? "Open" | ||
| 148 | : "UNKNOWN"); | ||
| 149 | 2 | dumpSerialConfig(); | |
| 150 | 2 | ThreadedChannelOutput::DumpConfig(); | |
| 151 | 2 | } | |
| 152 |