From a450f351c589ae6882f16f80418ae08d080bcf58 Mon Sep 17 00:00:00 2001 From: Eggert Jung Date: Wed, 17 Dec 2025 02:24:38 +0100 Subject: [PATCH] add vibecoded artnet lib --- draw_parra/artnet_receiver.c | 135 +++++++++++++++++++++++++++++++++++++++++++ draw_parra/artnet_receiver.h | 41 +++++++++++++ draw_parra/dmx2img.c | 35 +++++++---- draw_parra/makefile | 4 +- 4 files changed, 201 insertions(+), 14 deletions(-) create mode 100644 draw_parra/artnet_receiver.c create mode 100644 draw_parra/artnet_receiver.h diff --git a/draw_parra/artnet_receiver.c b/draw_parra/artnet_receiver.c new file mode 100644 index 0000000..1a655f0 --- /dev/null +++ b/draw_parra/artnet_receiver.c @@ -0,0 +1,135 @@ +/* -------------------------------------------------------------------- + * 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; +} diff --git a/draw_parra/artnet_receiver.h b/draw_parra/artnet_receiver.h new file mode 100644 index 0000000..f3c005a --- /dev/null +++ b/draw_parra/artnet_receiver.h @@ -0,0 +1,41 @@ +/* -------------------------------------------------------------------- + * artnet_receiver.c – tiny Art‑Net DMX receiver + * + * This file contains only the pieces you need to: + * * open a UDP socket on port 6454, + * * receive ArtDMX packets, + * * extract the DMX data (up to 512 bytes), + * * copy the first N bytes into the variables of the main program. + * + * The code is deliberately simple – it ignores ArtPoll, ArtSync, + * multiple universes, etc. If you need those features you can replace + * the receive loop with libartnet or another full implementation. + * -------------------------------------------------------------------- */ + +#ifndef ARTNET_RECEIVER_H +#define ARTNET_RECEIVER_H + +#include +#include +#include +#include + +/* --------------------------------------------------------------- + * Public API – call from the main program + * + * artnet_init() : creates the receiving thread. + * artnet_stop() : asks the thread to terminate and joins it. + * --------------------------------------------------------------- */ +bool artnet_init(void); /* returns true on success */ +void artnet_stop(void); + +/* --------------------------------------------------------------- + * The variables that the Art‑Net thread will write to. + * They are declared `extern` here and defined in the main file. + * --------------------------------------------------------------- */ +extern int16_t rotAnglesDeg[12]; /* 0 … 180° (mapped from 0‑255) */ +extern int LINE_THICKNESS; /* 1 … 10 (clamped) */ +extern int MARGIN_TOP; /* 0 … 200 px (scaled) */ +extern int MARGIN_BOTTOM; /* 0 … 200 px (scaled) */ + +#endif /* ARTNET_RECEIVER_H */ diff --git a/draw_parra/dmx2img.c b/draw_parra/dmx2img.c index e718328..6066b3f 100644 --- a/draw_parra/dmx2img.c +++ b/draw_parra/dmx2img.c @@ -23,6 +23,8 @@ #include #include +#include "artnet_receiver.h" + #define FB_W 800 /* framebuffer width */ #define FB_H 600 /* framebuffer height */ #define BPP 4 /* bytes per pixel (A,R,G,B) */ @@ -35,9 +37,9 @@ #define LINE_THICKNESS 4 /* thickness of outline (pixels) */ #define NUM_RECTS 12 -/*---------------------------------------------------------------*/ -/* 1. Per‑rectangle tilt angles (in degrees). Edit as you wish. */ -static int16_t rotAnglesDeg[NUM_RECTS] = { +///*---------------------------------------------------------------*/ +///* 1. Per‑rectangle tilt angles (in degrees). Edit as you wish. */ +int16_t rotAnglesDeg[NUM_RECTS] = { 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55 }; @@ -205,6 +207,15 @@ static int send_framebuffer(const uint8_t *fb, size_t len, /*---------------------------------------------------------------*/ int main(void) { + + /* --------------------------------------------------------------- + * 1️⃣ Start Art‑Net receiver – it will fill the globals + * --------------------------------------------------------------- */ + if (!artnet_init()) { + fprintf(stderr, "Failed to start Art‑Net receiver\n"); + return EXIT_FAILURE; + } + uint8_t *framebuf = malloc(FRAMEBUF_SIZE); if (!framebuf) { fprintf(stderr, "Failed to allocate framebuffer (%zu bytes)\n", @@ -213,17 +224,17 @@ int main(void) } while(1){ - for(uint8_t i=0; i