diff --git a/plugins/dmxusb/src/CMakeLists.txt b/plugins/dmxusb/src/CMakeLists.txt index ae2f47d372..2336d2c278 100644 --- a/plugins/dmxusb/src/CMakeLists.txt +++ b/plugins/dmxusb/src/CMakeLists.txt @@ -114,6 +114,7 @@ target_sources(${module_name} PRIVATE enttecdmxusbpro.cpp enttecdmxusbpro.h stageprofi.cpp stageprofi.h vinceusbdmx512.cpp vinceusbdmx512.h + usbdmxlegacy.cpp usbdmxlegacy.h ) target_include_directories(${module_name} PRIVATE diff --git a/plugins/dmxusb/src/DMX_USB_ca_ES.ts b/plugins/dmxusb/src/DMX_USB_ca_ES.ts index 4628169dc3..35bd8dd26d 100644 --- a/plugins/dmxusb/src/DMX_USB_ca_ES.ts +++ b/plugins/dmxusb/src/DMX_USB_ca_ES.ts @@ -200,6 +200,7 @@ + Protocol Protocol @@ -208,6 +209,7 @@ + Output Sortida @@ -225,6 +227,7 @@ + Serial number Nombre de Sèrie diff --git a/plugins/dmxusb/src/DMX_USB_cz_CZ.ts b/plugins/dmxusb/src/DMX_USB_cz_CZ.ts index 50cd1d5b91..455e22a338 100644 --- a/plugins/dmxusb/src/DMX_USB_cz_CZ.ts +++ b/plugins/dmxusb/src/DMX_USB_cz_CZ.ts @@ -200,6 +200,7 @@ + Protocol Protokol @@ -208,6 +209,7 @@ + Output Výstup @@ -225,6 +227,7 @@ + Serial number Sériové číslo diff --git a/plugins/dmxusb/src/DMX_USB_de_DE.ts b/plugins/dmxusb/src/DMX_USB_de_DE.ts index d29b30a383..7227b3f59d 100644 --- a/plugins/dmxusb/src/DMX_USB_de_DE.ts +++ b/plugins/dmxusb/src/DMX_USB_de_DE.ts @@ -200,6 +200,7 @@ + Protocol Protokoll @@ -208,6 +209,7 @@ + Serial number Seriennummer @@ -216,6 +218,7 @@ + Output Ausgabe diff --git a/plugins/dmxusb/src/DMX_USB_es_ES.ts b/plugins/dmxusb/src/DMX_USB_es_ES.ts index 83e4953871..a15549ef68 100644 --- a/plugins/dmxusb/src/DMX_USB_es_ES.ts +++ b/plugins/dmxusb/src/DMX_USB_es_ES.ts @@ -200,6 +200,7 @@ + Protocol Protocolo @@ -208,6 +209,7 @@ + Serial number Número de Serie @@ -216,6 +218,7 @@ + Output Salida diff --git a/plugins/dmxusb/src/DMX_USB_fi_FI.ts b/plugins/dmxusb/src/DMX_USB_fi_FI.ts index 0d3dbeeceb..9b720ac535 100644 --- a/plugins/dmxusb/src/DMX_USB_fi_FI.ts +++ b/plugins/dmxusb/src/DMX_USB_fi_FI.ts @@ -200,6 +200,7 @@ + Protocol @@ -208,6 +209,7 @@ + Serial number @@ -216,6 +218,7 @@ + Output diff --git a/plugins/dmxusb/src/DMX_USB_fr_FR.ts b/plugins/dmxusb/src/DMX_USB_fr_FR.ts index 25a6708c31..66c2404a9a 100644 --- a/plugins/dmxusb/src/DMX_USB_fr_FR.ts +++ b/plugins/dmxusb/src/DMX_USB_fr_FR.ts @@ -201,6 +201,7 @@ NOTE : L'interface VCP FTDI n'est pas supportée par ce plugin. + Protocol Protocole @@ -209,6 +210,7 @@ NOTE : L'interface VCP FTDI n'est pas supportée par ce plugin. + Serial number N° de série @@ -217,6 +219,7 @@ NOTE : L'interface VCP FTDI n'est pas supportée par ce plugin. + Output Sortie diff --git a/plugins/dmxusb/src/DMX_USB_it_IT.ts b/plugins/dmxusb/src/DMX_USB_it_IT.ts index a670013f28..37b5785656 100644 --- a/plugins/dmxusb/src/DMX_USB_it_IT.ts +++ b/plugins/dmxusb/src/DMX_USB_it_IT.ts @@ -200,6 +200,7 @@ + Protocol Protocollo @@ -208,6 +209,7 @@ + Serial number Numero di serie @@ -216,6 +218,7 @@ + Output Uscita diff --git a/plugins/dmxusb/src/DMX_USB_ja_JP.ts b/plugins/dmxusb/src/DMX_USB_ja_JP.ts index b499750db7..d284c7275f 100644 --- a/plugins/dmxusb/src/DMX_USB_ja_JP.ts +++ b/plugins/dmxusb/src/DMX_USB_ja_JP.ts @@ -200,6 +200,7 @@ + Protocol Protocol @@ -208,6 +209,7 @@ + Output 出力 @@ -225,6 +227,7 @@ + Serial number シリアルナンバー diff --git a/plugins/dmxusb/src/DMX_USB_nl_NL.ts b/plugins/dmxusb/src/DMX_USB_nl_NL.ts index 655918252e..a90b0da089 100644 --- a/plugins/dmxusb/src/DMX_USB_nl_NL.ts +++ b/plugins/dmxusb/src/DMX_USB_nl_NL.ts @@ -200,6 +200,7 @@ + Protocol Protocol @@ -208,6 +209,7 @@ + Output Output @@ -225,6 +227,7 @@ + Serial number Serienummer diff --git a/plugins/dmxusb/src/DMX_USB_pt_BR.ts b/plugins/dmxusb/src/DMX_USB_pt_BR.ts index f7b885fad9..a0c2072422 100644 --- a/plugins/dmxusb/src/DMX_USB_pt_BR.ts +++ b/plugins/dmxusb/src/DMX_USB_pt_BR.ts @@ -200,6 +200,7 @@ + Protocol Protocolo @@ -208,6 +209,7 @@ + Serial number Número de série @@ -216,6 +218,7 @@ + Output Saída diff --git a/plugins/dmxusb/src/dmxusb.cpp b/plugins/dmxusb/src/dmxusb.cpp index 10c867e585..646879b652 100644 --- a/plugins/dmxusb/src/dmxusb.cpp +++ b/plugins/dmxusb/src/dmxusb.cpp @@ -152,7 +152,7 @@ QString DMXUSB::pluginInfo() str += tr("This plugin provides DMX output support for"); str += QString(" DMXKing ultraDMX range, Enttec DMX USB Pro, " "Enttec Open DMX USB, FTDI USB COM485 Plus1, " - "Vince USB-DMX512 "); + "Vince USB-DMX512, usbdmx.com (legacy) "); str += tr("and compatible devices."); str += QString("

"); diff --git a/plugins/dmxusb/src/dmxusbconfig.cpp b/plugins/dmxusb/src/dmxusbconfig.cpp index 8452ff6d48..2ef82ef081 100644 --- a/plugins/dmxusb/src/dmxusbconfig.cpp +++ b/plugins/dmxusb/src/dmxusbconfig.cpp @@ -151,6 +151,7 @@ QComboBox *DMXUSBConfig::createTypeCombo(DMXUSBWidget *widget) combo->addItem(QString("DMX4ALL"), DMXUSBWidget::DMX4ALL); combo->addItem(QString("Vince TX"), DMXUSBWidget::VinceTX); combo->addItem(QString("Eurolite"), DMXUSBWidget::Eurolite); + combo->addItem(QString("usbdmx.com (legacy)"), DMXUSBWidget::USBDMXLegacy); int index = combo->findData(widget->type()); combo->setCurrentIndex(index); diff --git a/plugins/dmxusb/src/dmxusbwidget.cpp b/plugins/dmxusb/src/dmxusbwidget.cpp index 85adb69c52..5ffcbdc63d 100644 --- a/plugins/dmxusb/src/dmxusbwidget.cpp +++ b/plugins/dmxusb/src/dmxusbwidget.cpp @@ -33,6 +33,7 @@ #endif #include "stageprofi.h" #include "vinceusbdmx512.h" +#include "usbdmxlegacy.h" #if defined(WIN32) || defined(Q_OS_WIN) #include @@ -168,6 +169,9 @@ QList DMXUSBWidget::widgets() widgetList << new EuroliteUSBDMXPro(iface, output_id++); break; #endif + case DMXUSBWidget::USBDMXLegacy: + widgetList << new UsbdmxLegacy(iface, output_id++); + break; default: case DMXUSBWidget::ProRXTX: widgetList << new EnttecDMXUSBPro(iface, output_id++, input_id++); diff --git a/plugins/dmxusb/src/dmxusbwidget.h b/plugins/dmxusb/src/dmxusbwidget.h index 3a95c7dbae..df4420d1ba 100644 --- a/plugins/dmxusb/src/dmxusbwidget.h +++ b/plugins/dmxusb/src/dmxusbwidget.h @@ -63,7 +63,8 @@ class DMXUSBWidget UltraPro, //! DMXKing Ultra Pro widget using 2 TX and 1RX ports DMX4ALL, //! DMX4ALL widget (only TX) VinceTX, //! Vince USB-DMX512 widget using the TX side of the dongle - Eurolite //! Eurolite USB DMX512 Pro widget + Eurolite, //! Eurolite USB DMX512 Pro widget + USBDMXLegacy //! usbdmx.com legacy interface }; /** The possible features of a line */ diff --git a/plugins/dmxusb/src/enttecdmxusbpro.h b/plugins/dmxusb/src/enttecdmxusbpro.h index b4fee3b7c7..9a113fa42a 100644 --- a/plugins/dmxusb/src/enttecdmxusbpro.h +++ b/plugins/dmxusb/src/enttecdmxusbpro.h @@ -47,7 +47,7 @@ class EnttecDMXUSBPro : public QThread, public DMXUSBWidget virtual ~EnttecDMXUSBPro(); /** @reimp */ - Type type() const; + DMXUSBWidget::Type type() const; // DMXking port flags enum PortType diff --git a/plugins/dmxusb/src/src.pro b/plugins/dmxusb/src/src.pro index aae7b4f956..b37703785d 100644 --- a/plugins/dmxusb/src/src.pro +++ b/plugins/dmxusb/src/src.pro @@ -112,6 +112,7 @@ HEADERS += dmxusb.h \ dmxusbopenrx.h \ stageprofi.h \ vinceusbdmx512.h \ + usbdmxlegacy.h \ dmxinterface.h unix|macx: HEADERS += nanodmx.h euroliteusbdmxpro.h @@ -127,6 +128,7 @@ SOURCES += dmxinterface.cpp \ enttecdmxusbopen.cpp \ dmxusbopenrx.cpp \ stageprofi.cpp \ + usbdmxlegacy.cpp \ vinceusbdmx512.cpp INCLUDEPATH += ../../midi/src/common diff --git a/plugins/dmxusb/src/usbdmxlegacy.cpp b/plugins/dmxusb/src/usbdmxlegacy.cpp new file mode 100644 index 0000000000..25c3809e3e --- /dev/null +++ b/plugins/dmxusb/src/usbdmxlegacy.cpp @@ -0,0 +1,182 @@ +/* + Q Light Controller Plus + usbdmxlegacy.cpp + + Adds support for the "USB/DMX Interface" (usbdmx.com) custom protocol. + + Protocol reference (V1.4): + - TX ON : 0x44 + - TX OFF : 0x46 + - SET VALUE : 0x48 | 0x49 (high address bit), , + - SET LAST TX : 0x4E | 0x4F (high address bit), (response 0xCE) + - Start code defaults to 0; we don't change it here. + + Addressing in the spec is 0-based (0x000 -> channel 1). In QLC+ data + buffer the first slot is channel 1 at index 0, so we can pass the index + as-is. +*/ + +#include "usbdmxlegacy.h" + +#define USBDMXLEGACY_DEFAULT_FREQUENCY 40 +#define USBDMXLEGACY_CMD_TX_ON 0x44 +#define USBDMXLEGACY_CMD_TX_OFF 0x46 +#define USBDMXLEGACY_CMD_SET_VALUE 0x48 +#define USBDMXLEGACY_CMD_SET_VALUE_HIGH_ADDRESS_BIT 0x49 +#define USBDMXLEGACY_CMD_SET_LAST_TX 0x4E +#define USBDMXLEGACY_CMD_SET_LAST_TX_HIGH_ADDRESS_BIT 0x4F + + +UsbdmxLegacy::UsbdmxLegacy(DMXInterface *iface, quint32 outputLine) + : DMXUSBWidget(iface, outputLine, USBDMXLEGACY_DEFAULT_FREQUENCY) +{ + // configure one output port + QList ports; + ports << (DMXUSBWidget::DMX | DMXUSBWidget::Output); + setPortsMapping(ports); +} + +UsbdmxLegacy::~UsbdmxLegacy() +{ + close(); +} + +DMXUSBWidget::Type UsbdmxLegacy::type() const +{ + return DMXUSBWidget::USBDMXLegacy; +} + +/************************************************************************* + * Open & Close + *************************************************************************/ + +bool UsbdmxLegacy::open(quint32 line, bool input) +{ + Q_UNUSED(line) + Q_UNUSED(input) + + if (DMXUSBWidget::open() == false) + return false; + + // Ensure TX is off, set last channel, then enable TX. + cmdTxOff(); + cmdSetLastChannel(DMX_CHANNELS - 1); + return cmdTxOn(); +} + +bool UsbdmxLegacy::close(quint32 line, bool input) +{ + Q_UNUSED(line) + Q_UNUSED(input) + + cmdTxOff(); + return DMXUSBWidget::close(); +} + +/************************************************************************* + * Outputs + *************************************************************************/ + +bool UsbdmxLegacy::writeUniverse(quint32 universe, quint32 output, const QByteArray& data, bool dataChanged) +{ + Q_UNUSED(universe) + Q_UNUSED(output) + + if (!isOpen()) + return false; + + // Initialize local cache once. + if (m_portsInfo[0].m_universeData.size() == 0) + { + m_portsInfo[0].m_universeData = QByteArray(DMX_CHANNELS, 0); + + // Make sure the device does not waste time sending beyond our bounds + cmdSetLastChannel(DMX_CHANNELS - 1); + cmdTxOn(); + } + + if (!dataChanged) + return true; + + bool result = true; + // Update cache and send only diffs as SET VALUE commands, as per spec. + for (int i = 0; i < qMin(DMX_CHANNELS, data.size()); i++) + { + const uchar newVal = static_cast(data.at(i)); + uchar &oldVal = reinterpret_cast(m_portsInfo[0].m_universeData[i]); + + if (newVal == oldVal) + continue; + + if (!cmdSetChannelValue(i, newVal)) + { + result = false; + continue; + } + + oldVal = newVal; + } + + return result; +} + +/************************************************************************* + * Info + *************************************************************************/ + +QString UsbdmxLegacy::additionalInfo() const +{ + QString info; + info += QString("

"); + info += QString("%1: usbdmx.com (legacy) (%3)").arg(QObject::tr("Protocol")) + .arg(QObject::tr("Output")); + info += QString("
"); + info += QString("%1: %2").arg(QObject::tr("Serial number")) + .arg(serial()); + info += QString("

"); + return info; +} + +/************************************************************************* + * Command helpers + *************************************************************************/ + +bool UsbdmxLegacy::cmdTxOn() +{ + QByteArray msg; + msg.append(char(USBDMXLEGACY_CMD_TX_ON)); + return iface()->write(msg); +} + +bool UsbdmxLegacy::cmdTxOff() +{ + QByteArray msg; + msg.append(char(USBDMXLEGACY_CMD_TX_OFF)); + return iface()->write(msg); +} + +bool UsbdmxLegacy::cmdSetLastChannel(int lastIndex) +{ + if (lastIndex < 0) + lastIndex = 0; + + if (lastIndex > 511) + lastIndex = 511; + + QByteArray msg; + msg.append(char(hiBit(lastIndex) ? USBDMXLEGACY_CMD_SET_LAST_TX_HIGH_ADDRESS_BIT : USBDMXLEGACY_CMD_SET_LAST_TX)); + msg.append(char(lo(lastIndex))); + return iface()->write(msg); +} + +bool UsbdmxLegacy::cmdSetChannelValue(int index, uchar value) +{ + if (index < 0 || index > 511) + return false; + + QByteArray msg; + msg.append(char(hiBit(index) ? USBDMXLEGACY_CMD_SET_VALUE_HIGH_ADDRESS_BIT : USBDMXLEGACY_CMD_SET_VALUE)); + msg.append(char(lo(index))); + msg.append(char(value)); + return iface()->write(msg); +} diff --git a/plugins/dmxusb/src/usbdmxlegacy.h b/plugins/dmxusb/src/usbdmxlegacy.h new file mode 100644 index 0000000000..cdaaec3bca --- /dev/null +++ b/plugins/dmxusb/src/usbdmxlegacy.h @@ -0,0 +1,73 @@ +/* + Q Light Controller Plus + usbdmxlegacy.h + + Adds support for the "USB/DMX Interface" (usbdmx.com) custom protocol + as documented in "USB/DMX Interface - Command Specification (V 1.4)". + + Copyright (C) 2025 + + 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 +*/ + +#ifndef USBDMXLEGACY_H +#define USBDMXLEGACY_H + +#include "dmxusbwidget.h" + +/** + * Implements the usbdmx.com interface protocol (Ben Suffolk, v1.4 PDF). + * The device is FTDI-based and maintains its own DMX timing once TX is ON. + * We only push per-channel updates and set the "last TX channel" bound. + */ +class UsbdmxLegacy : public QThread, public DMXUSBWidget +{ + Q_OBJECT + + /************************************************************************ + * Initialization + ************************************************************************/ +public: + UsbdmxLegacy(DMXInterface *iface, quint32 outputLine); + virtual ~UsbdmxLegacy(); + + /** @reimp */ + DMXUSBWidget::Type type() const override; + + /************************************************************************* + * Open & Close + *************************************************************************/ + /** @reimp */ + bool open(quint32 line = 0, bool input = false) override; + + /** @reimp */ + bool close(quint32 line = 0, bool input = false) override; + + /************************************************************************* + * Outputs + *************************************************************************/ + /** @reimp */ + bool writeUniverse(quint32 universe, quint32 output, const QByteArray& data, bool dataChanged) override; + + /************************************************************************* + * Info + *************************************************************************/ + /** @reimp */ + QString additionalInfo() const override; + +private: + // usbdmx.com command helpers + bool cmdTxOn(); + bool cmdTxOff(); + bool cmdSetLastChannel(int lastIndex); // argument in [0..511] + bool cmdSetChannelValue(int index, uchar value); // index in [0..511] + + inline uchar lo(int index) const { return static_cast(index & 0xFF); } + inline bool hiBit(int index) const { return (index & 0x100) != 0; } +}; + +#endif // USBDMXLEGACY_H