#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <time.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <poll.h>
#include <sys/types.h>
#include <error.h>
#include <fcntl.h>
#define termios asmtermios
#define winsize asmwinsize
#define termio asmtermio
#include <asm/termios.h>
#undef termios
#undef winsize
#undef termio
#define S0(x) (x ? x : "")
// start bit: low, 2 stop bits high
struct artnet {
char name[8]; // Art-Net\0
char opcode[2]; // 0x5000 in LE: { 0x00, 0x50 }
uint8_t version_hi; // 0
uint8_t version_lo; // 14
uint8_t sequence; // set to 0 to disable sequencing
uint8_t physical; // original universe
uint8_t sub_uni;
uint8_t net;
uint8_t length_hi;
uint8_t length_lo;
unsigned char data[512];
} __attribute__((packed));
int samomor = 0;
void handle_me (int s __attribute__((unused))) {
samomor++;
}
int rate (int what_rate, int uart) {
int r = 0;
struct termios2 uartattr2;
if (ioctl(uart, TCGETS2, &uartattr2) == -1) {
error_at_line(0, errno, __FILE__, __LINE__, "ioctl TCGETS2, fd: %d", uart);
r = 1;
goto r;
}
uartattr2.c_cflag &= ~CBAUD;
uartattr2.c_cflag |= BOTHER;
uartattr2.c_ispeed = what_rate;
uartattr2.c_ospeed = what_rate;
if (ioctl(uart, TCSETS2, &uartattr2) == -1) {
error_at_line(0, errno, __FILE__, __LINE__, "ioctl TCSETS2");
r = 2;
goto r;
}
r:
return r;
}
int main (int argc, char ** argv) {
int r = 0;
if (argc != 1+1)
error_at_line(1, 0, __FILE__, __LINE__, "usage: %s /dev/ttyUSB0", S0(argv[0]));
struct sigaction act = {
.sa_handler = handle_me,
.sa_flags = SA_RESTART
};
if (sigaction(SIGINT, &act, NULL) == -1)
error_at_line(2, errno, __FILE__, __LINE__, "sigaction");
int sock = -1;
int uart = open(argv[1], O_WRONLY | O_NOCTTY | O_NDELAY | O_CLOEXEC);
if (uart == -1)
error_at_line(3, errno, __FILE__, __LINE__, "open");
struct termios uartattr;
if (tcgetattr(uart, &uartattr) == -1) {
error_at_line(0, errno, __FILE__, __LINE__, "tcgetattr");
r = 4;
goto r;
}
uartattr.c_iflag = 0;
uartattr.c_oflag = 0;
uartattr.c_cflag = CS8 | CSTOPB;
uartattr.c_lflag = 0;
if (tcflush(uart, TCIOFLUSH) == -1) {
error_at_line(0, errno, __FILE__, __LINE__, "tcflush");
r = 5;
goto r;
}
if (tcsetattr(uart, TCSANOW, &uartattr) == -1) {
error_at_line(0, errno, __FILE__, __LINE__, "tcsetattr");
r = 6;
goto r;
}
if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
error_at_line(0, errno, __FILE__, __LINE__, "socket");
r = 7;
goto r;
}
int z = 1;
if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &z, sizeof z) == -1) {
error_at_line(0, errno, __FILE__, __LINE__, "setsockopt");
r = 8;
goto r;
}
struct sockaddr_in bind_address = {
.sin_family = AF_INET,
.sin_port = htons(6454),
.sin_addr = {
.s_addr = INADDR_ANY
}
};
if (bind(sock, (struct sockaddr *) &bind_address, sizeof bind_address) == -1) {
error_at_line(0, errno, __FILE__, __LINE__, "bind");
r = 9;
goto r;
}
long long last_dmx_burst = 0;
struct artnet udp;
while (!samomor) {
struct pollfd pollfds[2];
pollfds[0].fd = sock;
pollfds[0].events = POLLIN | POLLERR | POLLHUP | POLLNVAL;
// pollfds[1].fd = uart;
// pollfds[1].events = POLLOUT | POLLERR | POLLHUP | POLLNVAL;
int poll_return;
fprintf(stderr, "before poll\n");
if ((poll_return = poll(pollfds, 1 /* corr: I don't care about uart */, -1)) == -1) {
error_at_line(0, errno, __FILE__, __LINE__, "poll");
r = 10;
goto r;
}
fprintf(stderr, "after poll\n");
if (!poll_return) { // this can't happen, timeout is -1
error_at_line(0, 0, __FILE__, __LINE__, "this can't happen, poll returned 0 and timeout was -1\n");
r = 11;
goto r;
}
int both_events = pollfds[0].revents /* | pollfds[1].revents */;
if ((both_events & POLLERR) | (both_events & POLLHUP) | (both_events & POLLNVAL)) {
error_at_line(0, 0, __FILE__, __LINE__, "(both_events & POLLERR) | (both_events & POLLHUP) | (both_events & POLLNVAL)");
r = 12;
goto r;
}
struct sockaddr_in sender;
socklen_t sender_len = sizeof sender;
ssize_t bytes = recvfrom(sock, &udp, sizeof udp, MSG_DONTWAIT, (struct sockaddr *) &sender, &sender_len);
if (bytes == -1) {
error_at_line(0, errno, __FILE__, __LINE__, "recvfrom");
r = 13;
goto r;
} else {
fprintf(stderr, "received %ld bytes from %s:%d\n", bytes, inet_ntoa(sender.sin_addr), ntohs(sender.sin_port));
}
if (udp.opcode[0] != 0x00 || udp.opcode[1] != 0x50)
continue;
// if (bytes < 530)
// raise(SIGINT);
for (int i = 0; i < 512; i++)
if (udp.data[i] != 0)
fprintf(stderr, "\tDMX channel %d is %u\n", i+1, udp.data[i]);
struct timespec current_time;
if (clock_gettime(CLOCK_MONOTONIC, ¤t_time) == -1) {
error_at_line(0, errno, __FILE__, __LINE__, "clock_gettime");
r = 14;
goto r;
}
long long current_time_ms = current_time.tv_sec*1000+current_time.tv_nsec/1000000;
if (current_time_ms >= last_dmx_burst+50) {
last_dmx_burst = current_time_ms;
} else
continue;
if (tcflush(uart, TCIOFLUSH) == -1) {
error_at_line(0, errno, __FILE__, __LINE__, "tcflush");
r = 15;
goto r;
}
switch (rate(10000, uart)) {
case 1:
r = 16;
goto r;
break;
case 2:
r = 17;
goto r;
break;
default:
break;
}
if (tcflush(uart, TCIOFLUSH) == -1) {
error_at_line(0, errno, __FILE__, __LINE__, "tcflush");
r = 18;
goto r;
}
usleep(10000);
if (write(uart, "\0", 1) == -1) {
error_at_line(0, errno, __FILE__, __LINE__, "write packet start sequence");
r = 19;
goto r;
}
usleep(1000);
switch (rate(250000, uart)) {
case 1:
r = 20;
goto r;
break;
case 2:
r = 21;
goto r;
break;
default:
break;
}
udp.data[-1] = '\0';
#pragma GCC diagnostic ignored "-Wstringop-overread"
if (write(uart, udp.data-1, 513) == -1) {
error_at_line(0, errno, __FILE__, __LINE__, "write packet data");
r = 21;
goto r;
}
#pragma GCC diagnostic pop
}
r:
fprintf(stderr, "closing fds and returning\n");
if (close(uart) == -1)
error_at_line(22, errno, __FILE__, __LINE__, "close(uart)");
if (sock != -1)
if (close(sock) == -1)
error_at_line(23, errno, __FILE__, __LINE__, "close(sock)");
return r;
}