#include "OpenRGBE131ReceiverDialog.h" #include "ui_OpenRGBE131ReceiverDialog.h" #include #include #include #ifndef _WIN32 #include #define closesocket ::close #endif #define MAX_LEDS_PER_UNIVERSE 170 void DeviceListChanged_Callback(void * this_ptr) { OpenRGBE131ReceiverDialog * this_obj = (OpenRGBE131ReceiverDialog *)this_ptr; QMetaObject::invokeMethod(this_obj, "DeviceListChanged", Qt::QueuedConnection); } typedef struct { RGBController* controller; unsigned int start_channel; unsigned int start_led; unsigned int num_leds; bool update; } universe_member; typedef struct { unsigned int universe; std::vector members; } universe_entry; static std::vector universe_list; typedef unsigned int LineEditParameter; enum { LINEEDIT_PARAMETER_START_CHANNEL, LINEEDIT_PARAMETER_START_LED, LINEEDIT_PARAMETER_NUM_LEDS }; class LineEditArgument : public QObject { public: QWidget * widget; unsigned int universe_idx; unsigned int member_idx; LineEditParameter parameter; }; typedef unsigned int CheckBoxParameter; enum { CHECKBOX_PARAMETER_UPDATE }; class CheckBoxArgument : public QObject { public: QWidget * widget; unsigned int universe_idx; unsigned int member_idx; CheckBoxParameter parameter; }; OpenRGBE131ReceiverDialog::OpenRGBE131ReceiverDialog(ResourceManager* manager, QWidget *parent) : QWidget(parent), ui(new Ui::OpenRGBE131ReceiverDialog) { ui->setupUi(this); resource_manager = manager; /*-------------------------------------------------*\ | Register device list change callback | \*-------------------------------------------------*/ resource_manager->RegisterDeviceListChangeCallback(DeviceListChanged_Callback, this); resource_manager->RegisterDetectionProgressCallback(DeviceListChanged_Callback, this); online = false; received_count = 0; UpdateOnlineStatus(); } OpenRGBE131ReceiverDialog::~OpenRGBE131ReceiverDialog() { delete ui; } void OpenRGBE131ReceiverDialog::DeviceListChanged() { if(resource_manager->GetDetectionPercent() == 100) { ui->E131TreeView->clear(); universe_list.clear(); for(unsigned int controller_idx = 0; controller_idx < resource_manager->GetRGBControllers().size(); controller_idx++) { RGBController* controller = resource_manager->GetRGBControllers()[controller_idx]; // Determine if the controller has a Direct mode bool has_direct = false; for(unsigned int mode_idx = 0; mode_idx < controller->modes.size(); mode_idx++) { if(controller->modes[mode_idx].name == "Direct") { has_direct = true; break; } } // Only map controllers that have a Direct mode if(has_direct) { unsigned int num_universes = 1 + ((controller->leds.size() * 3) / 512); unsigned int remaining_leds = controller->leds.size(); unsigned int start_led = 0; for(unsigned int universe_idx = 0; universe_idx < num_universes; universe_idx++) { // For now, create universes sequentially per controller universe_entry new_entry; new_entry.universe = universe_list.size() + 1; // Add members as needed universe_member new_member; new_member.controller = resource_manager->GetRGBControllers()[controller_idx]; new_member.start_channel = 1; new_member.start_led = start_led; new_member.num_leds = remaining_leds; new_member.update = false; // Limit the number of LEDs if(new_member.num_leds > MAX_LEDS_PER_UNIVERSE) { new_member.num_leds = MAX_LEDS_PER_UNIVERSE; } // Update start LED start_led = start_led + new_member.num_leds; // Update remaining LED count remaining_leds = remaining_leds - new_member.num_leds; // Only set the update flag for the last universe of the controller if(universe_idx == (num_universes - 1)) { new_member.update = true; } new_entry.members.push_back(new_member); universe_list.push_back(new_entry); } } } UpdateTreeView(); } else { ui->E131TreeView->clear(); universe_list.clear(); } } void OpenRGBE131ReceiverDialog::UpdateTreeView() { ui->E131TreeView->clear(); ui->E131TreeView->setColumnCount(5); ui->E131TreeView->header()->setStretchLastSection(false); ui->E131TreeView->header()->setSectionResizeMode(0, QHeaderView::Stretch); ui->E131TreeView->setHeaderLabels(QStringList() << "Universe" << "Start Channel" << "Start LED" << "LED Count" << "Update"); /*-----------------------------------------------------*\ | Create a signal mapper for LineEdit fields | \*-----------------------------------------------------*/ QSignalMapper* LineEditMapper = new QSignalMapper(this); connect(LineEditMapper, SIGNAL(mapped(QObject *)), this, SLOT(on_LineEdit_updated(QObject *))); /*-----------------------------------------------------*\ | Create a signal mapper for CheckBox fields | \*-----------------------------------------------------*/ QSignalMapper* CheckBoxMapper = new QSignalMapper(this); connect(CheckBoxMapper, SIGNAL(mapped(QObject *)), this, SLOT(on_CheckBox_updated(QObject *))); for(unsigned int universe_idx = 0; universe_idx < universe_list.size(); universe_idx++) { QTreeWidgetItem* new_universe_entry = new QTreeWidgetItem(ui->E131TreeView); new_universe_entry->setText(0, QString::fromStdString("Universe " + std::to_string(universe_list[universe_idx].universe))); for(unsigned int member_idx = 0; member_idx < universe_list[universe_idx].members.size(); member_idx++) { QTreeWidgetItem* new_member_entry = new QTreeWidgetItem(new_universe_entry); new_member_entry->setText(0, QString::fromStdString((universe_list[universe_idx].members[member_idx].controller->name))); QLineEdit* start_channel_edit = new QLineEdit(ui->E131TreeView); start_channel_edit->setText(QString::number(universe_list[universe_idx].members[member_idx].start_channel)); ui->E131TreeView->setItemWidget(new_member_entry, 1, start_channel_edit); LineEditArgument* start_channel_argument = new LineEditArgument(); start_channel_argument->widget = start_channel_edit; start_channel_argument->universe_idx = universe_idx; start_channel_argument->member_idx = member_idx; start_channel_argument->parameter = LINEEDIT_PARAMETER_START_CHANNEL; connect(start_channel_edit, SIGNAL(editingFinished()), LineEditMapper, SLOT(map())); LineEditMapper->setMapping(start_channel_edit, start_channel_argument); QLineEdit* start_led_edit = new QLineEdit(ui->E131TreeView); start_led_edit->setText(QString::number(universe_list[universe_idx].members[member_idx].start_led)); ui->E131TreeView->setItemWidget(new_member_entry, 2, start_led_edit); LineEditArgument* start_led_argument = new LineEditArgument(); start_led_argument->widget = start_led_edit; start_led_argument->universe_idx = universe_idx; start_led_argument->member_idx = member_idx; start_led_argument->parameter = LINEEDIT_PARAMETER_START_LED; connect(start_led_edit, SIGNAL(editingFinished()), LineEditMapper, SLOT(map())); LineEditMapper->setMapping(start_led_edit, start_led_argument); QLineEdit* num_leds_edit = new QLineEdit(ui->E131TreeView); num_leds_edit->setText(QString::number(universe_list[universe_idx].members[member_idx].num_leds)); ui->E131TreeView->setItemWidget(new_member_entry, 3, num_leds_edit); LineEditArgument* num_leds_argument = new LineEditArgument(); num_leds_argument->widget = num_leds_edit; num_leds_argument->universe_idx = universe_idx; num_leds_argument->member_idx = member_idx; num_leds_argument->parameter = LINEEDIT_PARAMETER_NUM_LEDS; connect(num_leds_edit, SIGNAL(editingFinished()), LineEditMapper, SLOT(map())); LineEditMapper->setMapping(num_leds_edit, num_leds_argument); QCheckBox* update_checkbox = new QCheckBox(ui->E131TreeView); update_checkbox->setChecked(universe_list[universe_idx].members[member_idx].update); ui->E131TreeView->setItemWidget(new_member_entry, 4, update_checkbox); CheckBoxArgument* update_argument = new CheckBoxArgument(); update_argument->widget = update_checkbox; update_argument->universe_idx = universe_idx; update_argument->member_idx = member_idx; update_argument->parameter = CHECKBOX_PARAMETER_UPDATE; connect(update_checkbox, SIGNAL(clicked()), CheckBoxMapper, SLOT(map())); CheckBoxMapper->setMapping(update_checkbox, update_argument); } } ui->E131TreeView->setStyleSheet("QLineEdit { border: none }"); ui->E131TreeView->expandAll(); } void OpenRGBE131ReceiverDialog::E131ReceiverThreadFunction() { int sockfd; e131_packet_t packet; e131_error_t error; uint8_t last_seq = 0x00; std::chrono::time_point last_update_time; /*-----------------------------------------------------*\ | Initialize update time | \*-----------------------------------------------------*/ last_update_time = std::chrono::steady_clock::now(); /*-----------------------------------------------------*\ | Clear online status | \*-----------------------------------------------------*/ online = false; /*-----------------------------------------------------*\ | Create a socket for E1.31 | \*-----------------------------------------------------*/ sockfd = e131_socket(); if(sockfd < 0) { printf("Socket error\r\n"); return; } /*-----------------------------------------------------*\ | Bind the socket to the default E1.31 port | \*-----------------------------------------------------*/ if(e131_bind(sockfd, E131_DEFAULT_PORT) < 0) { printf("Bind error\r\n"); return; } /*-----------------------------------------------------*\ | Join the configured universes if multicast enabled | \*-----------------------------------------------------*/ bool multicast = ui->EnableMulticastBox->checkState() == Qt::Checked; for(unsigned int universe_idx = 0; universe_idx < universe_list.size(); universe_idx++) { if(multicast) { if(e131_multicast_join(sockfd, universe_list[universe_idx].universe) < 0) { printf("Join error\r\n"); return; } } } /*-----------------------------------------------------*\ | Set online status | \*-----------------------------------------------------*/ online = true; UpdateOnlineStatus(); /*-----------------------------------------------------*\ | Loop to receive E1.31 packets | \*-----------------------------------------------------*/ while(online) { if(e131_recv(sockfd, &packet) < 0) { printf("Receive error\r\n"); online = false; } if((error = e131_pkt_validate(&packet)) != E131_ERR_NONE) { printf("Validation error: %s\r\n", e131_strerror(error)); continue; } // if(e131_pkt_discard(&packet, last_seq)) // { // //printf("Warning: packet out of order received\r\n"); // last_seq = packet.frame.seq_number; // continue; // } received_count++; if((std::chrono::steady_clock::now() - last_update_time) > std::chrono::milliseconds(500)) { ui->PacketsReceivedValue->setText(QString::number(received_count)); ui->ReceiverSourceValue->setText(QString::fromLocal8Bit((const char *)packet.frame.source_name, 64)); last_update_time = std::chrono::steady_clock::now(); } last_seq = packet.frame.seq_number; /*-------------------------------------------------*\ | If received packet is from a valid universe, | | update the corresponding devices | \*-------------------------------------------------*/ unsigned int universe = ntohs(packet.frame.universe); for(unsigned int universe_idx = 0; universe_idx < universe_list.size(); universe_idx++) { if(universe_list[universe_idx].universe == universe) { for(unsigned int member_idx = 0; member_idx < universe_list[universe_idx].members.size(); member_idx++) { RGBController* controller = universe_list[universe_idx].members[member_idx].controller; unsigned int channel = universe_list[universe_idx].members[member_idx].start_channel; unsigned int start_led = universe_list[universe_idx].members[member_idx].start_led; unsigned int num_leds = universe_list[universe_idx].members[member_idx].num_leds; bool update = universe_list[universe_idx].members[member_idx].update; // Channel range is 1-512, so break if channel is zero if(channel == 0) { break; } for(unsigned int led_idx = start_led; led_idx < (start_led + num_leds); led_idx++) { // Calculate channels for this LED unsigned int red_idx = channel + 0; unsigned int grn_idx = channel + 1; unsigned int blu_idx = channel + 2; // Verify last channel (blue) is within range 1-512 if(blu_idx > 512) { break; } // Get color out of E1.31 packet RGBColor led_color = ToRGBColor(packet.dmp.prop_val[red_idx], packet.dmp.prop_val[grn_idx], packet.dmp.prop_val[blu_idx]); // Verify LED index if(led_idx >= controller->colors.size()) { break; } // Set LED color in controller controller->colors[led_idx] = led_color; // Increment the channel by 3 to account for red, green, and blue channels channel = channel + 3; } // If configured to update the device, update if(update) { controller->UpdateLEDs(); } } } } } closesocket(sockfd); UpdateOnlineStatus(); } void OpenRGBE131ReceiverDialog::UpdateOnlineStatus() { if(online) { ui->ReceiverStatusValue->setText("Online"); ui->ButtonStartReceiver->setEnabled(false); ui->ButtonStopReceiver->setEnabled(true); } else { ui->ReceiverStatusValue->setText("Offline"); received_count = 0; ui->PacketsReceivedValue->setText(QString::number(received_count)); ui->ButtonStartReceiver->setEnabled(true); ui->ButtonStopReceiver->setEnabled(false); } } void OpenRGBE131ReceiverDialog::on_ButtonStartReceiver_clicked() { if(!online) { // Start the receiver thread E131ReceiverThread = new std::thread(&OpenRGBE131ReceiverDialog::E131ReceiverThreadFunction, this); } } void OpenRGBE131ReceiverDialog::on_ButtonStopReceiver_clicked() { online = false; UpdateOnlineStatus(); } void OpenRGBE131ReceiverDialog::on_LineEdit_updated(QObject* lineedit_argument) { /*-----------------------------------------------------*\ | Update the parameter | \*-----------------------------------------------------*/ unsigned int universe_idx = ((LineEditArgument*)lineedit_argument)->universe_idx; unsigned int member_idx = ((LineEditArgument*)lineedit_argument)->member_idx; LineEditParameter parameter = ((LineEditArgument*)lineedit_argument)->parameter; QWidget* widget = ((LineEditArgument*)lineedit_argument)->widget; unsigned int value = ((QLineEdit*)widget)->text().toInt(); switch(parameter) { case LINEEDIT_PARAMETER_START_CHANNEL: universe_list[universe_idx].members[member_idx].start_channel = value; break; case LINEEDIT_PARAMETER_START_LED: universe_list[universe_idx].members[member_idx].start_led = value; break; case LINEEDIT_PARAMETER_NUM_LEDS: universe_list[universe_idx].members[member_idx].num_leds = value; break; } } void OpenRGBE131ReceiverDialog::on_CheckBox_updated(QObject* checkbox_argument) { /*-----------------------------------------------------*\ | Update the parameter | \*-----------------------------------------------------*/ unsigned int universe_idx = ((CheckBoxArgument*)checkbox_argument)->universe_idx; unsigned int member_idx = ((CheckBoxArgument*)checkbox_argument)->member_idx; CheckBoxParameter parameter = ((CheckBoxArgument*)checkbox_argument)->parameter; QWidget* widget = ((CheckBoxArgument*)checkbox_argument)->widget; bool value = ((QCheckBox*)widget)->isChecked(); switch(parameter) { case CHECKBOX_PARAMETER_UPDATE: universe_list[universe_idx].members[member_idx].update = value; break; } }