Skip to content

Commit

Permalink
plugins/dmxusb: add dedicated class for DMXking MAX devices
Browse files Browse the repository at this point in the history
IO is performed using Art-Net over USB
  • Loading branch information
mcallegari committed Dec 1, 2024
1 parent cd9ffcc commit c1c1507
Show file tree
Hide file tree
Showing 7 changed files with 485 additions and 29 deletions.
23 changes: 5 additions & 18 deletions plugins/dmxusb/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,6 @@ if(UNIX)
# Link your executable against the libftdi1 library
target_link_libraries(${module_name} PRIVATE ${LIBFTDI1_LINK_LIBRARIES})

if (APPLE)
#include(../../../platforms/macos/nametool.pri)
#add_custom_command(TARGET ${module_name}
# COMMAND ${PKG_CONFIG_NAMETOOL} libusb-1.0 libusb-1.0.0.dylib
# COMMAND ${PKG_CONFIG_NAMETOOL} libftdi1 libftdi1.2.dylib
# COMMENT "Creating symlinks for libusb-1.0.0.dylib and libftdi1.2.dylib"
#)
endif()

message("Building with libFTDI1 support. Version: ${LIBFTDI1_VERSION}")
set(WITH_LIBFTDI TRUE)

Expand All @@ -75,14 +66,6 @@ if(UNIX)
target_compile_definitions(${module_name} PRIVATE LIBFTDI)
target_link_libraries(${module_name} PRIVATE ${LIBFTDI_LIBRARIES})

if (APPLE)
#include(../../../platforms/macos/nametool.pri)
#add_custom_command(TARGET ${module_name}
# COMMAND ${PKG_CONFIG_NAMETOOL} libftdi libftdi.1.dylib
# COMMENT "Creating symlink for libftdi.1.dylib"
#)
endif()

message("Building with libFTDI support.")
set(WITH_LIBFTDI TRUE)

Expand All @@ -96,26 +79,30 @@ target_sources(${module_name} PRIVATE
../../interfaces/qlcioplugin.cpp ../../interfaces/qlcioplugin.h
../../interfaces/rdmprotocol.cpp ../../interfaces/rdmprotocol.h
../../midi/src/common/midiprotocol.cpp ../../midi/src/common/midiprotocol.h
../../artnet/src/artnetpacketizer.cpp ../../artnet/src/artnetpacketizer.h
dmxinterface.cpp dmxinterface.h
${module_name}.cpp ${module_name}.h
dmxusbconfig.cpp dmxusbconfig.h
dmxusbopenrx.cpp dmxusbopenrx.h
dmxusbwidget.cpp dmxusbwidget.h
enttecdmxusbopen.cpp enttecdmxusbopen.h
enttecdmxusbpro.cpp enttecdmxusbpro.h
dmxkingmax.cpp dmxkingmax.h
stageprofi.cpp stageprofi.h
vinceusbdmx512.cpp vinceusbdmx512.h
)

target_include_directories(${module_name} PRIVATE
../../interfaces
../../midi/src/common
../../artnet/src
)

target_link_libraries(${module_name} PRIVATE
Qt${QT_MAJOR_VERSION}::Core
Qt${QT_MAJOR_VERSION}::Gui
Qt${QT_MAJOR_VERSION}::Widgets
Qt${QT_MAJOR_VERSION}::Network
)

if(UNIX)
Expand Down Expand Up @@ -169,4 +156,4 @@ endif()
install(TARGETS ${module_name}
LIBRARY DESTINATION ${INSTALLROOT}/${PLUGINDIR}
RUNTIME DESTINATION ${INSTALLROOT}/${PLUGINDIR}
)
)
330 changes: 330 additions & 0 deletions plugins/dmxusb/src/dmxkingmax.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
/*
Q Light Controller
dmxkingmax.cpp
Copyright (C) Massimo Callegari
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.txt
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include "dmxkingmax.h"
#include "artnetpacketizer.h"

#define ENTTEC_PRO_START_OF_MSG char(0x7E)
#define ENTTEC_PRO_END_OF_MSG char(0xE7)

#define DMXKING_UDP_SEND_LABEL char(0x6F)
#define DMXKING_UDP_RECEIVE_LABEL char(0x70)

DMXKingMAX::DMXKingMAX(DMXInterface *iface, quint32 outputLine, quint32 inputLine)
: QThread(NULL)
, DMXUSBWidget(iface, outputLine, DEFAULT_OUTPUT_FREQUENCY)
, m_threadRunning(false)
, m_rdm(NULL)
{
m_inputBaseLine = inputLine;

getDeviceInfo();
}

DMXKingMAX::~DMXKingMAX()
{
qDebug() << Q_FUNC_INFO;
close(m_inputBaseLine, true);
close(m_outputBaseLine, false);

if (m_rdm)
free(m_rdm);
}

DMXUSBWidget::Type DMXKingMAX::type() const
{
return DMXUSBWidget::DMXKingMax;
}

QString DMXKingMAX::additionalInfo() const
{
QString info;

info += QString("<P>");

info += QString("<B>%1:</B> %2").arg(tr("Protocol")).arg("ultraDMX USB Pro");
info += QString("<BR>");
info += QString("<B>%1:</B> DMXKing").arg(tr("Manufacturer"));
info += QString("<BR>");
info += QString("<B>%1:</B> %2").arg(tr("Serial number")).arg(m_serialNumber);
info += QString("</P>");

return info;
}

void DMXKingMAX::getDeviceInfo()
{
QByteArray pollPacket;
ArtNetPacketizer packetizer;
packetizer.setupArtNetPoll(pollPacket);

if (iface()->open() == false)
return;

iface()->clearRts();

if (sendUDPMessage(pollPacket) == false)
{
qDebug() << "Failed to send ArtPoll data";
return;
}

usleep(100000);

bool noReply = false;

while (noReply == false)
{
QByteArray reply;
ArtNetNodeInfo devNode;

if (receiveUDPMessage(reply) == false)
{
qDebug() << "Failed to receive ArtPollReply data";
break;
}

qDebug() << "Universe" << devNode.universe << "isInput" << devNode.isInput << "isOutput" << devNode.isOutput;

if (!packetizer.fillArtPollReplyInfo(reply, devNode))
{
qWarning() << "[DMXKing] Bad ArtPollReply received";
break;
}
msleep(1000);
}

iface()->close();
}

bool DMXKingMAX::sendUDPMessage(QByteArray &data)
{
QByteArray packet;
ushort udpDataLen = data.length() + 8;

// Append packet header
packet.append(ENTTEC_PRO_START_OF_MSG);
packet.append(DMXKING_UDP_SEND_LABEL);
packet.append(udpDataLen & 0xFF); // UDP data length LSB
packet.append((udpDataLen >> 8) & 0xFF); // UDP data length MSB

// Append UDP Protocol Datagram info
packet.append(char(0x19)); // source port MSB
packet.append(char(0x79)); // source port LSB
packet.append(char(0x19)); // destination port MSB - Art-Net
packet.append(char(0x36)); // destination port LSB - Art-Net
packet.append((udpDataLen >> 8) & 0xFF); // UDP data length MSB
packet.append(udpDataLen & 0xFF); // UDP data length LSB
packet.append(char(0x00)); // checksum MSB - unused
packet.append(char(0x00)); // checksum LSB - unused

packet.append(data);
packet.append(ENTTEC_PRO_END_OF_MSG);

if (iface()->write(packet) == false)
{
qWarning() << Q_FUNC_INFO << name() << "cannot write UDP message";
return false;
}

return true;
}

bool DMXKingMAX::receiveUDPMessage(QByteArray &data)
{
QByteArray recvData = iface()->read(1024);

if (recvData.isEmpty())
{
qDebug() << "No data received!";
return false;
}

if (recvData.at(0) != ENTTEC_PRO_START_OF_MSG)
{
qDebug() << "Bad start code. Got" << recvData.at(0) << "instead of" << ENTTEC_PRO_START_OF_MSG;
return false;
}

if (recvData.at(1) != DMXKING_UDP_RECEIVE_LABEL)
{
qDebug() << "Bad label code. Got" << recvData.at(1) << "instead of" << DMXKING_UDP_RECEIVE_LABEL;
return false;
}

int udpLen = (uchar(recvData.at(3)) << 8) + uchar(recvData.at(2));

data = recvData.mid(12, udpLen - 8);
qDebug() << "Packet payload:" << data.toHex(',');

return true;
}

void DMXKingMAX::stopThread()
{
qDebug() << Q_FUNC_INFO;

if (m_threadRunning == true)
{
m_threadRunning = false;
wait();
}
}

void DMXKingMAX::run()
{
qDebug() << "OUTPUT thread started";
QElapsedTimer timer;
ArtNetPacketizer packetizer;

m_threadRunning = true;

while (m_threadRunning == true)
{
timer.restart();

// no open output lines: do nothing
if (openOutputLines() == 0)
goto framesleep;

// output data first
for (int i = 0; i < m_outputLines.count(); i++)
{

int dataLen = m_outputLines[i].m_universeData.length();
if (dataLen == 0)
continue;

QByteArray outPacket;
packetizer.setupArtNetDmx(outPacket, i, m_outputLines[i].m_universeData);
if (iface()->write(outPacket) == false)
{
qWarning() << Q_FUNC_INFO << name() << "will not accept DMX data";
continue;
}
}

framesleep:
int timetoSleep = m_frameTimeUs - (timer.nsecsElapsed() / 1000);
if (timetoSleep < 0)
qWarning() << "DMX output is running late !";
else
usleep(timetoSleep);
}
}

/****************************************************************************
* Open & Close
****************************************************************************/

bool DMXKingMAX::open(quint32 line, bool input)
{
if (DMXUSBWidget::open(line, input) == false)
return close(line, input);

if (iface()->clearRts() == false)
return close(line, input);

// start the input/output thread
if (m_threadRunning == false)
start();

return true;
}

bool DMXKingMAX::close(quint32 line, bool input)
{
stopThread();

return DMXUSBWidget::close(line, input);
}

/************************************************************************
* Name & Serial
************************************************************************/

QString DMXKingMAX::uniqueName(ushort line, bool input) const
{
QString devName;

if (realName().isEmpty() == false)
devName = realName();
else
devName = name();

if (input)
{
return QString("%1 - %2 %3 - (S/N: %4)").arg(devName, QObject::tr("DMX Input"), QString::number(line + 1), m_serialNumber);
}
else
{
return QString("%1 - %2 %3 - (S/N: %4)").arg(devName, QObject::tr("DMX Output"), QString::number(line + 1), m_serialNumber);
}
}

/************************************************************************
* Output
************************************************************************/

bool DMXKingMAX::writeUniverse(quint32 universe, quint32 output, const QByteArray &data, bool dataChanged)
{
Q_UNUSED(universe)

if (isOpen() == false)
{
qDebug() << "[DMXUSB] writeUniverse: device is not open!";
return false;
}

quint32 devLine = output - m_outputBaseLine;
if (devLine >= (quint32)outputsNumber())
return false;

if (m_outputLines[devLine].m_universeData.size() == 0)
{
m_outputLines[devLine].m_universeData.append(data);
m_outputLines[devLine].m_universeData.append(DMX_CHANNELS - data.size(), 0);
}

if (dataChanged)
m_outputLines[devLine].m_universeData.replace(0, data.size(), data);

return true;
}

/********************************************************************
* RDM
********************************************************************/

bool DMXKingMAX::supportRDM()
{
return true;
}

bool DMXKingMAX::sendRDMCommand(quint32 universe, quint32 line, uchar command, QVariantList params)
{
// TODO
Q_UNUSED(universe)
Q_UNUSED(line)
Q_UNUSED(command)
Q_UNUSED(params)

return false;
}

Loading

0 comments on commit c1c1507

Please sign in to comment.