/* -------------------------------------------------------------------- * artnet_receiver.c – tiny Art‑Net DMX receiver * -------------------------------------------------------------------- */ #define _POSIX_C_SOURCE 200112L #include "artnet_receiver.h" #include #include #include #include #include #include #include #include #define ARTNET_PORT 6454 #define ARTNET_HEADER_SIZE 18 /* "Art-Net\0" + OpCode + … */ #define OP_DMX 0x5000 /* low‑byte first (little‑endian) */ /* --------------------------------------------------------------- */ static int artnet_sock = -1; static pthread_t receiver_thread; static volatile bool run_thread = false; /* ----------------------------------------------------------------- * Helper: map a DMX byte (0‑255) to a signed or unsigned integer * ----------------------------------------------------------------- */ static inline int map_range_uint8(uint8_t v, int dst_min, int dst_max) { return dst_min + (int)((v * (dst_max - dst_min)) / 255.0); } /* ----------------------------------------------------------------- * The thread that receives Art‑Net packets * ----------------------------------------------------------------- */ static void *receiver_func(void *arg) { (void)arg; /* unused */ struct sockaddr_in from; socklen_t fromlen = sizeof(from); uint8_t buf[1500]; /* big enough for a full packet */ while (run_thread) { ssize_t n = recvfrom(artnet_sock, buf, sizeof(buf), 0, (struct sockaddr *)&from, &fromlen); if (n < 0) { if (errno == EINTR) continue; perror("recvfrom"); continue; } /* ------------------ quick sanity checks ------------------- */ if (n < ARTNET_HEADER_SIZE) continue; /* too short */ if (memcmp(buf, "Art-Net\0", 8) != 0) continue; /* not Art‑Net */ uint16_t opcode = buf[8] | (buf[9] << 8); /* little‑endian */ if (opcode != OP_DMX) continue; /* not ArtDMX */ /* --------------------- DMX payload ----------------------- */ uint16_t universe = buf[14] | (buf[15] << 8); /* not used */ uint16_t length = buf[16] << 8 | buf[17]; /* big‑endian */ if (length + ARTNET_HEADER_SIZE != (size_t)n) continue; /* malformed */ uint8_t *dmx = buf + ARTNET_HEADER_SIZE; /* first DMX slot */ /* --------------------- update shared variables ------------ */ /* 0 … 11 → rotation angles (0‑180°) */ for (int i = 0; i < 12 && i < length; ++i) { rotAnglesDeg[i] = map_range_uint8(dmx[i], 0, 180); } ///* channel 12 → line thickness (1‑10) */ //if (length > 12) { // LINE_THICKNESS = map_range_uint8(dmx[12], 1, 10); //} ///* channel 13 → top margin (0‑200 px) */ //if (length > 13) { // MARGIN_TOP = map_range_uint8(dmx[13], 0, 200); //} ///* channel 14 → bottom margin (0‑200 px) */ //if (length > 14) { // MARGIN_BOTTOM = map_range_uint8(dmx[14], 0, 200); //} /* (Add more channels here if you need them) */ } return NULL; } /* ----------------------------------------------------------------- * Public API * ----------------------------------------------------------------- */ bool artnet_init(void) { struct sockaddr_in sa; artnet_sock = socket(AF_INET, SOCK_DGRAM, 0); if (artnet_sock < 0) { perror("socket"); return false; } memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_addr.s_addr = htonl(INADDR_ANY); sa.sin_port = htons(ARTNET_PORT); if (bind(artnet_sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) { perror("bind"); close(artnet_sock); artnet_sock = -1; return false; } run_thread = true; if (pthread_create(&receiver_thread, NULL, receiver_func, NULL) != 0) { perror("pthread_create"); close(artnet_sock); artnet_sock = -1; run_thread = false; return false; } return true; } void artnet_stop(void) { if (!run_thread) return; run_thread = false; /* Wake the thread if it is blocked in recvfrom */ shutdown(artnet_sock, SHUT_RDWR); pthread_join(receiver_thread, NULL); close(artnet_sock); artnet_sock = -1; }