Skip to content

Commit

Permalink
Merge pull request #15 from netmindz/DDP
Browse files Browse the repository at this point in the history
DDP Output
All looks good!
  • Loading branch information
ewoudwijma committed Aug 6, 2023
2 parents 6a94339 + dc8241f commit d0e19b9
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 1 deletion.
9 changes: 9 additions & 0 deletions .github/workflows/pio.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,14 @@ jobs:
python-version: '3.9'
- name: Install PlatformIO Core
run: pip install --upgrade platformio
- name: Setup Vars
run: git_hash=$(git rev-parse --short "$GITHUB_SHA")
- name: Build PlatformIO Project
run: pio run
- name: 'Upload Artifact'
uses: actions/upload-artifact@v3
with:
name: StarMod-esp32-${git_hash}.bin
path: .pio/build/esp32dev/firmware.bin
retention-days: 30

2 changes: 1 addition & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ lib_deps =

[appmod_leds]
build_flags =
-D APPMOD_LEDS
-D APPMOD_LEDS -D USERMOD_ARTNET -D USERMOD_DDP
lib_deps =
https://github.com/FastLED/FastLED.git

Expand Down
113 changes: 113 additions & 0 deletions src/User/UserModArtNet.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
@title StarMod
@file UserModArtNet.h
@date 20230730
@repo https://github.com/ewoudwijma/StarMod
@Authors https://github.com/ewoudwijma/StarMod/commits/main
@Copyright (c) 2023 Github StarMod Commit Authors
@license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007
*/

#define ARTNET_DEFAULT_PORT 6454

static const size_t ART_NET_HEADER_SIZE = 12;
static const byte ART_NET_HEADER[] PROGMEM = {0x41,0x72,0x74,0x2d,0x4e,0x65,0x74,0x00,0x00,0x50,0x00,0x0e};

class UserModArtNet:public Module {

public:

IPAddress targetIp;

UserModArtNet() :Module("ArtNet") {
print->print("%s %s\n", __PRETTY_FUNCTION__, name);

print->print("%s %s %s\n", __PRETTY_FUNCTION__, name, success?"success":"failed");
};

//setup filesystem
void setup() {
Module::setup();
print->print("%s %s\n", __PRETTY_FUNCTION__, name);
targetIp = IPAddress(192,168,178,161); // TODO allow setting at runtime
print->print("%s %s %s\n", __PRETTY_FUNCTION__, name, success?"success":"failed");
}

void connected() {
print->print("%s %s - Connected\n", __PRETTY_FUNCTION__, name);
isConnected = true;
}

void loop(){
// Module::loop();

if(!isConnected) return;

// calculate the number of UDP packets we need to send
bool isRGBW = false;

const size_t channelCount = ledsV.nrOfLedsP * (isRGBW?4:3); // 1 channel for every R,G,B,(W?) value
const size_t ARTNET_CHANNELS_PER_PACKET = isRGBW?512:510; // 512/4=128 RGBW LEDs, 510/3=170 RGB LEDs
const size_t packetCount = ((channelCount-1)/ARTNET_CHANNELS_PER_PACKET)+1;

uint32_t channel = 0;
size_t bufferOffset = 0;

sequenceNumber++;

WiFiUDP ddpUdp;

int bri = mdl->getValue("bri");

for (size_t currentPacket = 0; currentPacket < packetCount; currentPacket++) {

if (sequenceNumber > 255) sequenceNumber = 0;

if (!ddpUdp.beginPacket(targetIp, ARTNET_DEFAULT_PORT)) {
print->print("Art-Net WiFiUDP.beginPacket returned an error");
return; // borked
}

size_t packetSize = ARTNET_CHANNELS_PER_PACKET;

if (currentPacket == (packetCount - 1U)) {
// last packet
if (channelCount % ARTNET_CHANNELS_PER_PACKET) {
packetSize = channelCount % ARTNET_CHANNELS_PER_PACKET;
}
}

byte header_buffer[ART_NET_HEADER_SIZE];
memcpy_P(header_buffer, ART_NET_HEADER, ART_NET_HEADER_SIZE);
ddpUdp.write(header_buffer, ART_NET_HEADER_SIZE); // This doesn't change. Hard coded ID, OpCode, and protocol version.
ddpUdp.write(sequenceNumber & 0xFF); // sequence number. 1..255
ddpUdp.write(0x00); // physical - more an FYI, not really used for anything. 0..3
ddpUdp.write((currentPacket) & 0xFF); // Universe LSB. 1 full packet == 1 full universe, so just use current packet number.
ddpUdp.write(0x00); // Universe MSB, unused.
ddpUdp.write(0xFF & (packetSize >> 8)); // 16-bit length of channel data, MSB
ddpUdp.write(0xFF & (packetSize )); // 16-bit length of channel data, LSB

for (size_t i = 0; i < ledsV.nrOfLedsP; i++) {
CRGB pixel = ledsP[i];
ddpUdp.write(scale8(pixel.r, bri)); // R
ddpUdp.write(scale8(pixel.g, bri)); // G
ddpUdp.write(scale8(pixel.b, bri)); // B
// if (isRGBW) ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // W
}

if (!ddpUdp.endPacket()) {
print->print("Art-Net WiFiUDP.endPacket returned an error");
return; // borked
}
channel += packetSize;
}

}

private:
bool isConnected = false;
size_t sequenceNumber = 0;

};

static UserModArtNet *artnetmod;
135 changes: 135 additions & 0 deletions src/User/UserModDDP.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
@title StarMod
@file UserModArtNet.h
@date 20230730
@repo https://github.com/ewoudwijma/StarMod
@Authors https://github.com/ewoudwijma/StarMod/commits/main
@Copyright (c) 2023 Github StarMod Commit Authors
@license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007
*/
#define DDP_DEFAULT_PORT 4048
#define DDP_HEADER_LEN 10
#define DDP_SYNCPACKET_LEN 10

#define DDP_FLAGS1_VER 0xc0 // version mask
#define DDP_FLAGS1_VER1 0x40 // version=1
#define DDP_FLAGS1_PUSH 0x01
#define DDP_FLAGS1_QUERY 0x02
#define DDP_FLAGS1_REPLY 0x04
#define DDP_FLAGS1_STORAGE 0x08
#define DDP_FLAGS1_TIME 0x10

#define DDP_ID_DISPLAY 1
#define DDP_ID_CONFIG 250
#define DDP_ID_STATUS 251

// 1440 channels per packet
#define DDP_CHANNELS_PER_PACKET 1440 // 480 leds

#define DDP_TYPE_RGB24 0x0B // 00 001 011 (RGB , 8 bits per channel, 3 channels)
#define DDP_TYPE_RGBW32 0x1B // 00 011 011 (RGBW, 8 bits per channel, 4 channels)

class UserModDDP:public Module {

public:

IPAddress targetIp;

UserModDDP() :Module("DDP") {
print->print("%s %s\n", __PRETTY_FUNCTION__, name);

print->print("%s %s %s\n", __PRETTY_FUNCTION__, name, success?"success":"failed");
};

//setup filesystem
void setup() {
Module::setup();
print->print("%s %s\n", __PRETTY_FUNCTION__, name);
targetIp = IPAddress(192,168,178,161); // TODO allow setting at runtime
print->print("%s %s %s\n", __PRETTY_FUNCTION__, name, success?"success":"failed");
}

void connected() {
print->print("%s %s - Connected\n", __PRETTY_FUNCTION__, name);
isConnected = true;
}

void loop(){
// Module::loop();

if(!isConnected) return;

// calculate the number of UDP packets we need to send
bool isRGBW = false;

const size_t channelCount = ledsV.nrOfLedsP * (isRGBW? 4:3); // 1 channel for every R,G,B,(W?) value
const size_t packetCount = ((channelCount-1) / DDP_CHANNELS_PER_PACKET) +1;

uint32_t channel = 0;
size_t bufferOffset = 0;

sequenceNumber++;

WiFiUDP ddpUdp;

int bri = mdl->getValue("bri");

for (size_t currentPacket = 0; currentPacket < packetCount; currentPacket++) {

if (sequenceNumber > 15) sequenceNumber = 0;

if (!ddpUdp.beginPacket(targetIp, DDP_DEFAULT_PORT)) { // port defined in ESPAsyncE131.h
print->print("DDP WiFiUDP.beginPacket returned an error");
return; // borked
}

// the amount of data is AFTER the header in the current packet
size_t packetSize = DDP_CHANNELS_PER_PACKET;

uint8_t flags = DDP_FLAGS1_VER1;
if (currentPacket == (packetCount - 1U)) {
// last packet, set the push flag
// TODO: determine if we want to send an empty push packet to each destination after sending the pixel data
flags = DDP_FLAGS1_VER1 | DDP_FLAGS1_PUSH;
if (channelCount % DDP_CHANNELS_PER_PACKET) {
packetSize = channelCount % DDP_CHANNELS_PER_PACKET;
}
}

// write the header
/*0*/ddpUdp.write(flags);
/*1*/ddpUdp.write(sequenceNumber++ & 0x0F); // sequence may be unnecessary unless we are sending twice (as requested in Sync settings)
/*2*/ddpUdp.write(isRGBW ? DDP_TYPE_RGBW32 : DDP_TYPE_RGB24);
/*3*/ddpUdp.write(DDP_ID_DISPLAY);
// data offset in bytes, 32-bit number, MSB first
/*4*/ddpUdp.write(0xFF & (channel >> 24));
/*5*/ddpUdp.write(0xFF & (channel >> 16));
/*6*/ddpUdp.write(0xFF & (channel >> 8));
/*7*/ddpUdp.write(0xFF & (channel ));
// data length in bytes, 16-bit number, MSB first
/*8*/ddpUdp.write(0xFF & (packetSize >> 8));
/*9*/ddpUdp.write(0xFF & (packetSize ));

for (size_t i = 0; i < ledsV.nrOfLedsP; i++) {
CRGB pixel = ledsP[i];
ddpUdp.write(scale8(pixel.r, bri)); // R
ddpUdp.write(scale8(pixel.g, bri)); // G
ddpUdp.write(scale8(pixel.b, bri)); // B
// if (isRGBW) ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // W
}

if (!ddpUdp.endPacket()) {
print->print("DDP WiFiUDP.endPacket returned an error");
return; // problem
}
channel += packetSize;
}
}

private:
bool isConnected = false;
size_t sequenceNumber = 0;

};

static UserModDDP *ddpmod;
18 changes: 18 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
#ifdef APPMOD_LEDS
#include "App/AppModLeds.h"
#include "App/AppModLedFixGen.h"
#ifdef USERMOD_ARTNET
#include "User/UserModArtNet.h"
#endif
#ifdef USERMOD_DDP
#include "User/UserModDDP.h"
#endif
#endif
#ifdef USERMOD_E131
#include "User/UserModE131.h"
Expand All @@ -48,6 +54,12 @@ void setup() {
#ifdef APPMOD_LEDS
lds = new AppModLeds();
lfg = new AppModLedFixGen();
#ifdef USERMOD_ARTNET
artnetmod = new UserModArtNet();
#endif
#ifdef USERMOD_DDP
ddpmod = new UserModDDP();
#endif
#endif
#ifdef USERMOD_E131
e131mod = new UserModE131();
Expand All @@ -66,6 +78,12 @@ void setup() {
mdls->add(print);
#ifdef APPMOD_LEDS
mdls->add(lfg);
#ifdef USERMOD_ARTNET
mdls->add(artnetmod);
#endif
#ifdef USERMOD_DDP
mdls->add(ddpmod);
#endif
#endif
mdls->add(ui);
mdls->add(web);
Expand Down

0 comments on commit d0e19b9

Please sign in to comment.