Initial DMX (Enttec OpenDMX USB) support and serial_port improvements

* Support DMX RGB lights (PAR lights, spotlights, wash lights, etc)
  * Configurable R/G/B channel and Brightness/Master channel
  * Add configurable parameters to serial_port needed to configure a port for DMX
  * Add DMX tab to settings
This commit is contained in:
Adam Honse 2023-05-06 08:06:19 +00:00
parent 8b4b2bacbc
commit 81aaf67ff0
14 changed files with 1318 additions and 48 deletions

View file

@ -0,0 +1,142 @@
#include "Detector.h"
#include "RGBController.h"
#include "RGBController_DMX.h"
#include "SettingsManager.h"
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <fstream>
#include <iostream>
#include <string>
/******************************************************************************************\
* *
* DetectDMXControllers *
* *
* Detect devices supported by the DMX driver *
* *
\******************************************************************************************/
void DetectDMXControllers()
{
json dmx_settings;
std::vector<std::vector<DMXDevice>> device_lists;
DMXDevice dev;
/*-------------------------------------------------*\
| Get DMX settings from settings manager |
\*-------------------------------------------------*/
dmx_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("DMXDevices");
/*-------------------------------------------------*\
| If the DMX settings contains devices, process |
\*-------------------------------------------------*/
if(dmx_settings.contains("devices"))
{
for(unsigned int device_idx = 0; device_idx < dmx_settings["devices"].size(); device_idx++)
{
/*-------------------------------------------------*\
| Clear DMX device data |
\*-------------------------------------------------*/
dev.name = "";
dev.keepalive_time = 0;
if(dmx_settings["devices"][device_idx].contains("name"))
{
dev.name = dmx_settings["devices"][device_idx]["name"];
}
if(dmx_settings["devices"][device_idx].contains("port"))
{
dev.port = dmx_settings["devices"][device_idx]["port"];
}
if(dmx_settings["devices"][device_idx].contains("keepalive_time"))
{
dev.keepalive_time = dmx_settings["devices"][device_idx]["keepalive_time"];
}
if(dmx_settings["devices"][device_idx].contains("red_channel"))
{
dev.red_channel = dmx_settings["devices"][device_idx]["red_channel"];
}
if(dmx_settings["devices"][device_idx].contains("green_channel"))
{
dev.green_channel = dmx_settings["devices"][device_idx]["green_channel"];
}
if(dmx_settings["devices"][device_idx].contains("blue_channel"))
{
dev.blue_channel = dmx_settings["devices"][device_idx]["blue_channel"];
}
if(dmx_settings["devices"][device_idx].contains("brightness_channel"))
{
dev.brightness_channel = dmx_settings["devices"][device_idx]["brightness_channel"];
}
/*---------------------------------------------------------*\
| Determine whether to create a new list or add this device |
| to an existing list. A device is added to an existing |
| list if both devices share one or more universes for the |
| same output destination |
\*---------------------------------------------------------*/
bool device_added_to_existing_list = false;
/*---------------------------------------------------------*\
| Track grouping for all controllers. |
\*---------------------------------------------------------*/
for(unsigned int list_idx = 0; list_idx < device_lists.size(); list_idx++)
{
for(unsigned int device_idx = 0; device_idx < device_lists[list_idx].size(); device_idx++)
{
/*---------------------------------------------------------*\
| Check if the port used by this new device is the same as |
| in the existing device. If so, add the new device to the |
| existing list. |
\*---------------------------------------------------------*/
if(1)
{
device_lists[list_idx].push_back(dev);
device_added_to_existing_list = true;
break;
}
}
if(device_added_to_existing_list)
{
break;
}
}
/*---------------------------------------------------------*\
| If the device did not overlap with existing devices, |
| create a new list for it |
\*---------------------------------------------------------*/
if(!device_added_to_existing_list)
{
std::vector<DMXDevice> new_list;
new_list.push_back(dev);
device_lists.push_back(new_list);
}
}
for(unsigned int list_idx = 0; list_idx < device_lists.size(); list_idx++)
{
RGBController_DMX* rgb_controller;
rgb_controller = new RGBController_DMX(device_lists[list_idx]);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
}
} /* DetectDMXControllers() */
REGISTER_DETECTOR("DMX", DetectDMXControllers);

View file

@ -0,0 +1,233 @@
/*-----------------------------------------*\
| RGBController_DMX.cpp |
| |
| Generic RGB Interface for OpenAuraSDK |
| DMX interface |
| |
| Adam Honse (CalcProgrammer1) 4/30/2023 |
\*-----------------------------------------*/
#include "RGBController_DMX.h"
#include <math.h>
#include "LogManager.h"
using namespace std::chrono_literals;
/**------------------------------------------------------------------*\
@name DMX Devices
@category LEDStrip
@type Serial
@save :x:
@direct :white_check_mark:
@effects :x:
@detectors DetectDMXControllers
@comment
\*-------------------------------------------------------------------*/
RGBController_DMX::RGBController_DMX(std::vector<DMXDevice> device_list)
{
devices = device_list;
name = "DMX Device Group";
type = DEVICE_TYPE_LEDSTRIP;
description = "DMX Device";
location = "DMX: " + devices[0].port;
/*-----------------------------------------*\
| If this controller only represents a |
| single device, use the device name for the|
| controller name |
\*-----------------------------------------*/
if(devices.size() == 1)
{
name = devices[0].name;
}
/*-----------------------------------------*\
| Open OpenDMX port |
\*-----------------------------------------*/
port = new serial_port(devices[0].port.c_str(), 250000, SERIAL_PORT_PARITY_NONE, SERIAL_PORT_SIZE_8, SERIAL_PORT_STOP_BITS_2, false);
/*-----------------------------------------*\
| Clear the RTS signal, which enables the |
| OpenDMX RS-485 drive enable |
\*-----------------------------------------*/
port->serial_set_rts(false);
/*-----------------------------------------*\
| Set up modes |
\*-----------------------------------------*/
mode Direct;
Direct.name = "Direct";
Direct.value = 0;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Direct.color_mode = MODE_COLORS_PER_LED;
Direct.brightness = 255;
Direct.brightness_min = 0;
Direct.brightness_max = 255;
modes.push_back(Direct);
keepalive_delay = 0ms;
SetupZones();
for (std::size_t device_idx = 0; device_idx < devices.size(); device_idx++)
{
/*-----------------------------------------*\
| Update keepalive delay |
\*-----------------------------------------*/
if(devices[device_idx].keepalive_time > 0)
{
if(keepalive_delay.count() == 0 || keepalive_delay.count() > devices[device_idx].keepalive_time)
{
keepalive_delay = std::chrono::milliseconds(devices[device_idx].keepalive_time);
}
}
}
if(keepalive_delay.count() > 0)
{
keepalive_thread_run = 1;
keepalive_thread = new std::thread(&RGBController_DMX::KeepaliveThreadFunction, this);
}
else
{
keepalive_thread_run = 0;
keepalive_thread = nullptr;
}
}
RGBController_DMX::~RGBController_DMX()
{
if(keepalive_thread != nullptr)
{
keepalive_thread_run = 0;
keepalive_thread->join();
delete keepalive_thread;
}
/*---------------------------------------------------------*\
| Delete the matrix map |
\*---------------------------------------------------------*/
for(unsigned int zone_index = 0; zone_index < zones.size(); zone_index++)
{
if(zones[zone_index].matrix_map != NULL)
{
if(zones[zone_index].matrix_map->map != NULL)
{
delete zones[zone_index].matrix_map->map;
}
delete zones[zone_index].matrix_map;
}
}
}
void RGBController_DMX::SetupZones()
{
/*-----------------------------------------*\
| Add Zones |
\*-----------------------------------------*/
for(std::size_t zone_idx = 0; zone_idx < devices.size(); zone_idx++)
{
zone led_zone;
led_zone.name = devices[zone_idx].name;
led_zone.type = ZONE_TYPE_SINGLE;
led_zone.leds_min = 1;
led_zone.leds_max = 1;
led_zone.leds_count = 1;
led_zone.matrix_map = NULL;
zones.push_back(led_zone);
}
/*-----------------------------------------*\
| Add LEDs |
\*-----------------------------------------*/
for(std::size_t zone_idx = 0; zone_idx < zones.size(); zone_idx++)
{
for(std::size_t led_idx = 0; led_idx < zones[zone_idx].leds_count; led_idx++)
{
led new_led;
new_led.name = zones[zone_idx].name + " LED ";
new_led.name.append(std::to_string(led_idx));
leds.push_back(new_led);
}
}
SetupColors();
}
void RGBController_DMX::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_DMX::DeviceUpdateLEDs()
{
int color_idx = 0;
last_update_time = std::chrono::steady_clock::now();
unsigned char dmx_data[513];
memset(dmx_data, 0, sizeof(dmx_data));
for(unsigned int device_idx = 0; device_idx < devices.size(); device_idx++)
{
if(devices[device_idx].brightness_channel > 0)
{
dmx_data[devices[device_idx].brightness_channel] = modes[0].brightness;
}
if(devices[device_idx].red_channel > 0)
{
dmx_data[devices[device_idx].red_channel] = RGBGetRValue(colors[device_idx]);
}
if(devices[device_idx].green_channel > 0)
{
dmx_data[devices[device_idx].green_channel] = RGBGetGValue(colors[device_idx]);
}
if(devices[device_idx].blue_channel > 0)
{
dmx_data[devices[device_idx].blue_channel] = RGBGetBValue(colors[device_idx]);
}
}
port->serial_break();
port->serial_write((char*)&dmx_data, sizeof(dmx_data));
}
void RGBController_DMX::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_DMX::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_DMX::DeviceUpdateMode()
{
}
void RGBController_DMX::KeepaliveThreadFunction()
{
while(keepalive_thread_run.load())
{
if((std::chrono::steady_clock::now() - last_update_time) > ( keepalive_delay * 0.95f ) )
{
UpdateLEDs();
}
std::this_thread::sleep_for(keepalive_delay / 2);
}
}

View file

@ -0,0 +1,52 @@
/*-----------------------------------------*\
| RGBController_DMX.h |
| |
| Generic RGB Interface for OpenAuraSDK |
| DMX interface |
| |
| Adam Honse (CalcProgrammer1) 4/30/2023 |
\*-----------------------------------------*/
#pragma once
#include "RGBController.h"
#include "serial_port.h"
#include <chrono>
#include <thread>
struct DMXDevice
{
std::string name;
std::string port;
unsigned int keepalive_time;
unsigned int red_channel;
unsigned int green_channel;
unsigned int blue_channel;
unsigned int brightness_channel;
};
class RGBController_DMX : public RGBController
{
public:
RGBController_DMX(std::vector<DMXDevice> device_list);
~RGBController_DMX();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
void KeepaliveThreadFunction();
private:
std::vector<DMXDevice> devices;
serial_port * port;
std::thread * keepalive_thread;
std::atomic<bool> keepalive_thread_run;
std::chrono::milliseconds keepalive_delay;
std::chrono::time_point<std::chrono::steady_clock> last_update_time;
};

View file

@ -108,6 +108,7 @@ INCLUDEPATH +=
Controllers/CryorigH7QuadLumiController/ \
Controllers/DasKeyboardController/ \
Controllers/DebugController/ \
Controllers/DMXController/ \
Controllers/DuckyKeyboardController/ \
Controllers/DygmaRaiseController/ \
Controllers/E131Controller/ \
@ -240,6 +241,8 @@ HEADERS +=
qt/OpenRGBSystemInfoPage.h \
qt/OpenRGBThemeManager.h \
qt/OpenRGBZoneResizeDialog.h \
qt/OpenRGBDMXSettingsPage/OpenRGBDMXSettingsEntry.h \
qt/OpenRGBDMXSettingsPage/OpenRGBDMXSettingsPage.h \
qt/OpenRGBE131SettingsPage/OpenRGBE131SettingsEntry.h \
qt/OpenRGBE131SettingsPage/OpenRGBE131SettingsPage.h \
qt/OpenRGBLIFXSettingsPage/OpenRGBLIFXSettingsEntry.h \
@ -393,6 +396,7 @@ HEADERS +=
Controllers/DarkProject/RGBController_DarkProjectKeyboard.h \
Controllers/DasKeyboardController/DasKeyboardController.h \
Controllers/DasKeyboardController/RGBController_DasKeyboard.h \
Controllers/DMXController/RGBController_DMX.h \
Controllers/DuckyKeyboardController/DuckyKeyboardController.h \
Controllers/DuckyKeyboardController/RGBController_DuckyKeyboard.h \
Controllers/DygmaRaiseController/DygmaRaiseController.h \
@ -799,6 +803,8 @@ SOURCES +=
qt/QTooltipedSlider.cpp \
qt/TabLabel.cpp \
qt/hsv.cpp \
qt/OpenRGBDMXSettingsPage/OpenRGBDMXSettingsEntry.cpp \
qt/OpenRGBDMXSettingsPage/OpenRGBDMXSettingsPage.cpp \
qt/OpenRGBE131SettingsPage/OpenRGBE131SettingsEntry.cpp \
qt/OpenRGBE131SettingsPage/OpenRGBE131SettingsPage.cpp \
qt/OpenRGBLIFXSettingsPage/OpenRGBLIFXSettingsEntry.cpp \
@ -981,6 +987,8 @@ SOURCES +=
Controllers/DasKeyboardController/DasKeyboardController.cpp \
Controllers/DasKeyboardController/DasKeyboardControllerDetect.cpp \
Controllers/DasKeyboardController/RGBController_DasKeyboard.cpp \
Controllers/DMXController/DMXControllerDetect.cpp \
Controllers/DMXController/RGBController_DMX.cpp \
Controllers/DuckyKeyboardController/DuckyKeyboardController.cpp \
Controllers/DuckyKeyboardController/DuckyKeyboardControllerDetect.cpp \
Controllers/DuckyKeyboardController/RGBController_DuckyKeyboard.cpp \
@ -1426,6 +1434,8 @@ FORMS +=
qt/OpenRGBSupportedDevicesPage.ui \
qt/OpenRGBSystemInfoPage.ui \
qt/OpenRGBZoneResizeDialog.ui \
qt/OpenRGBDMXSettingsPage/OpenRGBDMXSettingsEntry.ui \
qt/OpenRGBDMXSettingsPage/OpenRGBDMXSettingsPage.ui \
qt/OpenRGBE131SettingsPage/OpenRGBE131SettingsEntry.ui \
qt/OpenRGBE131SettingsPage/OpenRGBE131SettingsPage.ui \
qt/OpenRGBElgatoKeyLightSettingsPage/OpenRGBElgatoKeyLightSettingsEntry.ui \

View file

@ -0,0 +1,29 @@
#include "OpenRGBDMXSettingsEntry.h"
#include "ui_OpenRGBDMXSettingsEntry.h"
using namespace Ui;
OpenRGBDMXSettingsEntry::OpenRGBDMXSettingsEntry(QWidget *parent) :
QWidget(parent),
ui(new Ui::OpenRGBDMXSettingsEntryUi)
{
ui->setupUi(this);
}
OpenRGBDMXSettingsEntry::~OpenRGBDMXSettingsEntry()
{
delete ui;
}
void OpenRGBDMXSettingsEntry::changeEvent(QEvent *event)
{
if(event->type() == QEvent::LanguageChange)
{
ui->retranslateUi(this);
}
}
void Ui::OpenRGBDMXSettingsEntry::on_TypeComboBox_currentIndexChanged(int index)
{
}

View file

@ -0,0 +1,25 @@
#ifndef OPENRGBDMXSETTINGSENTRY_H
#define OPENRGBDMXSETTINGSENTRY_H
#include "ui_OpenRGBDMXSettingsEntry.h"
#include <QWidget>
namespace Ui {
class OpenRGBDMXSettingsEntry;
}
class Ui::OpenRGBDMXSettingsEntry : public QWidget
{
Q_OBJECT
public:
explicit OpenRGBDMXSettingsEntry(QWidget *parent = nullptr);
~OpenRGBDMXSettingsEntry();
Ui::OpenRGBDMXSettingsEntryUi *ui;
private slots:
void changeEvent(QEvent *event);
void on_TypeComboBox_currentIndexChanged(int index);
};
#endif // OPENRGBDMXSETTINGSENTRY_H

View file

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OpenRGBDMXSettingsEntryUi</class>
<widget class="QWidget" name="OpenRGBDMXSettingsEntryUi">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>531</width>
<height>199</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>E131 settings entry</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string/>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="4" column="4">
<widget class="QLabel" name="BrightnessLabel">
<property name="text">
<string>Brightness Channel:</string>
</property>
</widget>
</item>
<item row="4" column="5">
<widget class="QLineEdit" name="BrightnessEdit"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Blue Channel:</string>
</property>
</widget>
</item>
<item row="3" column="5">
<widget class="QLineEdit" name="GreenEdit"/>
</item>
<item row="3" column="3">
<widget class="QLineEdit" name="RedEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="NameLabel">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item row="3" column="4">
<widget class="QLabel" name="GreenLabel">
<property name="text">
<string>Green Channel:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="RedLabel">
<property name="text">
<string>Red Channel:</string>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QLineEdit" name="BlueEdit"/>
</item>
<item row="1" column="3">
<widget class="QLineEdit" name="NameEdit"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="KeepaliveTimeLabel">
<property name="text">
<string>Keepalive Time:</string>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QLineEdit" name="KeepaliveTimeEdit"/>
</item>
<item row="1" column="4">
<widget class="QLabel" name="PortLabel">
<property name="text">
<string>Port:</string>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="QLineEdit" name="PortEdit"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>NameEdit</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,160 @@
#include "OpenRGBDMXSettingsPage.h"
#include "ui_OpenRGBDMXSettingsPage.h"
#include "ResourceManager.h"
using namespace Ui;
OpenRGBDMXSettingsPage::OpenRGBDMXSettingsPage(QWidget *parent) :
QWidget(parent),
ui(new Ui::OpenRGBDMXSettingsPageUi)
{
ui->setupUi(this);
json dmx_settings;
/*-------------------------------------------------*\
| Get DMX settings from settings manager |
\*-------------------------------------------------*/
dmx_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("DMXDevices");
/*-------------------------------------------------*\
| If the DMX settings contains devices, process |
\*-------------------------------------------------*/
if(dmx_settings.contains("devices"))
{
for(unsigned int device_idx = 0; device_idx < dmx_settings["devices"].size(); device_idx++)
{
OpenRGBDMXSettingsEntry* entry = new OpenRGBDMXSettingsEntry;
if(dmx_settings["devices"][device_idx].contains("name"))
{
entry->ui->NameEdit->setText(QString::fromStdString(dmx_settings["devices"][device_idx]["name"]));
}
if(dmx_settings["devices"][device_idx].contains("port"))
{
entry->ui->PortEdit->setText(QString::fromStdString(dmx_settings["devices"][device_idx]["port"]));
}
if(dmx_settings["devices"][device_idx].contains("red_channel"))
{
entry->ui->RedEdit->setText(QString::number((int)dmx_settings["devices"][device_idx]["red_channel"]));
}
if(dmx_settings["devices"][device_idx].contains("green_channel"))
{
entry->ui->GreenEdit->setText(QString::number((int)dmx_settings["devices"][device_idx]["green_channel"]));
}
if(dmx_settings["devices"][device_idx].contains("blue_channel"))
{
entry->ui->BlueEdit->setText(QString::number((int)dmx_settings["devices"][device_idx]["blue_channel"]));
}
if(dmx_settings["devices"][device_idx].contains("brightness_channel"))
{
entry->ui->BrightnessEdit->setText(QString::number((int)dmx_settings["devices"][device_idx]["brightness_channel"]));
}
if(dmx_settings["devices"][device_idx].contains("keepalive_time"))
{
entry->ui->KeepaliveTimeEdit->setText(QString::number((int)dmx_settings["devices"][device_idx]["keepalive_time"]));
}
entries.push_back(entry);
QListWidgetItem* item = new QListWidgetItem;
item->setSizeHint(entry->sizeHint());
ui->DMXDeviceList->addItem(item);
ui->DMXDeviceList->setItemWidget(item, entry);
ui->DMXDeviceList->show();
}
}
}
OpenRGBDMXSettingsPage::~OpenRGBDMXSettingsPage()
{
delete ui;
}
void OpenRGBDMXSettingsPage::changeEvent(QEvent *event)
{
if(event->type() == QEvent::LanguageChange)
{
ui->retranslateUi(this);
}
}
void Ui::OpenRGBDMXSettingsPage::on_AddDMXDeviceButton_clicked()
{
OpenRGBDMXSettingsEntry* entry = new OpenRGBDMXSettingsEntry;
entries.push_back(entry);
QListWidgetItem* item = new QListWidgetItem;
item->setSizeHint(entry->sizeHint());
ui->DMXDeviceList->addItem(item);
ui->DMXDeviceList->setItemWidget(item, entry);
ui->DMXDeviceList->show();
}
void Ui::OpenRGBDMXSettingsPage::on_RemoveDMXDeviceButton_clicked()
{
int cur_row = ui->DMXDeviceList->currentRow();
if(cur_row < 0)
{
return;
}
QListWidgetItem* item = ui->DMXDeviceList->takeItem(cur_row);
ui->DMXDeviceList->removeItemWidget(item);
delete item;
delete entries[cur_row];
entries.erase(entries.begin() + cur_row);
}
void Ui::OpenRGBDMXSettingsPage::on_SaveDMXConfigurationButton_clicked()
{
json dmx_settings;
/*-------------------------------------------------*\
| Get DMX settings from settings manager |
\*-------------------------------------------------*/
dmx_settings = ResourceManager::get()->GetSettingsManager()->GetSettings("DMXDevices");
dmx_settings["devices"].clear();
for(unsigned int device_idx = 0; device_idx < entries.size(); device_idx++)
{
/*-------------------------------------------------*\
| Required parameters |
\*-------------------------------------------------*/
dmx_settings["devices"][device_idx]["name"] = entries[device_idx]->ui->NameEdit->text().toStdString();
dmx_settings["devices"][device_idx]["port"] = entries[device_idx]->ui->PortEdit->text().toStdString();
dmx_settings["devices"][device_idx]["red_channel"] = entries[device_idx]->ui->RedEdit->text().toUInt();
dmx_settings["devices"][device_idx]["green_channel"] = entries[device_idx]->ui->GreenEdit->text().toUInt();
dmx_settings["devices"][device_idx]["blue_channel"] = entries[device_idx]->ui->BlueEdit->text().toUInt();
/*-------------------------------------------------*\
| Optional parameters |
\*-------------------------------------------------*/
if(entries[device_idx]->ui->BrightnessEdit->text() != "")
{
dmx_settings["devices"][device_idx]["brightness_channel"] = entries[device_idx]->ui->BrightnessEdit->text().toUInt();
}
if(entries[device_idx]->ui->KeepaliveTimeEdit->text() != "")
{
dmx_settings["devices"][device_idx]["keepalive_time"] = entries[device_idx]->ui->KeepaliveTimeEdit->text().toUInt();
}
}
ResourceManager::get()->GetSettingsManager()->SetSettings("DMXDevices", dmx_settings);
ResourceManager::get()->GetSettingsManager()->SaveSettings();
}

View file

@ -0,0 +1,35 @@
#ifndef OPENRGBDMXSETTINGSPAGE_H
#define OPENRGBDMXSETTINGSPAGE_H
#include "ui_OpenRGBDMXSettingsPage.h"
#include <QWidget>
#include "OpenRGBDMXSettingsEntry.h"
namespace Ui {
class OpenRGBDMXSettingsPage;
}
class Ui::OpenRGBDMXSettingsPage : public QWidget
{
Q_OBJECT
public:
explicit OpenRGBDMXSettingsPage(QWidget *parent = nullptr);
~OpenRGBDMXSettingsPage();
private slots:
void changeEvent(QEvent *event);
void on_AddDMXDeviceButton_clicked();
void on_RemoveDMXDeviceButton_clicked();
void on_SaveDMXConfigurationButton_clicked();
private:
Ui::OpenRGBDMXSettingsPageUi* ui;
std::vector<OpenRGBDMXSettingsEntry*> entries;
};
#endif // OPENRGBDMXSETTINGSPAGE_H

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OpenRGBDMXSettingsPageUi</class>
<widget class="QWidget" name="OpenRGBDMXSettingsPageUi">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>E131 settings page</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QPushButton" name="AddDMXDeviceButton">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="RemoveDMXDeviceButton">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="SaveDMXConfigurationButton">
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<widget class="QListWidget" name="DMXDeviceList">
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -432,6 +432,11 @@ OpenRGBDialog2::OpenRGBDialog2(QWidget *parent) : QMainWindow(parent), ui(new Op
\*-----------------------------------------------------*/
AddPluginsPage();
/*-----------------------------------------------------*\
| Add the DMX settings page |
\*-----------------------------------------------------*/
AddDMXSettingsPage();
/*-----------------------------------------------------*\
| Add the E1.31 settings page |
\*-----------------------------------------------------*/
@ -757,6 +762,34 @@ void OpenRGBDialog2::AddSettingsPage()
connect(this, SIGNAL(ProfileListChanged()), SettingsPage, SLOT(UpdateProfiles()));
}
void OpenRGBDialog2::AddDMXSettingsPage()
{
/*-----------------------------------------------------*\
| Create the Settings page |
\*-----------------------------------------------------*/
DMXSettingsPage = new OpenRGBDMXSettingsPage();
ui->SettingsTabBar->addTab(DMXSettingsPage, "");
QString SettingsLabelString;
if(OpenRGBThemeManager::IsDarkTheme())
{
SettingsLabelString = "serial_dark.png";
}
else
{
SettingsLabelString = "serial.png";
}
/*-----------------------------------------------------*\
| Create the tab label |
\*-----------------------------------------------------*/
TabLabel* SettingsTabLabel = new TabLabel(SettingsLabelString, tr("DMX Devices"), (char *)"DMX Devices", (char *)context);
ui->SettingsTabBar->tabBar()->setTabButton(ui->SettingsTabBar->tabBar()->count() - 1, QTabBar::LeftSide, SettingsTabLabel);
}
void OpenRGBDialog2::AddE131SettingsPage()
{
/*-----------------------------------------------------*\

View file

@ -9,6 +9,7 @@
#include "OpenRGBSystemInfoPage.h"
#include "OpenRGBSupportedDevicesPage.h"
#include "OpenRGBSettingsPage.h"
#include "OpenRGBDMXSettingsPage/OpenRGBDMXSettingsPage.h"
#include "OpenRGBE131SettingsPage/OpenRGBE131SettingsPage.h"
#include "OpenRGBElgatoKeyLightSettingsPage/OpenRGBElgatoKeyLightSettingsPage.h"
#include "OpenRGBKasaSmartSettingsPage/OpenRGBKasaSmartSettingsPage.h"
@ -84,6 +85,7 @@ private:
OpenRGBSoftwareInfoPage *SoftInfoPage;
OpenRGBSupportedDevicesPage *SupportedPage;
OpenRGBSettingsPage *SettingsPage;
OpenRGBDMXSettingsPage *DMXSettingsPage;
OpenRGBE131SettingsPage *E131SettingsPage;
OpenRGBElgatoKeyLightSettingsPage *ElgatoKeyLightSettingsPage;
OpenRGBKasaSmartSettingsPage *KasaSmartSettingsPage;
@ -112,6 +114,7 @@ private:
void AddSoftwareInfoPage();
void AddSupportedDevicesPage();
void AddSettingsPage();
void AddDMXSettingsPage();
void AddE131SettingsPage();
void AddElgatoKeyLightSettingsPage();
void AddKasaSmartSettingsPage();

View file

@ -17,9 +17,13 @@
serial_port::serial_port()
{
/*-----------------------------------------------------*\
| Set a default baud rate |
| Set default port configuration but do not open |
\*-----------------------------------------------------*/
baud_rate = 9600;
baud_rate = 9600;
parity = SERIAL_PORT_PARITY_NONE;
size = SERIAL_PORT_SIZE_8;
stop_bits = SERIAL_PORT_STOP_BITS_1;
flow_control = true;
}
/*---------------------------------------------------------*\
@ -29,7 +33,44 @@ serial_port::serial_port()
\*---------------------------------------------------------*/
serial_port::serial_port(const char * name, unsigned int baud)
{
serial_open(name, baud);
/*-----------------------------------------------------*\
| Set default port configuration and open |
\*-----------------------------------------------------*/
baud_rate = baud;
parity = SERIAL_PORT_PARITY_NONE;
size = SERIAL_PORT_SIZE_8;
stop_bits = SERIAL_PORT_STOP_BITS_1;
flow_control = true;
serial_open(name);
}
/*---------------------------------------------------------*\
| serial_port (constructor) |
| When created with port information, the constructor |
| will automatically open port <name> at baud rate <baud>|
| with the given port configuration |
\*---------------------------------------------------------*/
serial_port::serial_port
(
const char * name,
unsigned int baud,
serial_port_parity parity,
serial_port_size size,
serial_port_stop_bits stop_bits,
bool flow_control
)
{
/*-----------------------------------------------------*\
| Set default port configuration and open |
\*-----------------------------------------------------*/
this->baud_rate = baud;
this->parity = parity;
this->size = size;
this->stop_bits = stop_bits;
this->flow_control = flow_control;
serial_open(name);
}
/*---------------------------------------------------------*\
@ -58,6 +99,9 @@ bool serial_port::serial_open()
char full_path[100];
snprintf(full_path, sizeof(full_path), "\\\\.\\%s", port_name);
/*-----------------------------------------*\
| Open the port read/write |
\*-----------------------------------------*/
file_descriptor = CreateFile(full_path, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if((int)file_descriptor < 0)
@ -65,29 +109,106 @@ bool serial_port::serial_open()
return false;
}
/*-----------------------------------------*\
| Get the port configuration options |
\*-----------------------------------------*/
SetupComm(file_descriptor, 1, 128);
GetCommState(file_descriptor, &dcb);
dcb.BaudRate = baud_rate; //Set baud rate
dcb.ByteSize = 8; //8 data bits
dcb.Parity = NOPARITY; //Parity = none
dcb.StopBits = ONESTOPBIT; //One stop bit
dcb.fAbortOnError = TRUE; //Abort on error
/*-----------------------------------------*\
| Configure baud rate |
\*-----------------------------------------*/
dcb.BaudRate = baud_rate;
/*-----------------------------------------*\
| Configure parity |
\*-----------------------------------------*/
switch(parity)
{
case SERIAL_PORT_PARITY_NONE:
dcb.Parity = NOPARITY;
break;
case SERIAL_PORT_PARITY_ODD:
dcb.Parity = ODDPARITY;
break;
case SERIAL_PORT_PARITY_EVEN:
dcb.Parity = EVENPARITY;
break;
}
/*-----------------------------------------*\
| Configure size |
\*-----------------------------------------*/
switch(size)
{
case SERIAL_PORT_SIZE_8:
dcb.ByteSize = 8;
break;
case SERIAL_PORT_SIZE_7:
dcb.ByteSize = 7;
break;
case SERIAL_PORT_SIZE_6:
dcb.ByteSize = 6;
break;
case SERIAL_PORT_SIZE_5:
dcb.ByteSize = 5;
break;
}
/*-----------------------------------------*\
| Configure stop bits |
\*-----------------------------------------*/
if(stop_bits == SERIAL_PORT_STOP_BITS_2)
{
dcb.StopBits = TWOSTOPBITS;
}
else
{
dcb.StopBits = ONESTOPBIT;
}
/*-----------------------------------------*\
| Configure flow control |
\*-----------------------------------------*/
if(flow_control)
{
dcb.fRtsControl = RTS_CONTROL_ENABLE;
}
else
{
dcb.fRtsControl = RTS_CONTROL_DISABLE;
}
/*-----------------------------------------*\
| Configure additional parameters |
\*-----------------------------------------*/
dcb.fAbortOnError = FALSE; //Abort on error
dcb.fOutX = FALSE; //XON/XOFF off for transmit
dcb.fInX = FALSE; //XON/XOFF off for receive
dcb.fOutxCtsFlow = FALSE; //Turn off CTS flow control
dcb.fRtsControl = RTS_CONTROL_DISABLE; //Options DISABLE, ENABLE, HANDSHAKE
dcb.fOutxDsrFlow = FALSE; //Turn off DSR flow control
dcb.fDtrControl = DTR_CONTROL_DISABLE; //Disable DTR control
/*-----------------------------------------*\
| Set the port configuration options |
\*-----------------------------------------*/
SetCommState(file_descriptor, &dcb);
COMMTIMEOUTS timeouts = {0};
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant=50;
timeouts.ReadTotalTimeoutMultiplier=10;
timeouts.WriteTotalTimeoutConstant=50;
timeouts.WriteTotalTimeoutMultiplier=10;
/*-----------------------------------------*\
| Set the port timeouts |
\*-----------------------------------------*/
COMMTIMEOUTS timeouts = {0};
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 50;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.WriteTotalTimeoutConstant = 50;
timeouts.WriteTotalTimeoutMultiplier = 10;
SetCommTimeouts(file_descriptor, &timeouts);
#endif
@ -95,6 +216,9 @@ bool serial_port::serial_open()
| Linux-specific code path for serial port opening |
\*-----------------------------------------------------*/
#ifdef __linux__
/*-----------------------------------------*\
| Open the port read/write with no delay |
\*-----------------------------------------*/
file_descriptor = open(port_name, O_RDWR | O_NOCTTY | O_NDELAY);
if(file_descriptor < 0)
@ -102,19 +226,105 @@ bool serial_port::serial_open()
return false;
}
/*-----------------------------------------*\
| Get the port configuration options |
\*-----------------------------------------*/
struct termios2 options;
ioctl(file_descriptor, TCGETS2, &options);
/*-----------------------------------------*\
| Configure baud rate |
\*-----------------------------------------*/
options.c_cflag &= ~CBAUD;
options.c_cflag |= BOTHER;
options.c_cflag |= CBAUDEX;
options.c_ispeed = baud_rate;
options.c_ospeed = baud_rate;
/*-----------------------------------------*\
| Configure parity |
\*-----------------------------------------*/
switch(parity)
{
case SERIAL_PORT_PARITY_NONE:
options.c_cflag &= ~PARENB;
options.c_cflag &= ~PARODD;
break;
case SERIAL_PORT_PARITY_ODD:
options.c_cflag |= PARENB;
options.c_cflag |= PARODD;
break;
case SERIAL_PORT_PARITY_EVEN:
options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
break;
}
/*-----------------------------------------*\
| Configure size |
\*-----------------------------------------*/
options.c_cflag &= ~CSIZE;
switch(size)
{
case SERIAL_PORT_SIZE_8:
options.c_cflag |= CS8;
break;
case SERIAL_PORT_SIZE_7:
options.c_cflag |= CS7;
break;
case SERIAL_PORT_SIZE_6:
options.c_cflag |= CS6;
break;
case SERIAL_PORT_SIZE_5:
options.c_cflag |= CS5;
break;
}
/*-----------------------------------------*\
| Configure stop bits |
\*-----------------------------------------*/
if(stop_bits == SERIAL_PORT_STOP_BITS_2)
{
options.c_cflag |= CSTOPB;
}
else
{
options.c_cflag &= ~CSTOPB;
}
/*-----------------------------------------*\
| Configure flow control |
\*-----------------------------------------*/
if(flow_control)
{
options.c_cflag |= CRTSCTS;
}
else
{
options.c_cflag &= ~CRTSCTS;
}
/*-----------------------------------------*\
| Configure additional parameters |
\*-----------------------------------------*/
options.c_lflag &= ~ICANON;
options.c_lflag &= ~ECHO; // Disable echo
options.c_lflag &= ~ECHOE; // Disable erasure
options.c_lflag &= ~ECHONL; // Disable new-line echo
options.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
options.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
options.c_ispeed = baud_rate;
options.c_ospeed = baud_rate;
options.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes
/*-----------------------------------------*\
| Set the port configuration options |
\*-----------------------------------------*/
ioctl(file_descriptor, TCSETS2, &options);
#endif
@ -122,6 +332,9 @@ bool serial_port::serial_open()
| MacOS-specific code path for serial port opening |
\*-----------------------------------------------------*/
#ifdef __APPLE__
/*-----------------------------------------*\
| Open the port read/write with no delay |
\*-----------------------------------------*/
file_descriptor = open(port_name, O_RDWR | O_NOCTTY | O_NDELAY);
if(file_descriptor < 0)
@ -129,53 +342,107 @@ bool serial_port::serial_open()
return false;
}
/*-----------------------------------------*\
| Get the port configuration options |
\*-----------------------------------------*/
struct termios options;
tcgetattr(file_descriptor, &options);
switch(baud_rate)
/*-----------------------------------------*\
| Configure parity |
\*-----------------------------------------*/
switch(parity)
{
case 9600:
cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);
case SERIAL_PORT_PARITY_NONE:
options.c_cflag &= ~PARENB;
options.c_cflag &= ~PARODD;
break;
case 19200:
cfsetispeed(&options, B19200);
cfsetospeed(&options, B19200);
case SERIAL_PORT_PARITY_ODD:
options.c_cflag |= PARENB;
options.c_cflag |= PARODD;
break;
case 115200:
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
break;
case 38400:
cfsetispeed(&options, B38400);
cfsetospeed(&options, B38400);
break;
case 57600:
cfsetispeed(&options, B57600);
cfsetospeed(&options, B57600);
break;
default:
cfsetispeed(&options, B9600);
cfsetospeed(&options, B9600);
case SERIAL_PORT_PARITY_EVEN:
options.c_cflag |= PARENB;
options.c_cflag &= ~PARODD;
break;
}
/*-----------------------------------------------------*\
| Configure other settings |
\*-----------------------------------------------------*/
/*-----------------------------------------*\
| Configure size |
\*-----------------------------------------*/
options.c_cflag &= ~CSIZE;
switch(size)
{
case SERIAL_PORT_SIZE_8:
options.c_cflag |= CS8;
break;
case SERIAL_PORT_SIZE_7:
options.c_cflag |= CS7;
break;
case SERIAL_PORT_SIZE_6:
options.c_cflag |= CS6;
break;
case SERIAL_PORT_SIZE_5:
options.c_cflag |= CS5;
break;
}
/*-----------------------------------------*\
| Configure stop bits |
\*-----------------------------------------*/
if(stop_bits == SERIAL_PORT_STOP_BITS_2)
{
options.c_cflag |= CSTOPB;
}
else
{
options.c_cflag &= ~CSTOPB;
}
/*-----------------------------------------*\
| Configure flow control |
\*-----------------------------------------*/
if(flow_control)
{
options.c_cflag |= CRTSCTS;
}
else
{
options.c_cflag &= ~CRTSCTS;
}
/*-----------------------------------------*\
| Configure additional parameters |
\*-----------------------------------------*/
options.c_lflag &= ~(ICANON | ISIG | ECHO);
options.c_iflag &= ~(INLCR | ICRNL);
options.c_iflag |= IGNPAR | IGNBRK;
options.c_oflag &= ~(OPOST | ONLCR | OCRNL);
options.c_cflag &= ~(PARENB | PARODD | CSTOPB | CSIZE | CRTSCTS);
options.c_cflag |= CLOCAL | CREAD | CS8;
options.c_lflag &= ~(ICANON | ISIG | ECHO);
options.c_cc[VTIME] = 1;
options.c_cc[VMIN] = 0;
/*-----------------------------------------*\
| Set the port configuration options |
\*-----------------------------------------*/
if(tcsetattr(file_descriptor, TCSANOW, &options) < 0)
{
close(file_descriptor);
return false;
}
/*-----------------------------------------*\
| Configure baud rate |
\*-----------------------------------------*/
ioctl(file_descriptor, IOSSIOSPEED, &baud_rate);
#endif
/*-----------------------------------------------------*\
@ -357,3 +624,85 @@ void serial_port::serial_flush_tx()
tcflush(file_descriptor, TCOFLUSH);
#endif
}
/*---------------------------------------------------------*\
| serial_break |
\*---------------------------------------------------------*/
void serial_port::serial_break()
{
/*-----------------------------------------------------*\
| Windows-specific code path for serial break |
\*-----------------------------------------------------*/
#ifdef _WIN32
SetCommBreak(file_descriptor);
Sleep(1);
ClearCommBreak(file_descriptor);
#endif
/*-----------------------------------------------------*\
| Linux-specific code path for serial break |
\*-----------------------------------------------------*/
#ifdef __linux__
//Send break for at least 1 ms
ioctl(file_descriptor, TIOCSBRK);
usleep(1000);
ioctl(file_descriptor, TIOCCBRK);
#endif
/*-----------------------------------------------------*\
| MacOS-specific code path for serial break |
\*-----------------------------------------------------*/
#ifdef __APPLE__
//Send break for at least 1 ms
ioctl(file_descriptor, TIOCSBRK);
usleep(1000);
ioctl(file_descriptor, TIOCCBRK);
#endif
}
void serial_port::serial_set_rts(bool rts)
{
/*-----------------------------------------------------*\
| Windows-specific code path for serial set RTS |
\*-----------------------------------------------------*/
#ifdef _WIN32
if(rts)
{
EscapeCommFunction(file_descriptor, SETRTS);
}
else
{
EscapeCommFunction(file_descriptor, CLRRTS);
}
#endif
/*-----------------------------------------------------*\
| Linux-specific code path for serial set RTS |
\*-----------------------------------------------------*/
#ifdef __linux__
const int RTSFLAG = TIOCM_RTS;
if(rts)
{
ioctl(file_descriptor, TIOCMBIS, &RTSFLAG);
}
else
{
ioctl(file_descriptor, TIOCMBIC, &RTSFLAG);
}
#endif
/*-----------------------------------------------------*\
| MacOS-specific code path for serial set RTS |
\*-----------------------------------------------------*/
#ifdef __APPLE__
const int RTSFLAG = TIOCM_RTS;
if(rts)
{
ioctl(file_descriptor, TIOCMBIS, &RTSFLAG);
}
else
{
ioctl(file_descriptor, TIOCMBIC, &RTSFLAG);
}
#endif
}

View file

@ -58,9 +58,37 @@
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <IOKit/serial/ioss.h>
#endif /* __APPLE__ */
/*-------------------------------------------------------------------------*\
| Serial Port Enums |
\*-------------------------------------------------------------------------*/
typedef unsigned int serial_port_parity;
enum
{
SERIAL_PORT_PARITY_NONE = 0, /* No parity */
SERIAL_PORT_PARITY_ODD = 1, /* Odd parity */
SERIAL_PORT_PARITY_EVEN = 2, /* Even parity */
};
typedef unsigned int serial_port_size;
enum
{
SERIAL_PORT_SIZE_8 = 0, /* 8 bits per byte */
SERIAL_PORT_SIZE_7 = 1, /* 7 bits per byte */
SERIAL_PORT_SIZE_6 = 2, /* 6 bits per byte */
SERIAL_PORT_SIZE_5 = 3, /* 5 bits per byte */
};
typedef unsigned int serial_port_stop_bits;
enum
{
SERIAL_PORT_STOP_BITS_1 = 0, /* 1 stop bit */
SERIAL_PORT_STOP_BITS_2 = 1, /* 2 stop bits */
};
/*-------------------------------------------------------------------------*\
| Serial Port Class |
| The reason for this class is that serial ports are treated differently |
@ -72,6 +100,12 @@ class serial_port
public:
serial_port();
serial_port(const char * name, unsigned int baud);
serial_port(const char * name,
unsigned int baud,
serial_port_parity parity,
serial_port_size size,
serial_port_stop_bits stop_bits,
bool flow_control);
~serial_port();
@ -90,12 +124,19 @@ public:
void serial_flush_rx();
void serial_flush_tx();
void serial_break();
void serial_set_rts(bool rts);
int serial_available();
private:
char port_name[1024];
unsigned int baud_rate;
char port_name[1024];
unsigned int baud_rate;
serial_port_parity parity;
serial_port_size size;
serial_port_stop_bits stop_bits;
bool flow_control;
#ifdef _WIN32
HANDLE file_descriptor;