multithreaded app

This commit is contained in:
2021-07-28 21:26:40 +01:00
parent c2e37543ff
commit 71ccbeffe6
66 changed files with 2527 additions and 260 deletions

View File

@@ -0,0 +1,139 @@
#include "rpi_artnet.h"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include "rpi_artnet_utils.h"
int udpSocket = -1;
char buffer[1024];
bool isUdpListenerRunning = false;
pthread_t udpListener;
uint8_t artDmxBufferArray[16][512];
static void *artnet_udp_handler(void *arg);
void artnet_send_poll_reply(struct sockaddr_in srcAddr);
void artnet_init() {}
void start_artnet_bg_worker() {
isUdpListenerRunning = true;
if (pthread_create(&udpListener, NULL, artnet_udp_handler, NULL) < 0) {
perror("pthread_create");
}
}
void stop_artnet_bg_worker() {
isUdpListenerRunning = false;
if (pthread_join(udpListener, NULL) != 0) {
perror("pthread_join");
}
}
int artnet_get_dmx_data(unsigned int univerve, uint8_t **dmxData) {
if (univerve > 8) {
fprintf(stderr, "Universe %d out of bounds %d\n", univerve, 16);
*dmxData = NULL;
return -1;
}
*dmxData = artDmxBufferArray[univerve];
return 0;
}
static void *artnet_udp_handler(void *arg) {
struct sockaddr_in serverAddr;
struct pollfd fds[1];
int timeoutMs;
int ret;
int flags;
/* Create UDP socket */
udpSocket = socket(PF_INET, SOCK_DGRAM, 0);
if (udpSocket < 0) {
perror("Opening socket failed");
}
/* Set non-blocking socket */
flags = fcntl(udpSocket, F_GETFL, 0);
fcntl(udpSocket, F_SETFL, flags | O_NONBLOCK);
/* pollfd structure and timeout */
memset(fds, 0, sizeof(fds));
fds[0].fd = udpSocket;
fds[0].events = POLLIN;
timeoutMs = 10;
/* Configure settings in address struct */
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(ARTNET_PORT);
serverAddr.sin_addr.s_addr = inet_addr("0.0.0.0");
memset(serverAddr.sin_zero, '\0', sizeof(serverAddr.sin_zero));
/* Bind socket with address struct */
bind(udpSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
struct sockaddr_in srcAddr;
socklen_t srcLen = sizeof(srcAddr);
while (isUdpListenerRunning) {
if ((ret = poll(fds, 1, timeoutMs)) == 1) {
ssize_t bufferLen =
recvfrom(udpSocket, buffer, ARTNET_MAX_BUFFER, 0, (struct sockaddr *)&srcAddr, &srcLen);
if (bufferLen <= ARTNET_MAX_BUFFER && bufferLen > sizeof(artnetHeader_t)) {
artnetHeader_t *artnetHeader = (artnetHeader_t *)buffer;
if (memcmp(artnetHeader->id, ARTNET_ID, sizeof(ARTNET_ID)) == 0) {
switch (artnetHeader->opCode) {
case OpDmx:
if (bufferLen >= 20) {
artDmx_t *artDmx = (artDmx_t *)buffer;
uint16_t dmxLength = (artDmx->lengthHi << 8) | artDmx->lengthLo;
uint8_t *artDmxBuffer = artDmxBufferArray[artDmx->subUni & 0x00ff];
if (dmxLength <= 512) {
// store for later use
memcpy(artDmxBuffer, artDmx->data, dmxLength);
}
}
break;
case OpPoll:
artnet_send_poll_reply(srcAddr);
break;
default:
break;
}
}
}
} else if (ret < 0) {
fprintf(stderr, "error polling %d: %s\n", udpSocket, strerror(errno));
}
}
close(udpSocket);
return NULL;
}
void artnet_send_poll_reply(struct sockaddr_in srcAddr) {
/* Configure settings in address struct */
srcAddr.sin_family = AF_INET;
srcAddr.sin_port = htons(ARTNET_PORT);
memset(srcAddr.sin_zero, '\0', sizeof(srcAddr.sin_zero));
sendto(udpSocket, (uint8_t *)&artPollReply, sizeof(artPollReply_t), 0,
(struct sockaddr *)&srcAddr, sizeof(srcAddr));
}

View File

@@ -0,0 +1,14 @@
#if !defined(__RPI_ARTNET_H__)
#define __RPI_ARTNET_H__
#include "rpi_artnet_packets.h"
void artnet_init();
void start_artnet_bg_worker();
void stop_artnet_bg_worker();
int artnet_get_dmx_data(unsigned int univerve, uint8_t **dmxData);
#endif // __RPI_ARTNET_H__

View File

@@ -0,0 +1,128 @@
#if !defined(__RPI_ARTNET_OP_CODES_H__)
#define __RPI_ARTNET_OP_CODES_H__
/* List of hex values and discriptions of Opcodes */
/* This is an ArtPoll packet, no other data is contained in this UDP packet */
#define OpPoll 0x2000
/* This is an ArtPollReply Packet. It contains device status information. */
#define OpPollReply 0x2100
/* Diagnostics and data logging packet. */
#define OpDiagData 0x2300
/* Used to send text based parameter commands. */
#define OpCommand 0x2400
/* This is an ArtDmx data packet. It contains zero start code DMX512 information for a single
* Universe. */
#define OpOutput 0x5000
/* This is an ArtDmx data packet. It contains zero start code DMX512 information for a single
* Universe. */
#define OpDmx 0x5000
/* This is an ArtNzs data packet. It contains non-zero start code (except RDM) DMX512 information
* for a single Universe. */
#define OpNzs 0x5100
/* This is an ArtAddress packet. It contains remote programming information for a Node. */
#define OpAddress 0x6000
/* This is an ArtInput packet. It contains enable disable data for DMX inputs. */
#define OpInput 0x7000
/* This is an ArtTodRequest packet. It is used to request a Table of Devices (ToD) for RDM
* discovery. */
#define OpTodRequest 0x8000
/* This is an ArtTodData packet. It is used to send a Table of Devices (ToD) for RDM discovery. */
#define OpTodData 0x8100
/* This is an ArtTodControl packet. It is used to send RDM discovery control messages. */
#define OpTodControl 0x8200
/* This is an ArtRdm packet. It is used to send all non discovery RDM messages. */
#define OpRdm 0x8300
/* This is an ArtRdmSub packet. It is used to send compressed, RDM Sub-Device data. */
#define OpRdmSub 0x8400
/* This is an ArtVideoSetup packet. It contains video screen setup information for nodes that
* implement the extended video features. */
#define OpVideoSetup 0xa010
/* This is an ArtVideoPalette packet. It contains colour palette setup information for nodes that
* implement the extended video features. */
#define OpVideoPalette 0xa020
/* This is an ArtVideoData packet. It contains display data for nodes that implement the extended
* video features. */
#define OpVideoData 0xa040
/* This is an ArtMacMaster packet. It is used to program the Nodes MAC address, Oem device type and
* ESTA manufacturer code. This is for factory initialisation of a Node. It is not to be used by
* applications. */
#define OpMacMaster 0xf000
/* This is an ArtMacSlave packet. It is returned by the node to acknowledge receipt of an
* ArtMacMaster packet. */
#define OpMacSlave 0xf100
/* This is an ArtFirmwareMaster packet. It is used to upload new firmware or firmware extensions to
* the Node. */
#define OpFirmwareMaster 0xf200
/* This is an ArtFirmwareReply packet. It is returned by the node to acknowledge receipt of an
* ArtFirmwareMaster packet or ArtFileTnMaster packet. */
#define OpFirmwareReply 0xf300
/* Uploads user file to node. */
#define OpFileTnMaster 0xf400
/* Downloads user file from node. */
#define OpFileFnMaster 0xf500
/* Node acknowledge for downloads. */
#define OpFileFnReply 0xf600
/* This is an ArtIpProg packet. It is used to reprogramme the IP, Mask and Port address of the Node.
*/
#define OpIpProg 0xf800
/* This is an ArtIpProgReply packet. It is returned by the node to acknowledge receipt of an
* ArtIpProg packet. */
#define OpIpProgReply 0xf900
/* This is an ArtMedia packet. It is Unicast by a Media Server and acted upon by a Controller. */
#define OpMedia 0x9000
/* This is an ArtMediaPatch packet. It is Unicast by a Controller and acted upon by a Media Server.
*/
#define OpMediaPatch 0x9100
/* This is an ArtMediaControl packet. It is Unicast by a Controller and acted upon by a Media
* Server. */
#define OpMediaControl 0x9200
/* This is an ArtMediaControlReply packet. It is Unicast by a Media Server and acted upon by a
* Controller. */
#define OpMediaContrlReply 0x9300
/* This is an ArtTimeCode packet. It is used to transport time code over the network. */
#define OpTimeCode 0x9700
/* Used to synchronise real time date and clock */
#define OpTimeSync 0x9800
/* Used to send trigger macros */
#define OpTrigger 0x9900
/* Requests a node's file list */
#define OpDirectory 0x9a00
/* Replies to OpDirectory with file list */
#define OpDirectoryReply 0x9b00
#endif // __RPI_ARTNET_OP_CODES_H__

View File

@@ -0,0 +1,178 @@
#if !defined(__RPI_ARTNET_PACKETS_H__)
#define __RPI_ARTNET_PACKETS_H__
#include <stdint.h>
#include "rpi_artnet_op_codes.h"
#include "rpi_artnet_utils.h"
typedef struct {
char id[8];
uint16_t opCode;
} __attribute__((__packed__)) artnetHeader_t;
typedef struct {
artnetHeader_t artnetHeader;
uint16_t protVer;
uint8_t sequence;
uint8_t physical;
uint8_t subUni;
uint8_t net;
uint8_t lengthHi;
uint8_t lengthLo;
uint8_t data[512];
} __attribute__((__packed__)) artDmx_t;
typedef struct {
char ID[8];
uint16_t OpCode;
uint8_t IPAddr[4];
uint16_t Port;
uint16_t VersionInfo; // The node's current FIRMWARE VERS
// Bits 14-8 of the 15 bit universe number are encoded into the bottom 7 bits of this field.
uint8_t NetSwitch;
// This is used in combination with SubSwitch and Swin[] or Swout[] to produce the full universe
// address. Bits 7-4 of the 15 bit universe number are encoded into the bottom 4 bits of this
// field.
uint8_t SubSwitch;
uint16_t Oem; // Manufacturer code, bit 15 set if
// extended features avail
uint8_t UbeaVersion; // Firmware version of UBEA
uint8_t Status;
// bit 0 = 0 UBEA not present
// bit 0 = 1 UBEA present
// bit 1 = 0 Not capable of RDM (Uni-directional DMX)
// bit 1 = 1 Capable of RDM (Bi-directional DMX)
// bit 2 = 0 Booted from flash (normal boot)
// bit 2 = 1 Booted from ROM (possible error condition)
// bit 3 = Not used
// bit 54 = 00 Universe programming authority unknown
// bit 54 = 01 Universe programming authority set by front panel controls
// bit 54 = 10 Universe programming authority set by network
// bit 76 = 00 Indicators Normal
// bit 76 = 01 Indicators Locate
// bit 76 = 10 Indicators Mute
uint8_t EstaMan[2]; // ESTA manufacturer id, lo byte
char ShortName[18]; // short name defaults to IP
char LongName[64];
// Text feedback of Node status or errors also used for debug info
char NodeReport[64];
uint8_t NumPortsHi; // 0
uint8_t NumPortsLo; // 4 If num i/p ports is dif to output ports, return biggest
uint8_t PortTypes[4];
// bit 7 is output
// bit 6 is input
// bits 0-5 are protocol number (0= DMX, 1=MIDI)
// for DMX-Hub ={0xc0,0xc0,0xc0,0xc0};
uint8_t GoodInput[4];
// bit 7 is data received
// bit 6 is data includes test packets
// bit 5 is data includes SIP's
// bit 4 is data includes text
// bit 3 set is input is disabled
// bit 2 is receive errors
// bit 1-0 not used, transmitted as zero.
// Don't test for zero!
uint8_t GoodOutput[4];
// bit 7 is data is transmitting
// bit 6 is data includes test packets
// bit 5 is data includes SIP's
// bit 4 is data includes text
// bit 3 output is merging data.
// bit 2 set if DMX output short detected on power up
// bit 1 set if DMX output merge mode is LTP
// bit 0 not used, transmitted as zero.
uint8_t SwIn[4];
// Bits 3-0 of the 15 bit universe number are encoded into the low nibble
// This is used in combination with SubSwitch and NetSwitch to produce the full universe address.
// THIS IS FOR INPUT - ART-NET or DMX
// NB ON ART-NET II THESE 4 UNIVERSES WILL BE UNICAST TO.
uint8_t SwOut[4];
// Bits 3-0 of the 15 bit universe number are encoded into the low nibble
// This is used in combination with SubSwitch and NetSwitch to produce the full universe address.
// data belongs
// THIS IS FOR OUTPUT - ART-NET or DMX.
// NB ON ART-NET II THESE 4 UNIVERSES WILL BE UNICAST TO.
uint8_t SwVideo;
// Low nibble is the value of the video
// output channel
uint8_t SwMacro;
// Bit 0 is Macro input 1
// Bit 7 is Macro input 8
uint8_t SwRemote;
// Bit 0 is Remote input 1
// Bit 7 is Remote input 8
uint8_t Spare1; // Spare, currently zero
uint8_t Spare2; // Spare, currently zero
uint8_t Spare3; // Spare, currently zero
uint8_t Style; // Set to Style code to describe type of equipment
uint8_t Mac[6]; // Mac Address, zero if info not available
uint8_t BindIp[4]; // If this unit is part of a larger or modular product, this is the IP of the
// root device.
uint8_t BindIndex; // Set to zero if no binding, otherwise this number represents the order of
// bound devices. A lower number means closer to root device.
uint8_t Status2;
// bit 0 = 0 Node does not support web browser
// bit 0 = 1 Node supports web browser configuration
// bit 1 = 0 Node's IP address is manually configured
// bit 1 = 1 Node's IP address is DHCP configured
// bit 2 = 0 Node is not DHCP capable
// bit 2 = 1 Node is DHCP capable
// bit 2-7 not implemented, transmit as zero
uint8_t Filler[26]; // Filler bytes, currently zero.
} artPollReply_t;
static artPollReply_t artPollReply = {
.ID = "Art-Net",
.OpCode = OpPollReply,
.IPAddr = {0},
.Port = ARTNET_PORT,
.VersionInfo = 1,
.NetSwitch = 0,
.SubSwitch = 0,
.Oem = 0x0190,
.UbeaVersion = 0,
.Status = 0,
.EstaMan = {0, 0},
.ShortName = "Tropicananass",
.LongName = "Tropicananass Artnetnode",
.NodeReport = {0},
.NumPortsHi = 0,
.NumPortsLo = 1,
.PortTypes = {0x80, 0, 0, 0},
.GoodInput = {0},
.GoodOutput = {0},
.SwIn = {0},
.SwOut = {0},
.SwVideo = 0,
.SwMacro = 0,
.SwRemote = 0,
.Spare1 = 0,
.Spare2 = 0,
.Spare3 = 0,
.Style = 0,
.Mac = {0},
.BindIp = {0},
.BindIndex = 0,
.Status2 = 0b00000000,
.Filler = {0},
};
#endif // __RPI_ARTNET_PACKETS_H__

View File

@@ -0,0 +1,25 @@
#if !defined(__RPI_ARTNET_UTILS_H__)
#define __RPI_ARTNET_UTILS_H__
#define ARTNET_PORT 0x1936
// Buffers
#define ARTNET_MAX_BUFFER 530
#define DMX_MAX_BUFFER 512
// Packet constants
#define ARTNET_ID "Art-Net"
#define ARTNET_DMX_START_LOC 18
// Packet confines
#define ARTNET_SHORT_NAME_MAX_LENGTH 17
#define ARTNET_LONG_NAME_MAX_LENGTH 63
// DMX settings
#define DMX_MAX_OUTPUTS 4
#define DMX_MS_BETWEEN_TICKS 25
// RDM
#define DMX_RDM_STARTCODE 0xCC
#endif // __RPI_ARTNET_UTILS_H__

View File

@@ -0,0 +1,146 @@
#include "rpi_cava.h"
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <Windows.h>
#else
#include <unistd.h>
#endif
#define MAXLINECHAR 128 * (5 + 1) + 1
pid_t cavaPid;
bool isFifoReaderRunning = false;
pthread_t fifoReader;
int cavaFifo;
char lineBuffer[MAXLINECHAR];
uint16_t buffer[128 + 2];
static void *fifo_to_buffer(void *arg);
void setup_cava() {
if ((cavaPid = fork()) == -1) {
perror("fork");
exit(1);
}
if (cavaPid == 0) {
/* Child process*/
pthread_setschedprio(pthread_self(), 30);
char *args[] = {"/usr/local/bin/cava", "-p", "/home/pi/LedBars/RpiLedBars/cava_config", NULL};
if (execv(args[0], args) != 0) {
perror("execv");
}
} else {
sleep(1);
}
}
void close_cava() {
stop_cava_bg_worker();
kill(cavaPid, SIGTERM);
}
void start_cava_bg_worker() {
isFifoReaderRunning = true;
pthread_create(&fifoReader, NULL, fifo_to_buffer, NULL);
}
void stop_cava_bg_worker() {
isFifoReaderRunning = false;
if (pthread_join(fifoReader, NULL) != 0) {
perror("pthread_join");
}
}
int get_cava_buffer(uint16_t **buffer_dst) {
*buffer_dst = buffer;
return 0;
}
size_t valueIndex = 0, charOffset = 0;
char strValue[6] = "0\0";
bool hasToBeDiscarded = false;
static void *fifo_to_buffer(void *arg) {
struct pollfd fds[1];
int timeoutMs;
int ret;
if ((cavaFifo = open("/tmp/cava_output", O_RDONLY | O_NONBLOCK)) < 0) {
perror("open");
close_cava();
exit(1);
}
memset(fds, 0, sizeof(fds));
fds[0].fd = cavaFifo;
fds[0].events = POLLIN;
timeoutMs = 10;
hasToBeDiscarded = true;
while (isFifoReaderRunning) {
if ((ret = poll(fds, 1, timeoutMs)) == 1) {
int nread;
nread = read(cavaFifo, lineBuffer, 128 + 1);
if (nread >= 0) {
for (size_t i = 0; i < nread; ++i) {
char current = lineBuffer[i];
if (hasToBeDiscarded) {
if (current == '\n') {
charOffset = 0;
strValue[charOffset] = '\0';
valueIndex = 0;
hasToBeDiscarded = false;
}
} else {
if ('0' <= current && current <= '9') {
strValue[charOffset++] = current;
} else if (current == '\n' || current == ';') {
strValue[charOffset] = '\0';
charOffset = 0;
buffer[valueIndex++] = atoi(strValue);
if (current == '\n' || valueIndex > 129) {
valueIndex = 0;
if (valueIndex > 129) {
fprintf(stderr, "Buffer overflow, \\n missed, discarding next\n");
hasToBeDiscarded = true;
}
}
} else {
fprintf(stderr, "Unexpected char %d [%c]\n", current, current);
}
}
}
} else {
if (errno != EAGAIN) {
perror("read");
}
}
} else if (ret < 0) {
fprintf(stderr, "error polling %d: %s\n", cavaFifo, strerror(errno));
}
}
close(cavaFifo);
return NULL;
}

View File

@@ -0,0 +1,16 @@
#if !defined(__RPI_CAVA_H__)
#define __RPI_CAVA_H__
#include <stdint.h>
void setup_cava();
int get_cava_buffer(uint16_t **buffer_dst);
void close_cava();
void start_cava_bg_worker();
void stop_cava_bg_worker();
#endif /* __RPI_CAVA_H__ */