Proper Art-Net Settings
This commit is contained in:
@@ -476,7 +476,10 @@ BusNetwork::BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) {
|
||||
_client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
|
||||
_broadcastLock = false;
|
||||
_valid = true;
|
||||
USER_PRINTF(" %u.%u.%u.%u] \n", bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
|
||||
_artnet_outputs = bc.artnet_outputs;
|
||||
_artnet_leds_per_output = bc.artnet_leds_per_output;
|
||||
_artnet_fps_limit = bc.artnet_fps_limit;
|
||||
USER_PRINTF(" %u.%u.%u.%u]\n", bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]);
|
||||
}
|
||||
|
||||
void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) {
|
||||
@@ -499,7 +502,7 @@ uint32_t BusNetwork::getPixelColor(uint16_t pix) const {
|
||||
void BusNetwork::show() {
|
||||
if (!_valid || !canShow()) return;
|
||||
_broadcastLock = true;
|
||||
realtimeBroadcast(_UDPtype, _client, _len, _data, _bri, _rgbw);
|
||||
realtimeBroadcast(_UDPtype, _client, _len, _data, _bri, _rgbw, _artnet_outputs, _artnet_leds_per_output, _artnet_fps_limit);
|
||||
_broadcastLock = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,12 +54,16 @@ struct BusConfig {
|
||||
uint8_t skipAmount;
|
||||
bool refreshReq;
|
||||
uint8_t autoWhite;
|
||||
uint8_t artnet_outputs, artnet_fps_limit;
|
||||
uint16_t artnet_leds_per_output;
|
||||
|
||||
uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; // WLEDMM warning: this means that BusConfig cannot handle nore than 5 pins per bus!
|
||||
uint16_t frequency;
|
||||
BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U) {
|
||||
BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, uint8_t art_o=1, uint16_t art_l=1, uint8_t art_f=30) {
|
||||
refreshReq = (bool) GET_BIT(busType,7);
|
||||
type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh)
|
||||
count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip; autoWhite = aw; frequency = clock_kHz;
|
||||
artnet_outputs = art_o; artnet_leds_per_output = art_l; artnet_fps_limit = art_f;
|
||||
uint8_t nPins = 1; // default = only one pin (clockless LEDs like WS281x)
|
||||
if ((type >= TYPE_NET_DDP_RGB) && (type < (TYPE_NET_DDP_RGB + 16))) nPins = 4; // virtual network bus. 4 "pins" store IP address
|
||||
else if ((type > 47) && (type < 63)) nPins = 2; // (data + clock / SPI) busses - two pins
|
||||
@@ -144,6 +148,9 @@ class Bus {
|
||||
virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; }
|
||||
virtual uint8_t skippedLeds() const { return 0; }
|
||||
virtual uint16_t getFrequency() const { return 0U; }
|
||||
virtual uint8_t get_artnet_fps_limit() const { return 0; }
|
||||
virtual uint8_t get_artnet_outputs() const { return 0; }
|
||||
virtual uint16_t get_artnet_leds_per_output() const { return 0; }
|
||||
inline uint16_t getStart() const { return _start; }
|
||||
inline void setStart(uint16_t start) { _start = start; }
|
||||
inline uint8_t getType() const { return _type; }
|
||||
@@ -348,12 +355,30 @@ class BusNetwork : public Bus {
|
||||
return !_broadcastLock;
|
||||
}
|
||||
|
||||
uint8_t getPins(uint8_t* pinArray) const;
|
||||
uint8_t getPins(uint8_t* pinArray) const;
|
||||
|
||||
uint16_t getLength() const override {
|
||||
uint16_t getLength() const override {
|
||||
return _len;
|
||||
}
|
||||
|
||||
uint8_t get_artnet_fps_limit() const override {
|
||||
return _artnet_fps_limit;
|
||||
}
|
||||
|
||||
uint8_t get_artnet_outputs() const override {
|
||||
return _artnet_outputs;
|
||||
}
|
||||
|
||||
uint16_t get_artnet_leds_per_output() const override {
|
||||
return _artnet_leds_per_output;
|
||||
}
|
||||
|
||||
void setColorOrder(uint8_t colorOrder);
|
||||
|
||||
uint8_t getColorOrder() const {
|
||||
return _colorOrder;
|
||||
}
|
||||
|
||||
void cleanup();
|
||||
|
||||
~BusNetwork() {
|
||||
@@ -361,12 +386,17 @@ class BusNetwork : public Bus {
|
||||
}
|
||||
|
||||
private:
|
||||
IPAddress _client;
|
||||
uint8_t _UDPtype;
|
||||
uint8_t _UDPchannels;
|
||||
bool _rgbw;
|
||||
bool _broadcastLock;
|
||||
byte *_data;
|
||||
IPAddress _client;
|
||||
uint8_t _UDPtype;
|
||||
uint8_t _UDPchannels;
|
||||
bool _rgbw;
|
||||
bool _broadcastLock;
|
||||
byte *_data;
|
||||
uint8_t _colorOrder = COL_ORDER_RGB;
|
||||
uint8_t _artnet_fps_limit;
|
||||
uint8_t _artnet_outputs;
|
||||
uint16_t _artnet_leds_per_output;
|
||||
const ColorOrderMap &_colorOrderMap;
|
||||
};
|
||||
|
||||
#ifdef WLED_ENABLE_HUB75MATRIX
|
||||
|
||||
@@ -194,13 +194,16 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM (not yet implemented fully)
|
||||
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
|
||||
uint8_t AWmode = elm[F("rgbwm")] | RGBW_MODE_MANUAL_ONLY;
|
||||
uint8_t artnet_outputs = elm["artnet_outputs"] | 0;
|
||||
uint16_t artnet_leds_per_output = elm["artnet_leds_per_output"] | 0;
|
||||
uint8_t artnet_fps_limit = elm["artnet_fps_limit"] | 0;
|
||||
if (fromFS) {
|
||||
BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz);
|
||||
BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, artnet_outputs, artnet_leds_per_output, artnet_fps_limit);
|
||||
mem += BusManager::memUsage(bc);
|
||||
if (mem <= MAX_LED_MEMORY) if (busses.add(bc) == -1) break; // finalization will be done in WLED::beginStrip()
|
||||
} else {
|
||||
if (busConfigs[s] != nullptr) delete busConfigs[s];
|
||||
busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode);
|
||||
busConfigs[s] = new BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, artnet_outputs, artnet_leds_per_output, artnet_fps_limit);
|
||||
busesChanged = true;
|
||||
}
|
||||
s++;
|
||||
@@ -829,6 +832,9 @@ void serializeConfig() {
|
||||
ins["ref"] = bus->isOffRefreshRequired();
|
||||
ins[F("rgbwm")] = bus->getAutoWhiteMode();
|
||||
ins[F("freq")] = bus->getFrequency();
|
||||
ins["artnet_outputs"] = bus->get_artnet_outputs();
|
||||
ins["artnet_fps_limit"] = bus->get_artnet_fps_limit();
|
||||
ins["artnet_leds_per_output"] = bus->get_artnet_leds_per_output();
|
||||
}
|
||||
|
||||
JsonArray hw_com = hw.createNestedArray(F("com"));
|
||||
|
||||
@@ -219,6 +219,30 @@
|
||||
gRGBW |= isRGBW = ((t > 17 && t < 22) || (t > 28 && t < 32) || (t > 40 && t < 46 && t != 43) || t == 88); // RGBW checkbox, TYPE_xxxx values from const.h
|
||||
gId("co"+n).style.display = ((t >= 80 && t < 96) || (t >= 40 && t < 48)||(t >= 100 && t < 110)) ? "none":"inline"; // hide color order for PWM
|
||||
gId("dig"+n+"w").style.display = (t > 28 && t < 32) ? "inline":"none"; // show swap channels dropdown
|
||||
gId("dig"+n+"O").style.display = (t == 82 || t == 83) ? "inline":"none"; // show Art-Net output number
|
||||
gId("dig"+n+"L").style.display = (t == 82 || t == 83) ? "inline":"none"; // show Art-Net LEDs per output
|
||||
gId("dig"+n+"F").style.display = (t == 82 || t == 83) ? "inline":"none"; // show Art-Net FPS limiter
|
||||
if (gId("dig"+n+"F").style.display == "inline") {
|
||||
total_leds = d.getElementsByName("LC"+n)[0].value;
|
||||
outputs = d.getElementsByName("AO"+n)[0].value;
|
||||
leds_per_output = d.getElementsByName("AL"+n)[0].value;
|
||||
fps_limit = d.getElementsByName("AF"+n)[0].value;
|
||||
last_octet = d.getElementsByName("L3"+n)[0].value;
|
||||
if (outputs > 1) {
|
||||
if (t == 82) gId("dig"+n+"W").innerHTML = "<br />Set your Art-Net Hardware to "+Math.ceil(leds_per_output/170)+" universes per output.";
|
||||
if (t == 83) gId("dig"+n+"W").innerHTML = "<br />Set your Art-Net Hardware to "+Math.ceil(leds_per_output/128)+" universes per output.";
|
||||
} else if (outputs == 1) {
|
||||
gId("dig"+n+"W").innerHTML = "<br />WLED-style Art-Net output enabled.";
|
||||
} else {
|
||||
gId("dig"+n+"W").innerHTML = "<br />You need at least 1 output!";
|
||||
}
|
||||
if (outputs > 1 && fps_limit > 33333/leds_per_output) gId("dig"+n+"W").innerHTML += "<br />FPS limit may be too high for WS281x pixels.";
|
||||
if (outputs*leds_per_output != total_leds) gId("dig"+n+"W").innerHTML += "<br />Total LEDs doesn't match outputs * LEDs per output.";
|
||||
if (last_octet == 255) {
|
||||
if (total_leds <= 1024) gId("dig"+n+"W").innerHTML += "<br />Art-Net is in broadcast mode.";
|
||||
if (total_leds > 1024) gId("dig"+n+"W").innerHTML += "<br />You are sending a lot of broadcast data. Be cautious.";
|
||||
}
|
||||
}
|
||||
if (!(t > 28 && t < 32)) d.getElementsByName("WO"+n)[0].value = 0; // reset swapping
|
||||
gId("dig"+n+"c").style.display = ((t >= 40 && t < 48)||(t >= 100 && t < 110)) ? "none":"inline"; // hide count for analog and HUB75
|
||||
gId("dig"+n+"r").style.display = (t >= 80) && (t < 100) ? "none":"inline"; // hide reversed for virtual, except for HUB75
|
||||
@@ -314,6 +338,7 @@
|
||||
gId("lc").textContent = sLC;
|
||||
gId("pc").textContent = (sLC == sPC) ? "":"(" + sPC + " physical)";
|
||||
|
||||
|
||||
// memory usage and warnings
|
||||
gId('m0').innerHTML = memu;
|
||||
bquot = memu / maxM * 100;
|
||||
@@ -420,6 +445,10 @@ ${i+1}:
|
||||
<span id="p2d${i}"></span><input type="number" name="L2${i}" class="s" onchange="UI()"/>
|
||||
<span id="p3d${i}"></span><input type="number" name="L3${i}" class="s" onchange="UI()"/>
|
||||
<span id="p4d${i}"></span><input type="number" name="L4${i}" class="s" onchange="UI()"/>
|
||||
<div id="dig${i}O" style="display:inline"><br>Number of Outputs: <input type="number" name="AO${i}" min="1" max="255" value="1" oninput="UI()"></div>
|
||||
<div id="dig${i}L" style="display:inline"><br>LEDs Per Output: <input type="number" name="AL${i}" min="1" max="65535" value="1" oninput="UI()"></div>
|
||||
<div id="dig${i}F" style="display:inline"><br>FPS Limit: <input type="number" name="AF${i}" min="1" max="255" value="30" oninput="UI()"></div>
|
||||
<div id="dig${i}W" style="display:inline"><br>Art-Net Warnings Go Here.</div>
|
||||
<div id="dig${i}r" style="display:inline"><br><span id="rev${i}">Reversed</span>: <input type="checkbox" name="CV${i}"></div>
|
||||
<div id="dig${i}s" style="display:inline"><br>Skip first LEDs: <input type="number" name="SL${i}" min="0" max="255" value="0" oninput="UI()"></div>
|
||||
<div id="dig${i}f" style="display:inline"><br><span id="ref${i}">Off Refresh</span>: <input id="rf${i}" type="checkbox" name="RF${i}"></div>
|
||||
|
||||
@@ -241,7 +241,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=tru
|
||||
|
||||
//udp.cpp
|
||||
void notify(byte callMode, bool followUp=false);
|
||||
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri=255, bool isRGBW=false);
|
||||
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri=255, bool isRGBW=false, uint8_t artnet_outouts=1, uint16_t artnet_leds_per_output=1, uint8_t artnet_fps_limit=1);
|
||||
void realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC);
|
||||
void exitRealtime();
|
||||
void handleNotifications();
|
||||
|
||||
@@ -95,8 +95,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t colorOrder, type, skip, awmode, channelSwap;
|
||||
uint16_t length, start;
|
||||
uint8_t colorOrder, type, skip, awmode, channelSwap, artnet_outputs, artnet_fps_limit;
|
||||
uint16_t length, start, artnet_leds_per_output;
|
||||
uint8_t pins[5] = {255, 255, 255, 255, 255};
|
||||
|
||||
autoSegments = request->hasArg(F("MS"));
|
||||
@@ -110,6 +110,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
|
||||
bool busesChanged = false;
|
||||
for (uint8_t s = 0; s < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; s++) {
|
||||
// "48+s" means the ASCII character "0", so 48+1 = ASCII for "1", etc - and "[3]=0" means null-terminate the string.
|
||||
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
|
||||
char lc[4] = "LC"; lc[2] = 48+s; lc[3] = 0; //strip length
|
||||
char co[4] = "CO"; co[2] = 48+s; co[3] = 0; //strip color order
|
||||
@@ -121,6 +122,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
char aw[4] = "AW"; aw[2] = 48+s; aw[3] = 0; //auto white mode
|
||||
char wo[4] = "WO"; wo[2] = 48+s; wo[3] = 0; //channel swap
|
||||
char sp[4] = "SP"; sp[2] = 48+s; sp[3] = 0; //bus clock speed (DotStar & PWM)
|
||||
char ao[4] = "AO"; ao[2] = 48+s; ao[3] = 0; //Art-Net outputs
|
||||
char al[4] = "AL"; al[2] = 48+s; al[3] = 0; //Art-Net LEDs per output
|
||||
char af[4] = "AF"; af[2] = 48+s; af[3] = 0; //Art-Net FPS limit
|
||||
if (!request->hasArg(lp)) {
|
||||
DEBUG_PRINT(F("No data for "));
|
||||
DEBUG_PRINTLN(s);
|
||||
@@ -167,10 +171,13 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
||||
}
|
||||
channelSwap = Bus::hasWhite(type) ? request->arg(wo).toInt() : 0;
|
||||
type |= request->hasArg(rf) << 7; // off refresh override
|
||||
artnet_outputs = (request->hasArg(ao)) ? request->arg(ao).toInt() : 1;
|
||||
artnet_leds_per_output = (request->hasArg(al)) ? request->arg(al).toInt() : length;
|
||||
artnet_fps_limit = (request->hasArg(af)) ? request->arg(af).toInt() : 33333/length;
|
||||
// actual finalization is done in WLED::loop() (removing old busses and adding new)
|
||||
// this may happen even before this loop is finished so we do "doInitBusses" after the loop
|
||||
if (busConfigs[s] != nullptr) delete busConfigs[s];
|
||||
busConfigs[s] = new BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freqHz);
|
||||
busConfigs[s] = new BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freqHz, artnet_outputs, artnet_leds_per_output, artnet_fps_limit);
|
||||
busesChanged = true;
|
||||
}
|
||||
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
|
||||
|
||||
199
wled00/udp.cpp
199
wled00/udp.cpp
@@ -759,10 +759,17 @@ void sendSysInfoUDP()
|
||||
// isRGBW - true if the buffer contains 4 components per pixel
|
||||
|
||||
static size_t sequenceNumber = 0; // this needs to be shared across all outputs
|
||||
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};
|
||||
static const byte ART_NET_HEADER[12] PROGMEM = {0x41,0x72,0x74,0x2d,0x4e,0x65,0x74,0x00,0x00,0x50,0x00,0x0e};
|
||||
static uint_fast16_t framenumber = 0;
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32P4)
|
||||
extern "C" {
|
||||
int p4_mul16x16(uint8_t* outpacket, uint8_t* brightness, uint16_t num_loops, uint8_t* pixelbuffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
uint8_t IRAM_ATTR realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri, bool isRGBW, uint8_t outputs, uint16_t leds_per_output, uint8_t fps_limit) {
|
||||
|
||||
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri, bool isRGBW) {
|
||||
if (!(apActive || interfacesInited) || !client[0] || !length) return 1; // network not initialised or dummy/unset IP address 031522 ajn added check for ap
|
||||
|
||||
WiFiUDP ddpUdp;
|
||||
@@ -838,58 +845,170 @@ uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8
|
||||
|
||||
case 2: //ArtNet
|
||||
{
|
||||
// calculate the number of UDP packets we need to send
|
||||
const size_t channelCount = length * (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;
|
||||
static uint_fast16_t artnetlimiter = millis()+(1000/fps_limit);
|
||||
while (artnetlimiter > micros()) {
|
||||
if (ArtNetSkipFrame) {
|
||||
return 0; // Let WLED keep generating effect frames and we output an Art-Net frame when fps_limit is reached.
|
||||
} else {
|
||||
delayMicroseconds(10); // Make WLED obey fps_limit and just delay here until we're ready to send a frame.
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t channel = 0;
|
||||
size_t bufferOffset = 0;
|
||||
/*
|
||||
WLED rendering Art-Net data considers itself to be 1 hardware output with many universes - but
|
||||
many Art-Net controllers like the H807SA can be manually set to "X universes per output" or in
|
||||
some cases "X channels per port" - which is the same thing, just expressed differently.
|
||||
|
||||
We need to know the LEDs per output so we can break the pixel data across physically attached universes.
|
||||
|
||||
The H807SA obeys the "510 channels for RGB" rule like WLED and xLights - some other controllers do not care,
|
||||
but we're not supporting those here. If you run into one of these, override ARTNET_CHANNELS_PER_PACKET to 512.
|
||||
*/
|
||||
|
||||
#ifdef ARTNET_TIMER
|
||||
uint_fast16_t datatotal = 0;
|
||||
uint_fast16_t packetstotal = 0;
|
||||
#endif
|
||||
uint_fast16_t timer = micros();
|
||||
|
||||
AsyncUDP artnetudp;// AsyncUDP so we can just blast packets.
|
||||
|
||||
const uint_fast16_t ARTNET_CHANNELS_PER_PACKET = isRGBW?512:510; // 512/4=128 RGBW LEDs, 510/3=170 RGB LEDs
|
||||
|
||||
uint_fast16_t bufferOffset = 0;
|
||||
uint_fast16_t hardware_output_universe = 0;
|
||||
|
||||
sequenceNumber++;
|
||||
|
||||
for (size_t currentPacket = 0; currentPacket < packetCount; currentPacket++) {
|
||||
if (sequenceNumber == 0 || sequenceNumber > 255) sequenceNumber = 1;
|
||||
|
||||
if (sequenceNumber > 255) sequenceNumber = 0;
|
||||
|
||||
if (!ddpUdp.beginPacket(client, ARTNET_DEFAULT_PORT)) {
|
||||
DEBUG_PRINTLN(F("Art-Net WiFiUDP.beginPacket returned an error"));
|
||||
return 1; // borked
|
||||
for (uint_fast16_t hardware_output = 0; hardware_output < outputs; hardware_output++) {
|
||||
|
||||
if (bufferOffset > length * (isRGBW?4:3)) {
|
||||
// This stop is reached if we don't have enough pixels for the defined Art-Net output.
|
||||
return 1; // stop when we hit end of LEDs
|
||||
}
|
||||
|
||||
size_t packetSize = ARTNET_CHANNELS_PER_PACKET;
|
||||
// hardware_output_universe = hardware_outputs_universe_start[hardware_output];
|
||||
|
||||
if (currentPacket == (packetCount - 1U)) {
|
||||
// last packet
|
||||
if (channelCount % ARTNET_CHANNELS_PER_PACKET) {
|
||||
packetSize = channelCount % ARTNET_CHANNELS_PER_PACKET;
|
||||
uint_fast16_t channels_remaining = leds_per_output * (isRGBW?4:3);
|
||||
|
||||
while (channels_remaining > 0) {
|
||||
|
||||
uint_fast16_t packetSize = ARTNET_CHANNELS_PER_PACKET;
|
||||
|
||||
if (channels_remaining < ARTNET_CHANNELS_PER_PACKET) {
|
||||
packetSize = channels_remaining;
|
||||
channels_remaining = 0;
|
||||
} else {
|
||||
channels_remaining -= packetSize;
|
||||
}
|
||||
|
||||
#ifdef ARTNET_TIMER
|
||||
packetstotal++;
|
||||
datatotal += packetSize + 18;
|
||||
#endif
|
||||
|
||||
// set the parts of the Art-Net packet header that change:
|
||||
packet_buffer[12] = sequenceNumber;
|
||||
// packet_buffer[13] = 0; // "The physical input port from which DMX512 data was input. This field is used by the receiving device to discriminate between packets with identical Port-Address that have been generated by different input ports and so need to be merged."
|
||||
packet_buffer[14] = hardware_output_universe;
|
||||
packet_buffer[15] = hardware_output_universe >> 8; // needed for universes > 255
|
||||
packet_buffer[16] = packetSize >> 8;
|
||||
packet_buffer[17] = packetSize;
|
||||
|
||||
#ifdef ARTNET_TESTING_ZEROS
|
||||
bri = 0; // Set all brightness to 0 but keep all calculations the same and keep sending packets.
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32P4)
|
||||
p4_mul16x16(packet_buffer+18, &bri, (packetSize >> 4)+1, buffer+bufferOffset);
|
||||
#else
|
||||
if (bri == 255) { // speed hack - don't adjust brightness if full brightness
|
||||
memcpy(packet_buffer+18, buffer+bufferOffset, packetSize);
|
||||
} else {
|
||||
for (uint_fast16_t i = 0; i < packetSize; i+=(isRGBW?4:3)) {
|
||||
// set brightness values in the packet - seems slightly faster than scale8()?
|
||||
// for some reason, doing 3 (or 4) at a time is 200 micros faster than 1 at a time.
|
||||
packet_buffer[i+18] = (buffer[bufferOffset+i] * bri) >> 8;
|
||||
packet_buffer[i+19] = (buffer[bufferOffset+i+1] * bri) >> 8;
|
||||
packet_buffer[i+20] = (buffer[bufferOffset+i+2] * bri) >> 8;
|
||||
if (isRGBW) packet_buffer[i+21] = (buffer[bufferOffset+i+3] * bri) >> 8;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bufferOffset += packetSize;
|
||||
|
||||
if (!artnetudp.writeTo(packet_buffer,packetSize+18, client, ARTNET_DEFAULT_PORT)) {
|
||||
DEBUG_PRINTLN(F("Art-Net artnetudp.writeTo() returned an error"));
|
||||
return 1; // borked
|
||||
}
|
||||
hardware_output_universe++;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// Send Art-Net sync. Just reuse the packet and adjust.
|
||||
// This should get re-written on the next run.
|
||||
// After the first sync packet, and assuming 1 sync packet every 4
|
||||
// seconds at least, should keep Art-Net nodes in synchronous mode.
|
||||
|
||||
for (size_t i = 0; i < packetSize; i += (isRGBW?4:3)) {
|
||||
ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // R
|
||||
ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // G
|
||||
ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // B
|
||||
if (isRGBW) ddpUdp.write(scale8(buffer[bufferOffset++], bri)); // W
|
||||
}
|
||||
// This is very much untested and generally not needed unless you
|
||||
// have several Art-Net devices being broadcast to, and should only
|
||||
// be called in that situation.
|
||||
|
||||
// Art-Net broadcast mode (setting Art-Net to 255.255.255.255) should ONLY
|
||||
// be used if you know what you're doing, as that is a lot of pixels being
|
||||
// sent to EVERYTHING on your network, including WiFi devices - and can
|
||||
// overwhelm them if you have a lot of Art-Net data being broadcast.
|
||||
|
||||
if (!ddpUdp.endPacket()) {
|
||||
DEBUG_PRINTLN(F("Art-Net WiFiUDP.endPacket returned an error"));
|
||||
#ifdef ARTNET_SYNC_ENABLED
|
||||
|
||||
// This block sends Art-Net "ArtSync" packets. Can't do this with AsyncUDP because it doesn't support source port binding.
|
||||
// Tested on Art-Net qualifier software but not on real hardware with known support for ArtSync.
|
||||
// Doesn't seem to do anything on my gear, so it's disabled.
|
||||
|
||||
// packet_buffer[8] = 0x00; // ArtSync opcode low byte (low byte is same as ArtDmx, 0x00)
|
||||
packet_buffer[9] = 0x52; // ArtSync opcode high byte
|
||||
packet_buffer[12] = 0x00; // Aux1 - Transmit as 0. This is normally the sequence number in ArtDMX packets.
|
||||
// packet_buffer[13] = 0x00; // Aux2 - Transmit as 0 - this should be 0 anyway in the packet already
|
||||
|
||||
#ifdef ARTNET_SYNC_STRICT
|
||||
WiFiUDP artnetsync;
|
||||
artnetsync.begin(ETH.localIP(), ARTNET_DEFAULT_PORT);
|
||||
artnetsync.beginPacket(IPADDR_BROADCAST,ARTNET_DEFAULT_PORT);
|
||||
artnetsync.write(packet_buffer,14);
|
||||
|
||||
if (!artnetsync.endPacket()) {
|
||||
DEBUG_PRINTLN(F("Art-Net Sync Broadcast Strict returned an error"));
|
||||
return 1; // borked
|
||||
}
|
||||
channel += packetSize;
|
||||
}
|
||||
} break;
|
||||
#else
|
||||
if (!artnetudp.broadcastTo(packet_buffer,14,ARTNET_DEFAULT_PORT)) {
|
||||
DEBUG_PRINTLN(F("Art-Net Sync Broadcast returned an error"));
|
||||
return 1; // borked
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ARTNET_TIMER
|
||||
packetstotal++;
|
||||
datatotal += 14;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
artnetlimiter = micros()+(1000000/fps_limit)-(micros()-timer);
|
||||
|
||||
// This is the proper stop if pixels = Art-Net output.
|
||||
|
||||
#ifdef ARTNET_TIMER
|
||||
float mbps = (datatotal*8)/((micros()-timer)*0.95367431640625f);
|
||||
// the "micros()" calc is just to limit the print to a more random debug output so it doesn't overwhelm the terminal
|
||||
if (micros() % 100 < 3) USER_PRINTF("UDP for %u pixels took %lu micros. %u data in %u total packets. %2.2f mbit/sec at %u FPS.\n",length, micros()-timer, datatotal, packetstotal, mbps, strip.getFps());
|
||||
#endif
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -424,6 +424,7 @@ void getSettingsJS(AsyncWebServerRequest* request, byte subPage, char* dest) //W
|
||||
for (uint8_t s=0; s < busses.getNumBusses(); s++) {
|
||||
Bus* bus = busses.getBus(s);
|
||||
if (bus == nullptr) continue;
|
||||
// "48+s" means the ASCII character "0", so 48+1 = ASCII for "1", etc - and "[3]=0" means null-terminate the string.
|
||||
char lp[4] = "L0"; lp[2] = 48+s; lp[3] = 0; //ascii 0-9 //strip data pin
|
||||
char lc[4] = "LC"; lc[2] = 48+s; lc[3] = 0; //strip length
|
||||
char co[4] = "CO"; co[2] = 48+s; co[3] = 0; //strip color order
|
||||
@@ -435,6 +436,9 @@ void getSettingsJS(AsyncWebServerRequest* request, byte subPage, char* dest) //W
|
||||
char aw[4] = "AW"; aw[2] = 48+s; aw[3] = 0; //auto white mode
|
||||
char wo[4] = "WO"; wo[2] = 48+s; wo[3] = 0; //swap channels
|
||||
char sp[4] = "SP"; sp[2] = 48+s; sp[3] = 0; //bus clock speed
|
||||
char ao[4] = "AO"; ao[2] = 48+s; ao[3] = 0; //Art-Net outputs
|
||||
char al[4] = "AL"; al[2] = 48+s; al[3] = 0; //Art-Net LEDs per output
|
||||
char af[4] = "AF"; af[2] = 48+s; af[3] = 0; //Art-Net FPS limit
|
||||
oappend(SET_F("addLEDs(1);"));
|
||||
uint8_t pins[5];
|
||||
uint8_t nPins = bus->getPins(pins);
|
||||
@@ -451,6 +455,9 @@ void getSettingsJS(AsyncWebServerRequest* request, byte subPage, char* dest) //W
|
||||
sappend('c',rf,bus->isOffRefreshRequired());
|
||||
sappend('v',aw,bus->getAutoWhiteMode());
|
||||
sappend('v',wo,bus->getColorOrder() >> 4);
|
||||
sappend('v',ao,bus->get_artnet_outputs());
|
||||
sappend('v',al,bus->get_artnet_leds_per_output());
|
||||
sappend('v',af,bus->get_artnet_fps_limit());
|
||||
uint16_t speed = bus->getFrequency();
|
||||
if (bus->getType() > TYPE_ONOFF && bus->getType() < 48) {
|
||||
switch (speed) {
|
||||
|
||||
Reference in New Issue
Block a user