diff --git a/OpenRGB/RGBController/RGBController.h b/OpenRGB/RGBController/RGBController.h index 59df813..c875357 100644 --- a/OpenRGB/RGBController/RGBController.h +++ b/OpenRGB/RGBController/RGBController.h @@ -144,7 +144,63 @@ typedef struct typedef void (*RGBControllerCallback)(void *); -class RGBController +class RGBControllerInterface +{ +public: + virtual void SetupColors() = 0; + + virtual RGBColor GetLED(unsigned int led) = 0; + virtual void SetLED(unsigned int led, RGBColor color) = 0; + virtual void SetAllLEDs(RGBColor color) = 0; + virtual void SetAllZoneLEDs(int zone, RGBColor color) = 0; + + virtual int GetMode() = 0; + virtual void SetMode(int mode) = 0; + + virtual unsigned char * GetDeviceDescription(unsigned int protocol_version) = 0; + virtual void ReadDeviceDescription(unsigned char* data_buf, unsigned int protocol_version) = 0; + + virtual unsigned char * GetModeDescription(int mode) = 0; + virtual void SetModeDescription(unsigned char* data_buf) = 0; + + virtual unsigned char * GetColorDescription() = 0; + virtual void SetColorDescription(unsigned char* data_buf) = 0; + + virtual unsigned char * GetZoneColorDescription(int zone) = 0; + virtual void SetZoneColorDescription(unsigned char* data_buf) = 0; + + virtual unsigned char * GetSingleLEDColorDescription(int led) = 0; + virtual void SetSingleLEDColorDescription(unsigned char* data_buf) = 0; + + virtual void RegisterUpdateCallback(RGBControllerCallback new_callback, void * new_callback_arg) = 0; + virtual void UnregisterUpdateCallback(void * callback_arg) = 0; + virtual void SignalUpdate() = 0; + + virtual void UpdateLEDs() = 0; + //virtual void UpdateZoneLEDs(int zone) = 0; + //virtual void UpdateSingleLED(int led) = 0; + + virtual void UpdateMode() = 0; + + virtual void DeviceCallThreadFunction() = 0; + + /*---------------------------------------------------------*\ + | Functions to be implemented in device implementation | + \*---------------------------------------------------------*/ + virtual void SetupZones() = 0; + + virtual void ResizeZone(int zone, int new_size) = 0; + + virtual void DeviceUpdateLEDs() = 0; + virtual void UpdateZoneLEDs(int zone) = 0; + virtual void UpdateSingleLED(int led) = 0; + + virtual void DeviceUpdateMode() = 0; + + virtual void SetCustomMode() = 0; +}; + +class RGBController : public RGBControllerInterface { public: std::string name; /* controller name */ diff --git a/OpenRGB/ResourceManager.h b/OpenRGB/ResourceManager.h index cf450dd..9f74e42 100644 --- a/OpenRGB/ResourceManager.h +++ b/OpenRGB/ResourceManager.h @@ -51,6 +51,8 @@ public: virtual std::vector & GetRGBControllers() = 0; + virtual unsigned int GetDetectionPercent() = 0; + virtual std::string GetConfigurationDirectory() = 0; virtual std::vector& GetClients() = 0; diff --git a/OpenRGBE131ReceiverDialog.cpp b/OpenRGBE131ReceiverDialog.cpp new file mode 100644 index 0000000..6f00f88 --- /dev/null +++ b/OpenRGBE131ReceiverDialog.cpp @@ -0,0 +1,236 @@ +#include "OpenRGBE131ReceiverDialog.h" +#include "ui_OpenRGBE131ReceiverDialog.h" + +#include + +#define MAX_LEDS_PER_UNIVERSE 170 + +void DeviceListChanged_Callback(void * this_ptr) +{ + ((OpenRGBE131ReceiverDialog *)this_ptr)->DeviceListChanged(); +} + +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; + +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); +} + +OpenRGBE131ReceiverDialog::~OpenRGBE131ReceiverDialog() +{ + delete ui; +} + +void OpenRGBE131ReceiverDialog::DeviceListChanged() +{ + if(resource_manager->GetDetectionPercent() == 100) + { + universe_list.clear(); + + for(unsigned int controller_idx = 0; controller_idx < resource_manager->GetRGBControllers().size(); controller_idx++) + { + RGBController* controller = resource_manager->GetRGBControllers()[controller_idx]; + + 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 = 1; + + // 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; + + new_entry.members.push_back(new_member); + + universe_list.push_back(new_entry); + } + } + + 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"); + + 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))); + new_member_entry->setText(1, QString::number(universe_list[universe_idx].members[member_idx].start_channel)); + new_member_entry->setText(2, QString::number(universe_list[universe_idx].members[member_idx].start_led)); + new_member_entry->setText(3, QString::number(universe_list[universe_idx].members[member_idx].num_leds)); + new_member_entry->setText(4, QString::number(universe_list[universe_idx].members[member_idx].update)); + } + } + + ui->E131TreeView->expandAll(); + + // Start the receiver thread + E131ReceiverThread = new std::thread(&OpenRGBE131ReceiverDialog::E131ReceiverThreadFunction, this); + } +} + +void OpenRGBE131ReceiverDialog::E131ReceiverThreadFunction() +{ + int sockfd; + e131_packet_t packet; + e131_error_t error; + uint8_t last_seq = 0x00; + + /*-----------------------------------------------------*\ + | 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 | + \*-----------------------------------------------------*/ + for(unsigned int universe_idx = 0; universe_idx < universe_list.size(); universe_idx++) + { + if(e131_multicast_join(sockfd, universe_list[universe_idx].universe) < 0) + { + printf("Join error\r\n"); + return; + } + } + + /*-----------------------------------------------------*\ + | Loop to receive E1.31 packets | + \*-----------------------------------------------------*/ + while(1) + { + if(e131_recv(sockfd, &packet) < 0) + { + printf("Receive error\r\n"); + return; + } + + 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; +// } + + 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) - 1; + + 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; + + 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; + + // 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]); + + // Set LED color in controller + controller->colors[led_idx] = led_color; + } + + // If configured to update the device, update + if(update) + { + controller->UpdateLEDs(); + } + } + } + } + } +} diff --git a/OpenRGBE131ReceiverDialog.h b/OpenRGBE131ReceiverDialog.h new file mode 100644 index 0000000..5d09fb7 --- /dev/null +++ b/OpenRGBE131ReceiverDialog.h @@ -0,0 +1,30 @@ +#ifndef OPENRGBE131RECEIVERDIALOG_H +#define OPENRGBE131RECEIVERDIALOG_H + +#include "ResourceManager.h" + +#include + +namespace Ui { +class OpenRGBE131ReceiverDialog; +} + +class OpenRGBE131ReceiverDialog : public QWidget +{ + Q_OBJECT + +public: + explicit OpenRGBE131ReceiverDialog(ResourceManager* manager, QWidget *parent = nullptr); + ~OpenRGBE131ReceiverDialog(); + + void DeviceListChanged(); + void E131ReceiverThreadFunction(); + +private: + ResourceManager* resource_manager; + Ui::OpenRGBE131ReceiverDialog *ui; + + std::thread* E131ReceiverThread; +}; + +#endif // OPENRGBE131RECEIVERDIALOG_H diff --git a/OpenRGBE131ReceiverDialog.ui b/OpenRGBE131ReceiverDialog.ui new file mode 100644 index 0000000..abd15fe --- /dev/null +++ b/OpenRGBE131ReceiverDialog.ui @@ -0,0 +1,36 @@ + + + OpenRGBE131ReceiverDialog + + + + 0 + 0 + 700 + 275 + + + + + 0 + 0 + + + + Form + + + + + + + 1 + + + + + + + + + diff --git a/OpenRGBE131ReceiverPlugin.cpp b/OpenRGBE131ReceiverPlugin.cpp new file mode 100644 index 0000000..8fed1ed --- /dev/null +++ b/OpenRGBE131ReceiverPlugin.cpp @@ -0,0 +1,66 @@ +/*-----------------------------------------*\ +| OpenRGBE131ReceiverPlugin.cpp | +| | +| OpenRGB E1.31 Receiver Plugin | +| | +| herosilas12 (CoffeeIsLife) 12/11/2020 | +| Adam Honse (CalcProgrammer1) 1/5/2021 | +\*-----------------------------------------*/ + +#include "OpenRGBE131ReceiverPlugin.h" +#include "OpenRGBE131ReceiverDialog.h" + +/*-----------------------------------------*\ +| Initialize | +| | +| This function must be present in all | +| OpenRGB plugins. It defines the plugin | +| name, description, location, and other | +| plugin information. It creates the tab | +| label and is the entry point for plugin | +| code | +\*-----------------------------------------*/ +OpenRGBPluginInfo OpenRGBPlugin::Initialize(bool dark_theme, ResourceManager* resource_manager_ptr) +{ + info.PluginName = "E1.31 Receiver"; + info.PluginDescription = "OpenRGB E1.31 Receiver Plugin"; + info.PluginLocation = "TopTabBar"; + info.HasCustom = false; + info.SettingName = ""; + info.PluginLabel = new QLabel(); + + /*-----------------------------------------------------*\ + | Set the label text | + \*-----------------------------------------------------*/ + info.PluginLabel->setText("E1.31 Receiver"); + + /*-----------------------------------------------------*\ + | Save the arguments to Initialize based on what you | + | need for this plugin's functionality. In this example| + | we will need the Resource Manager to access the device| + | list, so save the Resource Manager pointer locally. | + \*-----------------------------------------------------*/ + resource_manager = resource_manager_ptr; + + return info; +} + +/*-----------------------------------------*\ +| CreateGUI | +| | +| This function must be present in all | +| OpenRGB plugins. It creates the QWidget | +| that represents the plugin tab's content | +\*-----------------------------------------*/ +QWidget* OpenRGBPlugin::CreateGUI(QWidget* parent) +{ + /*-----------------------------------------------------*\ + | Create the main widget for this plugin tab | + \*-----------------------------------------------------*/ + QWidget* plugin_widget = new OpenRGBE131ReceiverDialog(resource_manager, parent); + + /*-----------------------------------------------------*\ + | The CreateGUI function must return the main widget | + \*-----------------------------------------------------*/ + return plugin_widget; +} diff --git a/OpenRGBPlugin.h b/OpenRGBE131ReceiverPlugin.h similarity index 90% rename from OpenRGBPlugin.h rename to OpenRGBE131ReceiverPlugin.h index d0748a4..4844583 100644 --- a/OpenRGBPlugin.h +++ b/OpenRGBE131ReceiverPlugin.h @@ -30,11 +30,6 @@ public: virtual QWidget *CreateGUI(QWidget* parent) override; - void TimerThreadFunction(); - private: ResourceManager* resource_manager; - QLabel* plugin_label; - - std::thread* TimerThread; }; diff --git a/OpenRGBPlugin.pro b/OpenRGBE131ReceiverPlugin.pro similarity index 66% rename from OpenRGBPlugin.pro rename to OpenRGBE131ReceiverPlugin.pro index 5e17fe2..c618f94 100644 --- a/OpenRGBPlugin.pro +++ b/OpenRGBE131ReceiverPlugin.pro @@ -1,5 +1,5 @@ #-----------------------------------------------------------------------------------------------# -# OpenRGB Plugin Template QMake Project # +# OpenRGB E1.31 Receiver Plugin QMake Project # # # # herosilas12 (CoffeeIsLife) 12/11/2020 # # Adam Honse (CalcProgrammer1) 1/5/2021 # @@ -26,11 +26,18 @@ CONFIG += c++11 #-----------------------------------------------------------------------------------------------# # Plugin Project Files # #-----------------------------------------------------------------------------------------------# +INCLUDEPATH += \ + dependencies/libe131/src/ \ + SOURCES += \ - OpenRGBPlugin.cpp \ + dependencies/libe131/src/e131.c \ + OpenRGBE131ReceiverDialog.cpp \ + OpenRGBE131ReceiverPlugin.cpp \ HEADERS += \ - OpenRGBPlugin.h \ + dependencies/libe131/src/e131.h \ + OpenRGBE131ReceiverDialog.h \ + OpenRGBE131ReceiverPlugin.h \ #-----------------------------------------------------------------------------------------------# # OpenRGB Plugin SDK # @@ -55,6 +62,29 @@ HEADERS += OpenRGB/net_port/net_port.h \ OpenRGB/RGBController/RGBController.h \ +#-----------------------------------------------------------------------------------------------# +# Windows-specific Configuration # +#-----------------------------------------------------------------------------------------------# +win32:contains(QMAKE_TARGET.arch, x86_64) { + LIBS += \ + -lws2_32 \ +} + +win32:contains(QMAKE_TARGET.arch, x86) { + LIBS += \ + -lws2_32 \ +} + +win32:DEFINES -= \ + UNICODE + +win32:DEFINES += \ + _MBCS \ + WIN32 \ + _CRT_SECURE_NO_WARNINGS \ + _WINSOCK_DEPRECATED_NO_WARNINGS \ + WIN32_LEAN_AND_MEAN + #-----------------------------------------------------------------------------------------------# # Default rules for deployment. # #-----------------------------------------------------------------------------------------------# @@ -63,3 +93,6 @@ unix { } !isEmpty(target.path): INSTALLS += target + +FORMS += \ + OpenRGBE131ReceiverDialog.ui diff --git a/OpenRGBPlugin.cpp b/OpenRGBPlugin.cpp deleted file mode 100644 index 3b438fc..0000000 --- a/OpenRGBPlugin.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/*-----------------------------------------*\ -| OpenRGBPlugin.cpp | -| | -| OpenRGB Plugin template with example | -| | -| herosilas12 (CoffeeIsLife) 12/11/2020 | -| Adam Honse (CalcProgrammer1) 1/5/2021 | -\*-----------------------------------------*/ - -#include "OpenRGBPlugin.h" - -/*-----------------------------------------*\ -| Initialize | -| | -| This function must be present in all | -| OpenRGB plugins. It defines the plugin | -| name, description, location, and other | -| plugin information. It creates the tab | -| label and is the entry point for plugin | -| code | -\*-----------------------------------------*/ -OpenRGBPluginInfo OpenRGBPlugin::Initialize(bool dark_theme, ResourceManager* resource_manager_ptr) -{ - info.PluginName = "OpenRGB Plugin Template"; - info.PluginDescription = "An example plugin for OpenRGB"; - info.PluginLocation = "InformationTab"; - info.HasCustom = false; - info.SettingName = ""; - info.PluginLabel = new QLabel(); - - /*-----------------------------------------------------*\ - | Set the label text | - \*-----------------------------------------------------*/ - info.PluginLabel->setText("OpenRGB Example Plugin"); - - /*-----------------------------------------------------*\ - | Save the arguments to Initialize based on what you | - | need for this plugin's functionality. In this example| - | we will need the Resource Manager to access the device| - | list, so save the Resource Manager pointer locally. | - \*-----------------------------------------------------*/ - resource_manager = resource_manager_ptr; - - return info; -} - -/*-----------------------------------------*\ -| CreateGUI | -| | -| This function must be present in all | -| OpenRGB plugins. It creates the QWidget | -| that represents the plugin tab's content | -\*-----------------------------------------*/ -QWidget* OpenRGBPlugin::CreateGUI(QWidget* parent) -{ - /*-----------------------------------------------------*\ - | Create the main widget for this plugin tab | - \*-----------------------------------------------------*/ - QWidget* plugin_widget = new QWidget(parent); - - /*-----------------------------------------------------*\ - | In this example, we will create a label showing the | - | number of RGBController devices. This will be shown | - | in a QLabel, updated at 1Hz by a background thread. | - \*-----------------------------------------------------*/ - plugin_label = new QLabel(plugin_widget); - - /*-----------------------------------------------------*\ - | With the label created, start the worker thread | - \*-----------------------------------------------------*/ - TimerThread = new std::thread(&OpenRGBPlugin::TimerThreadFunction, this); - - /*-----------------------------------------------------*\ - | The CreateGUI function must return the main widget | - \*-----------------------------------------------------*/ - return plugin_widget; -} - -/*-----------------------------------------*\ -| TimerThreadFunction | -| | -| This function is part of the example code| -| and is not a required part of an OpenRGB | -| plugin. This function is an example of | -| how a plugin can run a background thread | -| and interact with the Resource Manager | -\*-----------------------------------------*/ -void OpenRGBPlugin::TimerThreadFunction() -{ - /*-----------------------------------------------------*\ - | Begin infinite loop | - \*-----------------------------------------------------*/ - while(1) - { - /*-------------------------------------------------*\ - | Print the number of devices to a string | - \*-------------------------------------------------*/ - std::string text; - - text.append("Number of devices detected: "); - text.append(std::to_string(resource_manager->GetRGBControllers().size())); - text.append("\r\n"); - - /*-------------------------------------------------*\ - | Update the label | - \*-------------------------------------------------*/ - plugin_label->setText(QString::fromStdString(text)); - - /*-------------------------------------------------*\ - | Sleep for 1 second | - \*-------------------------------------------------*/ - Sleep(1000); - } -} diff --git a/dependencies/libe131/src/e131.c b/dependencies/libe131/src/e131.c new file mode 100644 index 0000000..09993b5 --- /dev/null +++ b/dependencies/libe131/src/e131.c @@ -0,0 +1,313 @@ +/** + * E1.31 (sACN) library for C/C++ + * Hugo Hromic - http://github.com/hhromic + * + * Some content of this file is based on: + * https://github.com/forkineye/E131/blob/master/E131.h + * https://github.com/forkineye/E131/blob/master/E131.cpp + * + * Copyright 2016 Hugo Hromic + * + * 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 + * + * 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 +#include +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#endif + +#include "e131.h" + +/* E1.31 Public Constants */ +const uint16_t E131_DEFAULT_PORT = 5568; +const uint8_t E131_DEFAULT_PRIORITY = 0x64; + +/* E1.31 Private Constants */ +const uint16_t _E131_PREAMBLE_SIZE = 0x0010; +const uint16_t _E131_POSTAMBLE_SIZE = 0x0000; +const uint8_t _E131_ACN_PID[] = {0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00}; +const uint32_t _E131_ROOT_VECTOR = 0x00000004; +const uint32_t _E131_FRAME_VECTOR = 0x00000002; +const uint8_t _E131_DMP_VECTOR = 0x02; +const uint8_t _E131_DMP_TYPE = 0xa1; +const uint16_t _E131_DMP_FIRST_ADDR = 0x0000; +const uint16_t _E131_DMP_ADDR_INC = 0x0001; + +/* Create a socket file descriptor suitable for E1.31 communication */ +int e131_socket(void) { +#ifdef _WIN32 + WSADATA WsaData; + WSAStartup(MAKEWORD(2, 2), &WsaData); +#endif + return socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); +} + +/* Bind a socket file descriptor to a port number for E1.31 communication */ +int e131_bind(int sockfd, const uint16_t port) { + e131_addr_t addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(port); + memset(addr.sin_zero, 0, sizeof addr.sin_zero); + return bind(sockfd, (struct sockaddr *)&addr, sizeof addr); +} + +/* Initialize a unicast E1.31 destination using a host and port number */ +int e131_unicast_dest(e131_addr_t *dest, const char *host, const uint16_t port) { + if (dest == NULL || host == NULL) { + errno = EINVAL; + return -1; + } + struct hostent *he = gethostbyname(host); + if (he == NULL) { + errno = EADDRNOTAVAIL; + return -1; + } + dest->sin_family = AF_INET; + dest->sin_addr = *(struct in_addr *)he->h_addr; + dest->sin_port = htons(port); + memset(dest->sin_zero, 0, sizeof dest->sin_zero); + return 0; +} + +/* Initialize a multicast E1.31 destination using a universe and port number */ +int e131_multicast_dest(e131_addr_t *dest, const uint16_t universe, const uint16_t port) { + if (dest == NULL || universe < 1 || universe > 63999) { + errno = EINVAL; + return -1; + } + dest->sin_family = AF_INET; + dest->sin_addr.s_addr = htonl(0xefff0000 | universe); + dest->sin_port = htons(port); + memset(dest->sin_zero, 0, sizeof dest->sin_zero); + return 0; +} + +/* Describe an E1.31 destination into a string */ +int e131_dest_str(char *str, const e131_addr_t *dest) { + if (str == NULL || dest == NULL) { + errno = EINVAL; + return -1; + } + sprintf(str, "%s:%d", inet_ntoa(dest->sin_addr), ntohs(dest->sin_port)); + return 0; +} + +/* Join a socket file descriptor to an E1.31 multicast group using a universe */ +int e131_multicast_join(int sockfd, const uint16_t universe) { + if (universe < 1 || universe > 63999) { + errno = EINVAL; + return -1; + } +#ifdef _WIN32 + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = htonl(0xefff0000 | universe); + mreq.imr_interface.s_addr = htonl(INADDR_ANY); +#else + struct ip_mreqn mreq; + mreq.imr_multiaddr.s_addr = htonl(0xefff0000 | universe); + mreq.imr_address.s_addr = htonl(INADDR_ANY); + mreq.imr_ifindex = 0; +#endif + return setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof mreq); +} + +/* Initialize an E1.31 packet using a universe and a number of slots */ +int e131_pkt_init(e131_packet_t *packet, const uint16_t universe, const uint16_t num_slots) { + if (packet == NULL || universe < 1 || universe > 63999 || num_slots < 1 || num_slots > 512) { + errno = EINVAL; + return -1; + } + + // compute packet layer lengths + uint16_t prop_val_cnt = num_slots + 1; + uint16_t dmp_length = prop_val_cnt + + sizeof packet->dmp - sizeof packet->dmp.prop_val; + uint16_t frame_length = sizeof packet->frame + dmp_length; + uint16_t root_length = sizeof packet->root.flength + + sizeof packet->root.vector + sizeof packet->root.cid + frame_length; + + // clear packet + memset(packet, 0, sizeof *packet); + + // set Root Layer values + packet->root.preamble_size = htons(_E131_PREAMBLE_SIZE); + packet->root.postamble_size = htons(_E131_POSTAMBLE_SIZE); + memcpy(packet->root.acn_pid, _E131_ACN_PID, sizeof packet->root.acn_pid); + packet->root.flength = htons(0x7000 | root_length); + packet->root.vector = htonl(_E131_ROOT_VECTOR); + + // set Framing Layer values + packet->frame.flength = htons(0x7000 | frame_length); + packet->frame.vector = htonl(_E131_FRAME_VECTOR); + packet->frame.priority = E131_DEFAULT_PRIORITY; + packet->frame.universe = htons(universe); + + // set Device Management Protocol (DMP) Layer values + packet->dmp.flength = htons(0x7000 | dmp_length); + packet->dmp.vector = _E131_DMP_VECTOR; + packet->dmp.type = _E131_DMP_TYPE; + packet->dmp.first_addr = htons(_E131_DMP_FIRST_ADDR); + packet->dmp.addr_inc = htons(_E131_DMP_ADDR_INC); + packet->dmp.prop_val_cnt = htons(prop_val_cnt); + + return 0; +} + +/* Get the state of a framing option in an E1.31 packet */ +bool e131_get_option(const e131_packet_t *packet, const e131_option_t option) { + if (packet != NULL && packet->frame.options & (1 << (option % 8))) + return true; + return false; +} + +/* Set the state of a framing option in an E1.31 packet */ +int e131_set_option(e131_packet_t *packet, const e131_option_t option, const bool state) { + if (packet == NULL) { + errno = EINVAL; + return -1; + } + packet->frame.options ^= (-state ^ packet->frame.options) & (1 << (option % 8)); + return 0; +} + +/* Send an E1.31 packet to a socket file descriptor using a destination */ +ssize_t e131_send(int sockfd, const e131_packet_t *packet, const e131_addr_t *dest) { + if (packet == NULL || dest == NULL) { + errno = EINVAL; + return -1; + } + const size_t packet_length = sizeof packet->raw - + sizeof packet->dmp.prop_val + htons(packet->dmp.prop_val_cnt); + return sendto(sockfd, packet->raw, packet_length, 0, + (const struct sockaddr *)dest, sizeof *dest); +} + +/* Receive an E1.31 packet from a socket file descriptor */ +ssize_t e131_recv(int sockfd, e131_packet_t *packet) { + if (packet == NULL) { + errno = EINVAL; + return -1; + } + return recv(sockfd, packet->raw, sizeof packet->raw, 0); +} + +/* Validate that an E1.31 packet is well-formed */ +e131_error_t e131_pkt_validate(const e131_packet_t *packet) { + if (packet == NULL) + return E131_ERR_NULLPTR; + if (ntohs(packet->root.preamble_size) != _E131_PREAMBLE_SIZE) + return E131_ERR_PREAMBLE_SIZE; + if (ntohs(packet->root.postamble_size) != _E131_POSTAMBLE_SIZE) + return E131_ERR_POSTAMBLE_SIZE; + if (memcmp(packet->root.acn_pid, _E131_ACN_PID, sizeof packet->root.acn_pid) != 0) + return E131_ERR_ACN_PID; + if (ntohl(packet->root.vector) != _E131_ROOT_VECTOR) + return E131_ERR_VECTOR_ROOT; + if (ntohl(packet->frame.vector) != _E131_FRAME_VECTOR) + return E131_ERR_VECTOR_FRAME; + if (packet->dmp.vector != _E131_DMP_VECTOR) + return E131_ERR_VECTOR_DMP; + if (packet->dmp.type != _E131_DMP_TYPE) + return E131_ERR_TYPE_DMP; + if (htons(packet->dmp.first_addr) != _E131_DMP_FIRST_ADDR) + return E131_ERR_FIRST_ADDR_DMP; + if (htons(packet->dmp.addr_inc) != _E131_DMP_ADDR_INC) + return E131_ERR_ADDR_INC_DMP; + return E131_ERR_NONE; +} + +/* Check if an E1.31 packet should be discarded (sequence number out of order) */ +bool e131_pkt_discard(const e131_packet_t *packet, const uint8_t last_seq_number) { + if (packet == NULL) + return true; + int8_t seq_num_diff = packet->frame.seq_number - last_seq_number; + if (seq_num_diff > -20 && seq_num_diff <= 0) + return true; + return false; +} + +/* Dump an E1.31 packet to a stream (i.e. stdout, stderr) */ +int e131_pkt_dump(FILE *stream, const e131_packet_t *packet) { + if (stream == NULL || packet == NULL) { + errno = EINVAL; + return -1; + } + fprintf(stream, "[Root Layer]\n"); + fprintf(stream, " Preamble Size .......... %" PRIu16 "\n", ntohs(packet->root.preamble_size)); + fprintf(stream, " Post-amble Size ........ %" PRIu16 "\n", ntohs(packet->root.postamble_size)); + fprintf(stream, " ACN Packet Identifier .. %s\n", packet->root.acn_pid); + fprintf(stream, " Flags & Length ......... %" PRIu16 "\n", ntohs(packet->root.flength)); + fprintf(stream, " Layer Vector ........... %" PRIu32 "\n", ntohl(packet->root.vector)); + fprintf(stream, " Component Identifier ... "); + for (size_t pos=0, total=sizeof packet->root.cid; posroot.cid[pos]); + fprintf(stream, "\n"); + fprintf(stream, "[Framing Layer]\n"); + fprintf(stream, " Flags & Length ......... %" PRIu16 "\n", ntohs(packet->frame.flength)); + fprintf(stream, " Layer Vector ........... %" PRIu32 "\n", ntohl(packet->frame.vector)); + fprintf(stream, " Source Name ............ %s\n", packet->frame.source_name); + fprintf(stream, " Packet Priority ........ %" PRIu8 "\n", packet->frame.priority); + fprintf(stream, " Reserved ............... %" PRIu16 "\n", ntohs(packet->frame.reserved)); + fprintf(stream, " Sequence Number ........ %" PRIu8 "\n", packet->frame.seq_number); + fprintf(stream, " Options Flags .......... %" PRIu8 "\n", packet->frame.options); + fprintf(stream, " DMX Universe Number .... %" PRIu16 "\n", ntohs(packet->frame.universe)); + fprintf(stream, "[Device Management Protocol (DMP) Layer]\n"); + fprintf(stream, " Flags & Length ......... %" PRIu16 "\n", ntohs(packet->dmp.flength)); + fprintf(stream, " Layer Vector ........... %" PRIu8 "\n", packet->dmp.vector); + fprintf(stream, " Address & Data Type .... %" PRIu8 "\n", packet->dmp.type); + fprintf(stream, " First Address .......... %" PRIu16 "\n", ntohs(packet->dmp.first_addr)); + fprintf(stream, " Address Increment ...... %" PRIu16 "\n", ntohs(packet->dmp.addr_inc)); + fprintf(stream, " Property Value Count ... %" PRIu16 "\n", ntohs(packet->dmp.prop_val_cnt)); + fprintf(stream, "[DMP Property Values]\n "); + for (size_t pos=0, total=ntohs(packet->dmp.prop_val_cnt); posdmp.prop_val[pos]); + fprintf(stream, "\n"); + return 0; +} + +/* Return a string describing an E1.31 error */ +const char *e131_strerror(const e131_error_t error) { + switch (error) { + case E131_ERR_NONE: + return "Success"; + case E131_ERR_PREAMBLE_SIZE: + return "Invalid Preamble Size"; + case E131_ERR_POSTAMBLE_SIZE: + return "Invalid Post-amble Size"; + case E131_ERR_ACN_PID: + return "Invalid ACN Packet Identifier"; + case E131_ERR_VECTOR_ROOT: + return "Invalid Root Layer Vector"; + case E131_ERR_VECTOR_FRAME: + return "Invalid Framing Layer Vector"; + case E131_ERR_VECTOR_DMP: + return "Invalid Device Management Protocol (DMP) Layer Vector"; + case E131_ERR_TYPE_DMP: + return "Invalid DMP Address & Data Type"; + case E131_ERR_FIRST_ADDR_DMP: + return "Invalid DMP First Address"; + case E131_ERR_ADDR_INC_DMP: + return "Invalid DMP Address Increment"; + default: + return "Unknown error"; + } +} diff --git a/dependencies/libe131/src/e131.h b/dependencies/libe131/src/e131.h new file mode 100644 index 0000000..ad19241 --- /dev/null +++ b/dependencies/libe131/src/e131.h @@ -0,0 +1,168 @@ +/** + * E1.31 (sACN) library for C/C++ + * Hugo Hromic - http://github.com/hhromic + * + * Some content of this file is based on: + * https://github.com/forkineye/E131/blob/master/E131.h + * https://github.com/forkineye/E131/blob/master/E131.cpp + * + * Copyright 2016 Hugo Hromic + * + * 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 + * + * 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. + */ + +#ifndef _E131_H +#define _E131_H +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#else +#include +#endif + +#ifdef __GNUC__ +#define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__)) +#endif + +#ifdef _MSC_VER +#define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop)) +#endif + +#if defined(_MSC_VER) +#include +typedef SSIZE_T ssize_t; +#endif + +/* E1.31 Public Constants */ +extern const uint16_t E131_DEFAULT_PORT; +extern const uint8_t E131_DEFAULT_PRIORITY; + +/* E1.31 Socket Address Type */ +typedef struct sockaddr_in e131_addr_t; + +/* E1.31 Packet Type */ +/* All packet contents shall be transmitted in network byte order (big endian) */ +typedef union { + PACK(struct { + PACK(struct { /* ACN Root Layer: 38 bytes */ + uint16_t preamble_size; /* Preamble Size */ + uint16_t postamble_size; /* Post-amble Size */ + uint8_t acn_pid[12]; /* ACN Packet Identifier */ + uint16_t flength; /* Flags (high 4 bits) & Length (low 12 bits) */ + uint32_t vector; /* Layer Vector */ + uint8_t cid[16]; /* Component Identifier (UUID) */ + }) root; + + PACK(struct { /* Framing Layer: 77 bytes */ + uint16_t flength; /* Flags (high 4 bits) & Length (low 12 bits) */ + uint32_t vector; /* Layer Vector */ + uint8_t source_name[64]; /* User Assigned Name of Source (UTF-8) */ + uint8_t priority; /* Packet Priority (0-200, default 100) */ + uint16_t reserved; /* Reserved (should be always 0) */ + uint8_t seq_number; /* Sequence Number (detect duplicates or out of order packets) */ + uint8_t options; /* Options Flags (bit 7: preview data, bit 6: stream terminated) */ + uint16_t universe; /* DMX Universe Number */ + }) frame; + + PACK(struct { /* Device Management Protocol (DMP) Layer: 523 bytes */ + uint16_t flength; /* Flags (high 4 bits) / Length (low 12 bits) */ + uint8_t vector; /* Layer Vector */ + uint8_t type; /* Address Type & Data Type */ + uint16_t first_addr; /* First Property Address */ + uint16_t addr_inc; /* Address Increment */ + uint16_t prop_val_cnt; /* Property Value Count (1 + number of slots) */ + uint8_t prop_val[513]; /* Property Values (DMX start code + slots data) */ + }) dmp; + }); + + uint8_t raw[638]; /* raw buffer view: 638 bytes */ +} e131_packet_t; + +/* E1.31 Framing Options Type */ +typedef enum { + E131_OPT_TERMINATED = 6, + E131_OPT_PREVIEW = 7, +} e131_option_t; + +/* E1.31 Validation Errors Type */ +typedef enum { + E131_ERR_NONE, + E131_ERR_NULLPTR, + E131_ERR_PREAMBLE_SIZE, + E131_ERR_POSTAMBLE_SIZE, + E131_ERR_ACN_PID, + E131_ERR_VECTOR_ROOT, + E131_ERR_VECTOR_FRAME, + E131_ERR_VECTOR_DMP, + E131_ERR_TYPE_DMP, + E131_ERR_FIRST_ADDR_DMP, + E131_ERR_ADDR_INC_DMP, +} e131_error_t; + +/* Create a socket file descriptor suitable for E1.31 communication */ +extern int e131_socket(void); + +/* Bind a socket file descriptor to a port number for E1.31 communication */ +extern int e131_bind(int sockfd, const uint16_t port); + +/* Initialize a unicast E1.31 destination using a host and port number */ +extern int e131_unicast_dest(e131_addr_t *dest, const char *host, const uint16_t port); + +/* Initialize a multicast E1.31 destination using a universe and port number */ +extern int e131_multicast_dest(e131_addr_t *dest, const uint16_t universe, const uint16_t port); + +/* Describe an E1.31 destination into a string (must be at least 22 bytes) */ +extern int e131_dest_str(char *str, const e131_addr_t *dest); + +/* Join a socket file descriptor to an E1.31 multicast group using a universe */ +extern int e131_multicast_join(int sockfd, const uint16_t universe); + +/* Initialize an E1.31 packet using a universe and a number of slots */ +extern int e131_pkt_init(e131_packet_t *packet, const uint16_t universe, const uint16_t num_slots); + +/* Get the state of a framing option in an E1.31 packet */ +extern bool e131_get_option(const e131_packet_t *packet, const e131_option_t option); + +/* Set the state of a framing option in an E1.31 packet */ +extern int e131_set_option(e131_packet_t *packet, const e131_option_t option, const bool state); + +/* Send an E1.31 packet to a socket file descriptor using a destination */ +extern ssize_t e131_send(int sockfd, const e131_packet_t *packet, const e131_addr_t *dest); + +/* Receive an E1.31 packet from a socket file descriptor */ +extern ssize_t e131_recv(int sockfd, e131_packet_t *packet); + +/* Validate that an E1.31 packet is well-formed */ +extern e131_error_t e131_pkt_validate(const e131_packet_t *packet); + +/* Check if an E1.31 packet should be discarded (sequence number out of order) */ +extern bool e131_pkt_discard(const e131_packet_t *packet, const uint8_t last_seq_number); + +/* Dump an E1.31 packet to a stream (i.e. stdout, stderr) */ +extern int e131_pkt_dump(FILE *stream, const e131_packet_t *packet); + +/* Return a string describing an E1.31 error */ +extern const char *e131_strerror(const e131_error_t error); + +#ifdef __cplusplus +} +#endif +#endif