├── autogen.sh ├── Makefile.am ├── configure.ac ├── examples ├── test_server.c └── test_client.c ├── .gitignore ├── src ├── e131.h └── e131.c ├── LICENSE └── README.md /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | autoreconf --install || exit 1 3 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | AM_CFLAGS = -Wall -Werror -std=gnu11 -pedantic 2 | ACLOCAL_AMFLAGS = -I m4 3 | 4 | dist_doc_DATA = LICENSE README.md 5 | 6 | lib_LTLIBRARIES = libe131.la 7 | libe131_la_SOURCES = src/e131.c 8 | libe131_la_LDFLAGS = -export-symbols-regex '^[eE]131_[^_]' 9 | include_HEADERS = src/e131.h 10 | 11 | # see: https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html 12 | # guide: for a library release `vX.Y.Z`, set `-version-info` to `X+Y:Z:Y` 13 | libe131_la_LDFLAGS += -version-info 5:0:4 14 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # -*- Autoconf -*- 2 | # Process this file with autoconf to produce a configure script. 3 | 4 | AC_PREREQ([2.71]) 5 | AC_INIT([libe131], [1.4.0], [hhromic@gmail.com]) 6 | AM_INIT_AUTOMAKE([foreign subdir-objects]) 7 | m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) 8 | LT_INIT 9 | AC_CONFIG_SRCDIR([src/e131.c]) 10 | AC_CONFIG_HEADERS([config.h]) 11 | AC_CONFIG_MACRO_DIRS([m4]) 12 | 13 | # Checks for programs. 14 | AC_PROG_CXX 15 | AC_PROG_AWK 16 | AC_PROG_CC 17 | AC_PROG_CPP 18 | AC_PROG_INSTALL 19 | AC_PROG_LN_S 20 | AC_PROG_MAKE_SET 21 | 22 | # Checks for libraries. 23 | 24 | # Checks for header files. 25 | AC_CHECK_HEADERS([arpa/inet.h inttypes.h netdb.h netinet/in.h stdint.h stdlib.h string.h sys/socket.h unistd.h]) 26 | 27 | # Checks for typedefs, structures, and compiler characteristics. 28 | AC_CHECK_HEADER_STDBOOL 29 | AC_TYPE_INT8_T 30 | AC_TYPE_SIZE_T 31 | AC_TYPE_SSIZE_T 32 | AC_TYPE_UINT16_T 33 | AC_TYPE_UINT32_T 34 | AC_TYPE_UINT8_T 35 | 36 | # Checks for library functions. 37 | AC_CHECK_FUNCS([getaddrinfo inet_ntoa memset socket]) 38 | 39 | AC_CONFIG_FILES([Makefile]) 40 | AC_OUTPUT 41 | -------------------------------------------------------------------------------- /examples/test_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int main() { 8 | int sockfd; 9 | e131_packet_t packet; 10 | e131_error_t error; 11 | uint8_t last_seq = 0x00; 12 | 13 | // create a socket for E1.31 14 | if ((sockfd = e131_socket()) < 0) 15 | err(EXIT_FAILURE, "e131_socket"); 16 | 17 | // bind the socket to the default E1.31 port 18 | if (e131_bind(sockfd, E131_DEFAULT_PORT) < 0) 19 | err(EXIT_FAILURE, "e131_bind"); 20 | 21 | // join the socket to multicast group for universe 1 on the default network interface 22 | if (e131_multicast_join_iface(sockfd, 1, 0) < 0) 23 | err(EXIT_FAILURE, "e131_multicast_join_iface"); 24 | 25 | // loop to receive E1.31 packets 26 | fprintf(stderr, "waiting for E1.31 packets ...\n"); 27 | for (;;) { 28 | if (e131_recv(sockfd, &packet) < 0) 29 | err(EXIT_FAILURE, "e131_recv"); 30 | if ((error = e131_pkt_validate(&packet)) != E131_ERR_NONE) { 31 | fprintf(stderr, "e131_pkt_validate: %s\n", e131_strerror(error)); 32 | continue; 33 | } 34 | if (e131_pkt_discard(&packet, last_seq)) { 35 | fprintf(stderr, "warning: packet out of order received\n"); 36 | last_seq = packet.frame.seq_number; 37 | continue; 38 | } 39 | e131_pkt_dump(stderr, &packet); 40 | last_seq = packet.frame.seq_number; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/test_client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main() { 9 | int sockfd; 10 | e131_packet_t packet; 11 | e131_addr_t dest; 12 | 13 | // create a socket for E1.31 14 | if ((sockfd = e131_socket()) < 0) 15 | err(EXIT_FAILURE, "e131_socket"); 16 | 17 | // configure socket to use the default network interface for outgoing multicast data 18 | if (e131_multicast_iface(sockfd, 0) < 0) 19 | err(EXIT_FAILURE, "e131_multicast_iface"); 20 | 21 | // initialize the new E1.31 packet in universe 1 with 24 slots in preview mode 22 | e131_pkt_init(&packet, 1, 24); 23 | memcpy(&packet.frame.source_name, "E1.31 Test Client", 18); 24 | if (e131_set_option(&packet, E131_OPT_PREVIEW, true) < 0) 25 | err(EXIT_FAILURE, "e131_set_option"); 26 | 27 | // set remote system destination as unicast address 28 | if (e131_unicast_dest(&dest, "127.0.0.1", E131_DEFAULT_PORT) < 0) 29 | err(EXIT_FAILURE, "e131_unicast_dest"); 30 | 31 | // loop to send cycling levels for each slot 32 | uint8_t level = 0; 33 | for (;;) { 34 | for (size_t pos=0; pos<24; pos++) 35 | packet.dmp.prop_val[pos + 1] = level; 36 | level++; 37 | if (e131_send(sockfd, &packet, &dest) < 0) 38 | err(EXIT_FAILURE, "e131_send"); 39 | e131_pkt_dump(stderr, &packet); 40 | packet.frame.seq_number++; 41 | usleep(250000); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/c,autotools 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=c,autotools 3 | 4 | ### Autotools ### 5 | # http://www.gnu.org/software/automake 6 | 7 | Makefile.in 8 | /ar-lib 9 | /mdate-sh 10 | /py-compile 11 | /test-driver 12 | /ylwrap 13 | .deps/ 14 | .dirstamp 15 | 16 | # http://www.gnu.org/software/autoconf 17 | 18 | autom4te.cache 19 | /autoscan.log 20 | /autoscan-*.log 21 | /aclocal.m4 22 | /compile 23 | /config.cache 24 | /config.guess 25 | /config.h.in 26 | /config.log 27 | /config.status 28 | /config.sub 29 | /configure 30 | /configure.scan 31 | /depcomp 32 | /install-sh 33 | /missing 34 | /stamp-h1 35 | 36 | # https://www.gnu.org/software/libtool/ 37 | 38 | /ltmain.sh 39 | 40 | # http://www.gnu.org/software/texinfo 41 | 42 | /texinfo.tex 43 | 44 | # http://www.gnu.org/software/m4/ 45 | 46 | m4/libtool.m4 47 | m4/ltoptions.m4 48 | m4/ltsugar.m4 49 | m4/ltversion.m4 50 | m4/lt~obsolete.m4 51 | 52 | # Generated Makefile 53 | # (meta build system like autotools, 54 | # can automatically generate from config.status script 55 | # (which is called by configure script)) 56 | Makefile 57 | 58 | ### Autotools Patch ### 59 | 60 | ### C ### 61 | # Prerequisites 62 | *.d 63 | 64 | # Object files 65 | *.o 66 | *.ko 67 | *.obj 68 | *.elf 69 | 70 | # Linker output 71 | *.ilk 72 | *.map 73 | *.exp 74 | 75 | # Precompiled Headers 76 | *.gch 77 | *.pch 78 | 79 | # Libraries 80 | *.lib 81 | *.a 82 | *.la 83 | *.lo 84 | 85 | # Shared objects (inc. Windows DLLs) 86 | *.dll 87 | *.so 88 | *.so.* 89 | *.dylib 90 | 91 | # Executables 92 | *.exe 93 | *.out 94 | *.app 95 | *.i*86 96 | *.x86_64 97 | *.hex 98 | 99 | # Debug files 100 | *.dSYM/ 101 | *.su 102 | *.idb 103 | *.pdb 104 | 105 | # Kernel Module Compile Results 106 | *.mod* 107 | *.cmd 108 | .tmp_versions/ 109 | modules.order 110 | Module.symvers 111 | Mkfile.old 112 | dkms.conf 113 | 114 | # End of https://www.toptal.com/developers/gitignore/api/c,autotools 115 | -------------------------------------------------------------------------------- /src/e131.h: -------------------------------------------------------------------------------- 1 | /** 2 | * E1.31 (sACN) library for C/C++ 3 | * Hugo Hromic - http://github.com/hhromic 4 | * 5 | * Some content of this file is based on: 6 | * https://github.com/forkineye/E131/blob/master/E131.h 7 | * https://github.com/forkineye/E131/blob/master/E131.cpp 8 | * 9 | * Copyright 2016 Hugo Hromic 10 | * 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | #ifndef _E131_H 25 | #define _E131_H 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #ifdef _WIN32 36 | #include 37 | #else 38 | #include 39 | #endif 40 | 41 | #ifdef __GNUC__ 42 | #define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__)) 43 | #endif 44 | 45 | #ifdef _MSC_VER 46 | #define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop)) 47 | #endif 48 | 49 | #ifdef _MSC_VER 50 | #include 51 | typedef SSIZE_T ssize_t; 52 | #endif 53 | 54 | /* E1.31 Public Constants */ 55 | extern const uint16_t E131_DEFAULT_PORT; 56 | extern const uint8_t E131_DEFAULT_PRIORITY; 57 | 58 | /* E1.31 Socket Address Type */ 59 | typedef struct sockaddr_in e131_addr_t; 60 | 61 | /* E1.31 Packet Type */ 62 | /* All packet contents shall be transmitted in network byte order (big endian) */ 63 | typedef union { 64 | PACK(struct { 65 | PACK(struct { /* ACN Root Layer: 38 bytes */ 66 | uint16_t preamble_size; /* Preamble Size */ 67 | uint16_t postamble_size; /* Post-amble Size */ 68 | uint8_t acn_pid[12]; /* ACN Packet Identifier */ 69 | uint16_t flength; /* Flags (high 4 bits) & Length (low 12 bits) */ 70 | uint32_t vector; /* Layer Vector */ 71 | uint8_t cid[16]; /* Component Identifier (UUID) */ 72 | }) root; 73 | 74 | PACK(struct { /* Framing Layer: 77 bytes */ 75 | uint16_t flength; /* Flags (high 4 bits) & Length (low 12 bits) */ 76 | uint32_t vector; /* Layer Vector */ 77 | uint8_t source_name[64]; /* User Assigned Name of Source (UTF-8) */ 78 | uint8_t priority; /* Packet Priority (0-200, default 100) */ 79 | uint16_t reserved; /* Reserved (should be always 0) */ 80 | uint8_t seq_number; /* Sequence Number (detect duplicates or out of order packets) */ 81 | uint8_t options; /* Options Flags (bit 7: preview data, bit 6: stream terminated) */ 82 | uint16_t universe; /* DMX Universe Number */ 83 | }) frame; 84 | 85 | PACK(struct { /* Device Management Protocol (DMP) Layer: 523 bytes */ 86 | uint16_t flength; /* Flags (high 4 bits) / Length (low 12 bits) */ 87 | uint8_t vector; /* Layer Vector */ 88 | uint8_t type; /* Address Type & Data Type */ 89 | uint16_t first_addr; /* First Property Address */ 90 | uint16_t addr_inc; /* Address Increment */ 91 | uint16_t prop_val_cnt; /* Property Value Count (1 + number of slots) */ 92 | uint8_t prop_val[513]; /* Property Values (DMX start code + slots data) */ 93 | }) dmp; 94 | }); 95 | 96 | uint8_t raw[638]; /* raw buffer view: 638 bytes */ 97 | } e131_packet_t; 98 | 99 | /* E1.31 Framing Options Type */ 100 | typedef enum { 101 | E131_OPT_TERMINATED = 6, 102 | E131_OPT_PREVIEW = 7, 103 | } e131_option_t; 104 | 105 | /* E1.31 Validation Errors Type */ 106 | typedef enum { 107 | E131_ERR_NONE, 108 | E131_ERR_NULLPTR, 109 | E131_ERR_PREAMBLE_SIZE, 110 | E131_ERR_POSTAMBLE_SIZE, 111 | E131_ERR_ACN_PID, 112 | E131_ERR_VECTOR_ROOT, 113 | E131_ERR_VECTOR_FRAME, 114 | E131_ERR_VECTOR_DMP, 115 | E131_ERR_TYPE_DMP, 116 | E131_ERR_FIRST_ADDR_DMP, 117 | E131_ERR_ADDR_INC_DMP, 118 | } e131_error_t; 119 | 120 | /* Create a socket file descriptor suitable for E1.31 communication */ 121 | extern int e131_socket(void); 122 | 123 | /* Bind a socket file descriptor to a port number for E1.31 communication */ 124 | extern int e131_bind(int sockfd, const uint16_t port); 125 | 126 | /* Initialize a unicast E1.31 destination using a host and port number */ 127 | extern int e131_unicast_dest(e131_addr_t *dest, const char *host, const uint16_t port); 128 | 129 | /* Initialize a multicast E1.31 destination using a universe and port number */ 130 | extern int e131_multicast_dest(e131_addr_t *dest, const uint16_t universe, const uint16_t port); 131 | 132 | /* Describe an E1.31 destination into a string (must be at least 22 bytes) */ 133 | extern int e131_dest_str(char *str, const e131_addr_t *dest); 134 | 135 | /* Configure a socket file descriptor to use a specific network interface for outgoing multicast data */ 136 | extern int e131_multicast_iface(int sockfd, const int ifindex); 137 | 138 | /* Join a socket file descriptor to an E1.31 multicast group using a universe */ 139 | extern int e131_multicast_join(int sockfd, const uint16_t universe); 140 | 141 | /* Join a socket file descriptor to an E1.31 multicast group using a universe and a specific network interface */ 142 | extern int e131_multicast_join_iface(int sockfd, const uint16_t universe, const int ifindex); 143 | 144 | /* Join a socket file descriptor to an E1.31 multicast group using a universe and an IP address to bind to */ 145 | extern int e131_multicast_join_ifaddr(int sockfd, const uint16_t universe, const char *ifaddr); 146 | 147 | /* Initialize an E1.31 packet using a universe and a number of slots */ 148 | extern int e131_pkt_init(e131_packet_t *packet, const uint16_t universe, const uint16_t num_slots); 149 | 150 | /* Get the state of a framing option in an E1.31 packet */ 151 | extern bool e131_get_option(const e131_packet_t *packet, const e131_option_t option); 152 | 153 | /* Set the state of a framing option in an E1.31 packet */ 154 | extern int e131_set_option(e131_packet_t *packet, const e131_option_t option, const bool state); 155 | 156 | /* Send an E1.31 packet to a socket file descriptor using a destination */ 157 | extern ssize_t e131_send(int sockfd, const e131_packet_t *packet, const e131_addr_t *dest); 158 | 159 | /* Receive an E1.31 packet from a socket file descriptor */ 160 | extern ssize_t e131_recv(int sockfd, e131_packet_t *packet); 161 | 162 | /* Validate that an E1.31 packet is well-formed */ 163 | extern e131_error_t e131_pkt_validate(const e131_packet_t *packet); 164 | 165 | /* Check if an E1.31 packet should be discarded (sequence number out of order) */ 166 | extern bool e131_pkt_discard(const e131_packet_t *packet, const uint8_t last_seq_number); 167 | 168 | /* Dump an E1.31 packet to a stream (i.e. stdout, stderr) */ 169 | extern int e131_pkt_dump(FILE *stream, const e131_packet_t *packet); 170 | 171 | /* Return a string describing an E1.31 error */ 172 | extern const char *e131_strerror(const e131_error_t error); 173 | 174 | #ifdef __cplusplus 175 | } 176 | #endif 177 | #endif 178 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Hugo Hromic 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/e131.c: -------------------------------------------------------------------------------- 1 | /** 2 | * E1.31 (sACN) library for C/C++ 3 | * Hugo Hromic - http://github.com/hhromic 4 | * 5 | * Some content of this file is based on: 6 | * https://github.com/forkineye/E131/blob/master/E131.h 7 | * https://github.com/forkineye/E131/blob/master/E131.cpp 8 | * 9 | * Copyright 2016 Hugo Hromic 10 | * 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #ifdef _WIN32 29 | #include 30 | #include 31 | #include 32 | #else 33 | #include 34 | #include 35 | #include 36 | #endif 37 | 38 | #include "e131.h" 39 | 40 | /* E1.31 Public Constants */ 41 | const uint16_t E131_DEFAULT_PORT = 5568; 42 | const uint8_t E131_DEFAULT_PRIORITY = 0x64; 43 | 44 | /* E1.31 Private Constants */ 45 | const uint16_t _E131_PREAMBLE_SIZE = 0x0010; 46 | const uint16_t _E131_POSTAMBLE_SIZE = 0x0000; 47 | const uint8_t _E131_ACN_PID[] = {0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00}; 48 | const uint32_t _E131_ROOT_VECTOR = 0x00000004; 49 | const uint32_t _E131_FRAME_VECTOR = 0x00000002; 50 | const uint8_t _E131_DMP_VECTOR = 0x02; 51 | const uint8_t _E131_DMP_TYPE = 0xa1; 52 | const uint16_t _E131_DMP_FIRST_ADDR = 0x0000; 53 | const uint16_t _E131_DMP_ADDR_INC = 0x0001; 54 | 55 | /* Create a socket file descriptor suitable for E1.31 communication */ 56 | int e131_socket(void) { 57 | #ifdef _WIN32 58 | WSADATA WsaData; 59 | WSAStartup(MAKEWORD(2, 2), &WsaData); 60 | #endif 61 | return socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); 62 | } 63 | 64 | /* Bind a socket file descriptor to a port number for E1.31 communication */ 65 | int e131_bind(int sockfd, const uint16_t port) { 66 | e131_addr_t addr; 67 | addr.sin_family = AF_INET; 68 | addr.sin_addr.s_addr = INADDR_ANY; 69 | addr.sin_port = htons(port); 70 | memset(addr.sin_zero, 0, sizeof addr.sin_zero); 71 | return bind(sockfd, (struct sockaddr *)&addr, sizeof addr); 72 | } 73 | 74 | /* Initialize a unicast E1.31 destination using a host and port number */ 75 | int e131_unicast_dest(e131_addr_t *dest, const char *host, const uint16_t port) { 76 | if (dest == NULL || host == NULL) { 77 | errno = EINVAL; 78 | return -1; 79 | } 80 | 81 | // get the address info of the host (the results are a linked list) 82 | struct addrinfo *ai, hints; 83 | memset(&hints, 0, sizeof hints); 84 | hints.ai_family = AF_INET; 85 | if (getaddrinfo(host, NULL, &hints, &ai) != 0) { 86 | errno = EADDRNOTAVAIL; 87 | return -1; 88 | } 89 | 90 | // for now, only use the first address info result 91 | dest->sin_family = AF_INET; 92 | dest->sin_addr = ((struct sockaddr_in *)ai->ai_addr)->sin_addr; 93 | dest->sin_port = htons(port); 94 | memset(dest->sin_zero, 0, sizeof dest->sin_zero); 95 | 96 | freeaddrinfo(ai); 97 | return 0; 98 | } 99 | 100 | /* Initialize a multicast E1.31 destination using a universe and port number */ 101 | int e131_multicast_dest(e131_addr_t *dest, const uint16_t universe, const uint16_t port) { 102 | if (dest == NULL || universe < 1 || universe > 63999) { 103 | errno = EINVAL; 104 | return -1; 105 | } 106 | dest->sin_family = AF_INET; 107 | dest->sin_addr.s_addr = htonl(0xefff0000 | universe); 108 | dest->sin_port = htons(port); 109 | memset(dest->sin_zero, 0, sizeof dest->sin_zero); 110 | return 0; 111 | } 112 | 113 | /* Describe an E1.31 destination into a string */ 114 | int e131_dest_str(char *str, const e131_addr_t *dest) { 115 | if (str == NULL || dest == NULL) { 116 | errno = EINVAL; 117 | return -1; 118 | } 119 | sprintf(str, "%s:%d", inet_ntoa(dest->sin_addr), ntohs(dest->sin_port)); 120 | return 0; 121 | } 122 | 123 | /* Configure a socket file descriptor to use a specific network interface for outgoing multicast data */ 124 | int e131_multicast_iface(int sockfd, const int ifindex) { 125 | #ifdef _WIN32 126 | if (ifindex != 0) { 127 | errno = ENOSYS; 128 | return -1; 129 | } 130 | struct ip_mreq mreq; 131 | mreq.imr_interface.s_addr = htonl(INADDR_ANY); 132 | #else 133 | struct ip_mreqn mreq; 134 | mreq.imr_address.s_addr = htonl(INADDR_ANY); 135 | mreq.imr_ifindex = ifindex; 136 | #endif 137 | return setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, (const void *) &mreq, sizeof mreq); 138 | } 139 | 140 | /* Join a socket file descriptor to an E1.31 multicast group using a universe */ 141 | int e131_multicast_join(int sockfd, const uint16_t universe) { 142 | return e131_multicast_join_iface(sockfd, universe, 0); 143 | } 144 | 145 | /* Join a socket file descriptor to an E1.31 multicast group using a universe and a specific network interface */ 146 | int e131_multicast_join_iface(int sockfd, const uint16_t universe, const int ifindex) { 147 | if (universe < 1 || universe > 63999) { 148 | errno = EINVAL; 149 | return -1; 150 | } 151 | #ifdef _WIN32 152 | if (ifindex != 0) { 153 | errno = ENOSYS; 154 | return -1; 155 | } 156 | struct ip_mreq mreq; 157 | mreq.imr_multiaddr.s_addr = htonl(0xefff0000 | universe); 158 | mreq.imr_interface.s_addr = htonl(INADDR_ANY); 159 | #else 160 | struct ip_mreqn mreq; 161 | mreq.imr_multiaddr.s_addr = htonl(0xefff0000 | universe); 162 | mreq.imr_address.s_addr = htonl(INADDR_ANY); 163 | mreq.imr_ifindex = ifindex; 164 | #endif 165 | return setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const void *) &mreq, sizeof mreq); 166 | } 167 | 168 | /* Join a socket file descriptor to an E1.31 multicast group using a universe and an IP address to bind to */ 169 | extern int e131_multicast_join_ifaddr(int sockfd, const uint16_t universe, const char *ifaddr) { 170 | if (universe < 1 || universe > 63999) { 171 | errno = EINVAL; 172 | return -1; 173 | } 174 | #ifdef _WIN32 175 | struct ip_mreq mreq; 176 | mreq.imr_multiaddr.s_addr = htonl(0xefff0000 | universe); 177 | mreq.imr_interface.s_addr = inet_addr(ifaddr); 178 | #else 179 | struct ip_mreqn mreq; 180 | mreq.imr_multiaddr.s_addr = htonl(0xefff0000 | universe); 181 | mreq.imr_address.s_addr = inet_addr(ifaddr); 182 | mreq.imr_ifindex = 0; 183 | #endif 184 | return setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const void *) &mreq, sizeof mreq); 185 | } 186 | 187 | /* Initialize an E1.31 packet using a universe and a number of slots */ 188 | int e131_pkt_init(e131_packet_t *packet, const uint16_t universe, const uint16_t num_slots) { 189 | if (packet == NULL || universe < 1 || universe > 63999 || num_slots < 1 || num_slots > 512) { 190 | errno = EINVAL; 191 | return -1; 192 | } 193 | 194 | // compute packet layer lengths 195 | uint16_t prop_val_cnt = num_slots + 1; 196 | uint16_t dmp_length = prop_val_cnt + 197 | sizeof packet->dmp - sizeof packet->dmp.prop_val; 198 | uint16_t frame_length = sizeof packet->frame + dmp_length; 199 | uint16_t root_length = sizeof packet->root.flength + 200 | sizeof packet->root.vector + sizeof packet->root.cid + frame_length; 201 | 202 | // clear packet 203 | memset(packet, 0, sizeof *packet); 204 | 205 | // set Root Layer values 206 | packet->root.preamble_size = htons(_E131_PREAMBLE_SIZE); 207 | packet->root.postamble_size = htons(_E131_POSTAMBLE_SIZE); 208 | memcpy(packet->root.acn_pid, _E131_ACN_PID, sizeof packet->root.acn_pid); 209 | packet->root.flength = htons(0x7000 | root_length); 210 | packet->root.vector = htonl(_E131_ROOT_VECTOR); 211 | 212 | // set Framing Layer values 213 | packet->frame.flength = htons(0x7000 | frame_length); 214 | packet->frame.vector = htonl(_E131_FRAME_VECTOR); 215 | packet->frame.priority = E131_DEFAULT_PRIORITY; 216 | packet->frame.universe = htons(universe); 217 | 218 | // set Device Management Protocol (DMP) Layer values 219 | packet->dmp.flength = htons(0x7000 | dmp_length); 220 | packet->dmp.vector = _E131_DMP_VECTOR; 221 | packet->dmp.type = _E131_DMP_TYPE; 222 | packet->dmp.first_addr = htons(_E131_DMP_FIRST_ADDR); 223 | packet->dmp.addr_inc = htons(_E131_DMP_ADDR_INC); 224 | packet->dmp.prop_val_cnt = htons(prop_val_cnt); 225 | 226 | return 0; 227 | } 228 | 229 | /* Get the state of a framing option in an E1.31 packet */ 230 | bool e131_get_option(const e131_packet_t *packet, const e131_option_t option) { 231 | if (packet != NULL && packet->frame.options & (1 << (option % 8))) 232 | return true; 233 | return false; 234 | } 235 | 236 | /* Set the state of a framing option in an E1.31 packet */ 237 | int e131_set_option(e131_packet_t *packet, const e131_option_t option, const bool state) { 238 | if (packet == NULL) { 239 | errno = EINVAL; 240 | return -1; 241 | } 242 | packet->frame.options ^= (-state ^ packet->frame.options) & (1 << (option % 8)); 243 | return 0; 244 | } 245 | 246 | /* Send an E1.31 packet to a socket file descriptor using a destination */ 247 | ssize_t e131_send(int sockfd, const e131_packet_t *packet, const e131_addr_t *dest) { 248 | if (packet == NULL || dest == NULL) { 249 | errno = EINVAL; 250 | return -1; 251 | } 252 | const size_t packet_length = sizeof packet->raw - 253 | sizeof packet->dmp.prop_val + htons(packet->dmp.prop_val_cnt); 254 | return sendto(sockfd, (const void *) packet->raw, packet_length, 0, 255 | (const struct sockaddr *)dest, sizeof *dest); 256 | } 257 | 258 | /* Receive an E1.31 packet from a socket file descriptor */ 259 | ssize_t e131_recv(int sockfd, e131_packet_t *packet) { 260 | if (packet == NULL) { 261 | errno = EINVAL; 262 | return -1; 263 | } 264 | return recv(sockfd, (void *) packet->raw, sizeof packet->raw, 0); 265 | } 266 | 267 | /* Validate that an E1.31 packet is well-formed */ 268 | e131_error_t e131_pkt_validate(const e131_packet_t *packet) { 269 | if (packet == NULL) 270 | return E131_ERR_NULLPTR; 271 | if (ntohs(packet->root.preamble_size) != _E131_PREAMBLE_SIZE) 272 | return E131_ERR_PREAMBLE_SIZE; 273 | if (ntohs(packet->root.postamble_size) != _E131_POSTAMBLE_SIZE) 274 | return E131_ERR_POSTAMBLE_SIZE; 275 | if (memcmp(packet->root.acn_pid, _E131_ACN_PID, sizeof packet->root.acn_pid) != 0) 276 | return E131_ERR_ACN_PID; 277 | if (ntohl(packet->root.vector) != _E131_ROOT_VECTOR) 278 | return E131_ERR_VECTOR_ROOT; 279 | if (ntohl(packet->frame.vector) != _E131_FRAME_VECTOR) 280 | return E131_ERR_VECTOR_FRAME; 281 | if (packet->dmp.vector != _E131_DMP_VECTOR) 282 | return E131_ERR_VECTOR_DMP; 283 | if (packet->dmp.type != _E131_DMP_TYPE) 284 | return E131_ERR_TYPE_DMP; 285 | if (htons(packet->dmp.first_addr) != _E131_DMP_FIRST_ADDR) 286 | return E131_ERR_FIRST_ADDR_DMP; 287 | if (htons(packet->dmp.addr_inc) != _E131_DMP_ADDR_INC) 288 | return E131_ERR_ADDR_INC_DMP; 289 | return E131_ERR_NONE; 290 | } 291 | 292 | /* Check if an E1.31 packet should be discarded (sequence number out of order) */ 293 | bool e131_pkt_discard(const e131_packet_t *packet, const uint8_t last_seq_number) { 294 | if (packet == NULL) 295 | return true; 296 | int8_t seq_num_diff = packet->frame.seq_number - last_seq_number; 297 | if (seq_num_diff > -20 && seq_num_diff <= 0) 298 | return true; 299 | return false; 300 | } 301 | 302 | /* Dump an E1.31 packet to a stream (i.e. stdout, stderr) */ 303 | int e131_pkt_dump(FILE *stream, const e131_packet_t *packet) { 304 | if (stream == NULL || packet == NULL) { 305 | errno = EINVAL; 306 | return -1; 307 | } 308 | fprintf(stream, "[Root Layer]\n"); 309 | fprintf(stream, " Preamble Size .......... %" PRIu16 "\n", ntohs(packet->root.preamble_size)); 310 | fprintf(stream, " Post-amble Size ........ %" PRIu16 "\n", ntohs(packet->root.postamble_size)); 311 | fprintf(stream, " ACN Packet Identifier .. %s\n", packet->root.acn_pid); 312 | fprintf(stream, " Flags & Length ......... %" PRIu16 "\n", ntohs(packet->root.flength)); 313 | #ifdef _WIN32 314 | fprintf(stream, " Layer Vector ........... %lu\n", ntohl(packet->root.vector)); 315 | #else 316 | fprintf(stream, " Layer Vector ........... %" PRIu32 "\n", ntohl(packet->root.vector)); 317 | #endif 318 | fprintf(stream, " Component Identifier ... "); 319 | for (size_t pos=0, total=sizeof packet->root.cid; posroot.cid[pos]); 321 | fprintf(stream, "\n"); 322 | fprintf(stream, "[Framing Layer]\n"); 323 | fprintf(stream, " Flags & Length ......... %" PRIu16 "\n", ntohs(packet->frame.flength)); 324 | #ifdef _WIN32 325 | fprintf(stream, " Layer Vector ........... %lu\n", ntohl(packet->frame.vector)); 326 | #else 327 | fprintf(stream, " Layer Vector ........... %" PRIu32 "\n", ntohl(packet->frame.vector)); 328 | #endif 329 | fprintf(stream, " Source Name ............ %s\n", packet->frame.source_name); 330 | fprintf(stream, " Packet Priority ........ %" PRIu8 "\n", packet->frame.priority); 331 | fprintf(stream, " Reserved ............... %" PRIu16 "\n", ntohs(packet->frame.reserved)); 332 | fprintf(stream, " Sequence Number ........ %" PRIu8 "\n", packet->frame.seq_number); 333 | fprintf(stream, " Options Flags .......... %" PRIu8 "\n", packet->frame.options); 334 | fprintf(stream, " DMX Universe Number .... %" PRIu16 "\n", ntohs(packet->frame.universe)); 335 | fprintf(stream, "[Device Management Protocol (DMP) Layer]\n"); 336 | fprintf(stream, " Flags & Length ......... %" PRIu16 "\n", ntohs(packet->dmp.flength)); 337 | fprintf(stream, " Layer Vector ........... %" PRIu8 "\n", packet->dmp.vector); 338 | fprintf(stream, " Address & Data Type .... %" PRIu8 "\n", packet->dmp.type); 339 | fprintf(stream, " First Address .......... %" PRIu16 "\n", ntohs(packet->dmp.first_addr)); 340 | fprintf(stream, " Address Increment ...... %" PRIu16 "\n", ntohs(packet->dmp.addr_inc)); 341 | fprintf(stream, " Property Value Count ... %" PRIu16 "\n", ntohs(packet->dmp.prop_val_cnt)); 342 | fprintf(stream, "[DMP Property Values]\n "); 343 | for (size_t pos=0, total=ntohs(packet->dmp.prop_val_cnt); posdmp.prop_val[pos]); 345 | fprintf(stream, "\n"); 346 | return 0; 347 | } 348 | 349 | /* Return a string describing an E1.31 error */ 350 | const char *e131_strerror(const e131_error_t error) { 351 | switch (error) { 352 | case E131_ERR_NONE: 353 | return "Success"; 354 | case E131_ERR_PREAMBLE_SIZE: 355 | return "Invalid Preamble Size"; 356 | case E131_ERR_POSTAMBLE_SIZE: 357 | return "Invalid Post-amble Size"; 358 | case E131_ERR_ACN_PID: 359 | return "Invalid ACN Packet Identifier"; 360 | case E131_ERR_VECTOR_ROOT: 361 | return "Invalid Root Layer Vector"; 362 | case E131_ERR_VECTOR_FRAME: 363 | return "Invalid Framing Layer Vector"; 364 | case E131_ERR_VECTOR_DMP: 365 | return "Invalid Device Management Protocol (DMP) Layer Vector"; 366 | case E131_ERR_TYPE_DMP: 367 | return "Invalid DMP Address & Data Type"; 368 | case E131_ERR_FIRST_ADDR_DMP: 369 | return "Invalid DMP First Address"; 370 | case E131_ERR_ADDR_INC_DMP: 371 | return "Invalid DMP Address Increment"; 372 | default: 373 | return "Unknown error"; 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libE131: a lightweight C/C++ library for the E1.31 (sACN) protocol 2 | 3 | This is a lightweight C/C++ library that provides a simple API for packet, client and server programming to be used for communicating with devices implementing the E1.31 (sACN) protocol. Detailed information about E.131 (sACN) can be found on this [Wiki article](https://www.doityourselfchristmas.com/wiki/index.php?title=E1.31_%28Streaming-ACN%29_Protocol) from which most informational content about E1.31 in this document comes from. 4 | 5 | ACN is a suite of protocols (via Ethernet) that is the industry standard for lighting and control. ESTA, the creator of the standard, ratified a subset of this protocol for "lightweight" devices which is called sACN (E1.31). This lightweight version allows microcontroller based lighting equipment to communicate via ethernet, without all the overhead of the full ACN protocol. 6 | 7 | The simplest way to think about E1.31 is that it is a way to transport a large number of lighting control channels over a traditional ethernet network connection. E1.31 transports those channels in "Universes", which is a collection of up to 512 channels together. E.131 is ethernet based and is the data sent via UDP. The two accepted transport methods are multicast and unicast, the most common implementation is multicast. When using multicast all the controller needs to do is subscribe to the multicast address for the universe that you want to receive data from. 8 | 9 | ## Installation 10 | 11 | To install libE131 in your system, download the [latest release archive](https://github.com/hhromic/libe131/releases/latest) and use the standard autotools approach: 12 | ``` 13 | ./configure --prefix=/usr \ 14 | && make \ 15 | && sudo make install 16 | ``` 17 | 18 | The last step requires `root` privileges. You can uninstall the library using: 19 | ``` 20 | sudo make uninstall 21 | ``` 22 | 23 | A development package for Arch Linux is also available on the [AUR](https://aur.archlinux.org/packages/libe131-git/). 24 | 25 | ## E1.31 (sACN) Packets 26 | 27 | E1.31 network packets are based on ACN and are composed of three layers: 28 | ``` 29 | E1.31 Packet = ACN Root Layer + Framing Layer + Device Management Protocol (DMP) Layer 30 | ``` 31 | 32 | All packet contents are transmitted in **network byte order (big endian)**. If you are accessing fields larger than one byte, you must read/write from/to them using the `ntohs`, `ntohl`, `htons` and `htonl` conversion functions. 33 | 34 | Fortunately, most of the time you will not need to manipulate those fields directly because libE131 provides convenience functions for the most common fields. See the API documentation section for more information. 35 | 36 | The following is the `e131_packet_t` type union provided by libE131 for sending/receiving E1.31 packets: 37 | ```c 38 | typedef union { 39 | PACK(struct { 40 | PACK(struct { /* ACN Root Layer: 38 bytes */ 41 | uint16_t preamble_size; /* Preamble Size */ 42 | uint16_t postamble_size; /* Post-amble Size */ 43 | uint8_t acn_pid[12]; /* ACN Packet Identifier */ 44 | uint16_t flength; /* Flags (high 4 bits) & Length (low 12 bits) */ 45 | uint32_t vector; /* Layer Vector */ 46 | uint8_t cid[16]; /* Component Identifier (UUID) */ 47 | }) root; 48 | 49 | PACK(struct { /* Framing Layer: 77 bytes */ 50 | uint16_t flength; /* Flags (high 4 bits) & Length (low 12 bits) */ 51 | uint32_t vector; /* Layer Vector */ 52 | uint8_t source_name[64]; /* User Assigned Name of Source (UTF-8) */ 53 | uint8_t priority; /* Packet Priority (0-200, default 100) */ 54 | uint16_t reserved; /* Reserved (should be always 0) */ 55 | uint8_t seq_number; /* Sequence Number (detect duplicates or out of order packets) */ 56 | uint8_t options; /* Options Flags (bit 7: preview data, bit 6: stream terminated) */ 57 | uint16_t universe; /* DMX Universe Number */ 58 | }) frame; 59 | 60 | PACK(struct { /* Device Management Protocol (DMP) Layer: 523 bytes */ 61 | uint16_t flength; /* Flags (high 4 bits) / Length (low 12 bits) */ 62 | uint8_t vector; /* Layer Vector */ 63 | uint8_t type; /* Address Type & Data Type */ 64 | uint16_t first_addr; /* First Property Address */ 65 | uint16_t addr_inc; /* Address Increment */ 66 | uint16_t prop_val_cnt; /* Property Value Count (1 + number of slots) */ 67 | uint8_t prop_val[513]; /* Property Values (DMX start code + slots data) */ 68 | }) dmp; 69 | }); 70 | 71 | uint8_t raw[638]; /* raw buffer view: 638 bytes */ 72 | } e131_packet_t; 73 | ``` 74 | 75 | This union provides two ways to access the data of an E1.31 packet: 76 | 77 | 1. Directly using the `raw` member (typically used for receiving data from the network). 78 | 2. Structurally using the `root`, `frame` and `dmp` members (typically used for processing a packet). 79 | 80 | You can easily create and initialize a new E1.31 packet to be used for sending using the `e131_pkt_init()` function. 81 | 82 | Refer to the [examples](#examples) section for how to use this data structure with libE131. 83 | 84 | ### Framing Layer Options 85 | 86 | The library provides two convenience functions, `e131_get_option()` and `e131_set_option()` to manipulate the options flag in the Framing Layer of an E1.31 packet. The following table describes the available option constants: 87 | 88 | | Option Constant | Description | 89 | |:----------------|:------------| 90 | | `E131_OPT_TERMINATED` | The current packet is the last one in the stream. The receiver should stop processing further packets. | 91 | | `E131_OPT_PREVIEW` | The data in the packet should be only used for preview purposes, e.g. console display, and not to drive live fixtures. | 92 | 93 | Refer to the [examples](#examples) section for how to use Framing Layer options with libE131. 94 | 95 | ### Packet Validation 96 | 97 | The library provides a convenience function, `e131_pkt_validate()`, to check if an E1.31 packet is valid to be processed by your application. This function returns a validation status from the `e131_error_t` enumeration. The following table describes the available error constants: 98 | 99 | | Error Constant | Description | 100 | |:---------------|:------------| 101 | | `E131_ERR_NONE` | Success (no validation error detected, you can process the packet). | 102 | | `E131_ERR_PREAMBLE_SIZE` | Invalid Preamble Size. | 103 | | `E131_ERR_POSTAMBLE_SIZE` | Invalid Post-amble Size. | 104 | | `E131_ERR_ACN_PID` | Invalid ACN Packet Identifier. | 105 | | `E131_ERR_VECTOR_ROOT` | Invalid Root Layer Vector. | 106 | | `E131_ERR_VECTOR_FRAME` | Invalid Framing Layer Vector. | 107 | | `E131_ERR_VECTOR_DMP` | Invalid Device Management Protocol (DMP) Layer Vector. | 108 | | `E131_ERR_TYPE_DMP` | Invalid DMP Address & Data Type. | 109 | | `E131_ERR_FIRST_ADDR_DMP` | Invalid DMP First Address. | 110 | | `E131_ERR_ADDR_INC_DMP` | Invalid DMP Address Increment. | 111 | 112 | The above error descriptions are also programatically obtainable using the `e131_strerror()` function. Refer to the [API documentation](#api-documentation) section for more information. 113 | 114 | Refer to the [examples](#examples) section for how to use packet validation and error reporting with libE131. 115 | 116 | ## Unicast and Multicast Destinations 117 | 118 | Most E1.31 software and hardware can be set up to communicate via two transport methods: **Unicast** and **Multicast**. 119 | 120 | ### Unicast Transmission 121 | 122 | Unicast is a method of sending data across a network where two devices, the control PC and the lighting controller, are directly connected (or through a network switch or router) and the channel control information meant for that specific controller is only sent to that controller. Unicast is a **point to point protocol** and the lighting channel information is only switched or routed to the device with the matching IP address. 123 | 124 | You must have unique IP address in each controller. Using Unicast the data packets are sent directly to the device instead of being broadcast across the entire subnet. 125 | 126 | In unicast transmission: 127 | 128 | * More channels of data are allowed to controllers and bridges (commonly 12 Universes vs. 7 for Multicast). 129 | * Channels can only be sent to one controller per Universe. 130 | 131 | ### Multicast Transmission 132 | 133 | Multicast is a method to send data across a network where a sender, typically a PC, broadcasts the data to all devices connected to the network subnet and the information about the channels are sent to all controllers connected to the network and every other device on the network. 134 | 135 | Multicast is a **point to multipoint broadcast** where the controllers need to listen to and only respond to information they are configured to use. Your PC sequencing software or streaming tool sends multicast packets with an address of `239.255..` where `UHB` is the Universe high byte and `LHB` is the Universe low byte. As an example, the address for universe `1` would be `239.255.0.1`. This is why using multicast addressing can be simpler to configure since this address is always the same for any device using that universe. 136 | 137 | Depending on the device and the number of universes of data sent it can swamp the device and possibly end up causing a loss of data. Note however that this is not an issue for most networks until you get into the dozens of universes so it's not an issue for most users. 138 | 139 | The data is received on multicast IP address not on the individual device IP address. 140 | 141 | In multicast transmission: 142 | 143 | * Less channels of data are allowed to controllers and bridges (commonly 7 Universes vs. 12 for Unicast). 144 | * Simpler network configuration since controllers don't need data address information. 145 | * Channels can be mirrored on multiple controllers since the same Universe can be used by multiple controllers. 146 | 147 | ## E1.31 (sACN) Universes 148 | 149 | E1.31 transports lighting information in "Universes", which is a collection of up to 512 channels together. You can chose any universe number from **1-63999** and assign it to a block of channels in your sequencing software. While a Universe can contain up to 512 channels, it does not have to, and can be any number between 1-512 channels. 150 | 151 | ## API Documentation 152 | 153 | ### Public Library Constants 154 | 155 | * *const uint16_t* `E131_DEFAULT_PORT`: default network port for E1.31 UDP data (5568). 156 | * *const uint8_t* `E131_DEFAULT_PRIORITY`: default E1.31 packet priority (100). 157 | 158 | ### Public Library Functions 159 | 160 | The library provides a number of functions to help you develop clients and servers that communicate using the E1.31 protocol. The following is a descriptive list of all functions provided. 161 | 162 | See the examples sections to see how the most common API functions are used with libE131. 163 | 164 | * `int e131_socket(void)`: Create a socket file descriptor suitable for E1.31 communication. On success, a file descriptor for the new socket is returned. On error, -1 is returned, and `errno` is set appropriately. 165 | 166 | * `int e131_bind(int sockfd, const uint16_t port)`: Bind a socket file descriptor to a port number for E1.31 communication. On success, zero is returned. On error, -1 is returned, and `errno` is set appropriately. 167 | 168 | * `int e131_unicast_dest(e131_addr_t *dest, const char *host, const uint16_t port)`: Initialize a unicast E1.31 destination using a host and port number. On success, zero is returned. On error, -1 is returned, and `errno` is set appropriately. 169 | 170 | * `int e131_multicast_dest(e131_addr_t *dest, const uint16_t universe, const uint16_t port)`: Initialize a multicast E1.31 destination using a universe and port number. On success, zero is returned. On error, -1 is returned, and `errno` is set appropriately. 171 | 172 | * `int e131_dest_str(char *str, const e131_addr_t *dest)`: Describe an E1.31 destination into a string (must be at least 22 bytes). On success, zero is returned. On error, -1 is returned, and `errno` is set appropriately. 173 | 174 | * `int e131_multicast_iface(int sockfd, const int ifindex)`: Configure a socket file descriptor to use a specific network interface for outgoing multicast data. Interface index zero is the system default interface. On error, -1 is returned, and `errno` is set appropriately. 175 | 176 | * `int e131_multicast_join(int sockfd, const uint16_t universe)`: Join a socket file descriptor to an E1.31 multicast group using a universe. On success, zero is returned. On error, -1 is returned, and `errno` is set appropriately. 177 | 178 | * `int e131_multicast_join_iface(int sockfd, const uint16_t universe, const int ifindex)`: Join a socket file descriptor to an E1.31 multicast group using a universe and a specific network interface. On error, -1 is returned, and `errno` is set appropriately. 179 | 180 | * `int e131_multicast_join_ifaddr(int sockfd, const uint16_t universe, const char *ifaddr)`: Join a socket file descriptor to an E1.31 multicast group using a universe and an IP address to bind to. On error, -1 is returned, and `errno` is set appropriately. 181 | 182 | * `int e131_pkt_init(e131_packet_t *packet, const uint16_t universe, const uint16_t num_slots)`: Initialize an E1.31 packet using a universe and a number of slots. On success, zero is returned. On error, -1 is returned, and `errno` is set appropriately. 183 | 184 | * `bool e131_get_option(const e131_packet_t *packet, const e131_option_t option)`: Get the state of a framing option in an E1.31 packet. 185 | 186 | * `int e131_set_option(e131_packet_t *packet, const e131_option_t option, const bool state)`: Set the state of a framing option in an E1.31 packet. On success, zero is returned. On error, -1 is returned, and `errno` is set appropriately. 187 | 188 | * `ssize_t e131_send(int sockfd, const e131_packet_t *packet, const e131_addr_t *dest)`: Send an E1.31 packet to a socket file descriptor using a destination. On success, the number of bytes sent is returned. On error, -1 is returned, and `errno` is set appropriately. 189 | 190 | * `ssize_t e131_recv(int sockfd, e131_packet_t *packet)`: Receive an E1.31 packet from a socket file descriptor. This function returns the number of bytes received, or -1 if an error occurred. On error, `errno` is set appropriately. 191 | 192 | * `e131_error_t e131_pkt_validate(const e131_packet_t *packet)`: Validate that an E1.31 packet is well-formed. See the Packet Validation section. 193 | 194 | * `bool e131_pkt_discard(const e131_packet_t *packet, const uint8_t last_seq_number)`: Check if an E1.31 packet should be discarded (sequence number out of order). This function uses the standard out-of-sequence detection algorithm defined in the E1.31 specification. 195 | 196 | * `int e131_pkt_dump(FILE *stream, const e131_packet_t *packet)`: Dump an E1.31 packet to a stream (i.e. stdout, stderr). The output is formatted for human readable output. On success, zero is returned. On error, -1 is returned, and `errno` is set appropriately. 197 | 198 | * `const char *e131_strdest(const e131_addr_t *dest)`: Return a string describing an E1.31 destination. 199 | 200 | * `const char *e131_strerror(const e131_error_t error)`: Return a string describing an E1.31 error. 201 | 202 | ## Examples 203 | 204 | The [`examples/test_client.c`](examples/test_client.c) example demonstrates how to create a simple E1.31 client. To compile it: 205 | ``` 206 | gcc -Wall test_client.c -o test_client -le131 207 | ``` 208 | 209 | The [`examples/test_server.c`](examples/test_server.c) example demonstrates how to create a simple E1.31 server. To compile it: 210 | ``` 211 | gcc -Wall test_server.c -o test_server -le131 212 | ``` 213 | 214 | ## Projects using libE131 215 | 216 | The following projects use libE131: 217 | 218 | * [E1.31 Xterm256 Console Viewer](https://github.com/hhromic/e131-viewer) 219 | * [E1.31 to AdaLight Bridge](https://github.com/hhromic/e131-adalight-bridge) 220 | * [E1.31 to MQTT Bridge](https://github.com/hhromic/e131-mqtt-bridge) 221 | * [MIDI to E1.31 Light Synthesizer](https://github.com/hhromic/midi-e131-synth) 222 | 223 | Also check out the [Node.js port](https://github.com/hhromic/e131-node) of libE131. 224 | 225 | ## License 226 | 227 | This software is under the **Apache License 2.0**. 228 | ``` 229 | Licensed under the Apache License, Version 2.0 (the "License"); 230 | you may not use this file except in compliance with the License. 231 | You may obtain a copy of the License at 232 | 233 | http://www.apache.org/licenses/LICENSE-2.0 234 | 235 | Unless required by applicable law or agreed to in writing, software 236 | distributed under the License is distributed on an "AS IS" BASIS, 237 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 238 | See the License for the specific language governing permissions and 239 | limitations under the License. 240 | ``` 241 | --------------------------------------------------------------------------------