mirror of
https://github.com/wled/WLED.git
synced 2025-11-07 18:18:52 +00:00
Compare commits
13 Commits
dnd-output
...
copilot/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7740231b8b | ||
|
|
f889e989ab | ||
|
|
449bdef36a | ||
|
|
4acec88c12 | ||
|
|
8dc220470e | ||
|
|
b60313e1f8 | ||
|
|
1fff61b726 | ||
|
|
c4850aed08 | ||
|
|
43eb2d6f47 | ||
|
|
0ec4488dd1 | ||
|
|
ca5debef32 | ||
|
|
cc0230f83f | ||
|
|
65b91762fd |
222
wled00/FX.cpp
222
wled00/FX.cpp
@@ -678,7 +678,7 @@ uint16_t mode_twinkle(void) {
|
||||
SEGENV.step = it;
|
||||
}
|
||||
|
||||
unsigned PRNG16 = SEGENV.aux1;
|
||||
uint16_t PRNG16 = SEGENV.aux1;
|
||||
|
||||
for (unsigned i = 0; i < SEGENV.aux0; i++)
|
||||
{
|
||||
@@ -5196,112 +5196,162 @@ static const char _data_FX_MODE_2DFRIZZLES[] PROGMEM = "Frizzles@X frequency,Y f
|
||||
///////////////////////////////////////////
|
||||
// 2D Cellular Automata Game of life //
|
||||
///////////////////////////////////////////
|
||||
typedef struct ColorCount {
|
||||
CRGB color;
|
||||
int8_t count;
|
||||
} colorCount;
|
||||
typedef struct Cell {
|
||||
uint8_t alive : 1, faded : 1, toggleStatus : 1, edgeCell: 1, oscillatorCheck : 1, spaceshipCheck : 1, unused : 2;
|
||||
} Cell;
|
||||
|
||||
uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/ and https://github.com/DougHaber/nlife-color
|
||||
uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/
|
||||
// and https://github.com/DougHaber/nlife-color , Modified By: Brandon Butler
|
||||
if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up
|
||||
const int cols = SEG_W, rows = SEG_H;
|
||||
const unsigned maxIndex = cols * rows;
|
||||
|
||||
const int cols = SEG_W;
|
||||
const int rows = SEG_H;
|
||||
const auto XY = [&](int x, int y) { return (x%cols) + (y%rows) * cols; };
|
||||
const unsigned dataSize = sizeof(CRGB) * SEGMENT.length(); // using width*height prevents reallocation if mirroring is enabled
|
||||
const int crcBufferLen = 2; //(SEGMENT.width() + SEGMENT.height())*71/100; // roughly sqrt(2)/2 for better repetition detection (Ewowi)
|
||||
if (!SEGENV.allocateData(SEGMENT.length() * sizeof(Cell))) return mode_static(); // allocation failed
|
||||
|
||||
if (!SEGENV.allocateData(dataSize + sizeof(uint16_t)*crcBufferLen)) return mode_static(); //allocation failed
|
||||
CRGB *prevLeds = reinterpret_cast<CRGB*>(SEGENV.data);
|
||||
uint16_t *crcBuffer = reinterpret_cast<uint16_t*>(SEGENV.data + dataSize);
|
||||
Cell *cells = reinterpret_cast<Cell*> (SEGENV.data);
|
||||
|
||||
CRGB backgroundColor = SEGCOLOR(1);
|
||||
uint16_t& generation = SEGENV.aux0, &gliderLength = SEGENV.aux1; // rename aux variables for clarity
|
||||
bool mutate = SEGMENT.check3;
|
||||
uint8_t blur = map(SEGMENT.custom1, 0, 255, 255, 4);
|
||||
|
||||
if (SEGENV.call == 0 || strip.now - SEGMENT.step > 3000) {
|
||||
SEGENV.step = strip.now;
|
||||
SEGENV.aux0 = 0;
|
||||
uint32_t bgColor = SEGCOLOR(1);
|
||||
uint32_t birthColor = SEGMENT.color_from_palette(128, false, PALETTE_SOLID_WRAP, 255);
|
||||
|
||||
//give the leds random state and colors (based on intensity, colors from palette or all posible colors are chosen)
|
||||
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) {
|
||||
unsigned state = hw_random8()%2;
|
||||
if (state == 0)
|
||||
SEGMENT.setPixelColorXY(x,y, backgroundColor);
|
||||
else
|
||||
SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 255));
|
||||
bool setup = SEGENV.call == 0;
|
||||
if (setup) {
|
||||
// Calculate glider length LCM(rows,cols)*4 once
|
||||
unsigned a = rows, b = cols;
|
||||
while (b) { unsigned t = b; b = a % b; a = t; }
|
||||
gliderLength = (cols * rows / a) << 2;
|
||||
}
|
||||
|
||||
if (abs(long(strip.now) - long(SEGENV.step)) > 2000) SEGENV.step = 0; // Timebase jump fix
|
||||
bool paused = SEGENV.step > strip.now;
|
||||
|
||||
// Setup New Game of Life
|
||||
if ((!paused && generation == 0) || setup) {
|
||||
SEGENV.step = strip.now + 1280; // show initial state for 1.28 seconds
|
||||
generation = 1;
|
||||
paused = true;
|
||||
//Setup Grid
|
||||
memset(cells, 0, maxIndex * sizeof(Cell));
|
||||
|
||||
for (unsigned i = 0; i < maxIndex; i++) {
|
||||
bool isAlive = !hw_random8(3); // ~33%
|
||||
cells[i].alive = isAlive;
|
||||
cells[i].faded = !isAlive;
|
||||
unsigned x = i % cols, y = i / cols;
|
||||
cells[i].edgeCell = (x == 0 || x == cols-1 || y == 0 || y == rows-1);
|
||||
|
||||
SEGMENT.setPixelColor(i, isAlive ? SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 0) : bgColor);
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) prevLeds[XY(x,y)] = CRGB::Black;
|
||||
memset(crcBuffer, 0, sizeof(uint16_t)*crcBufferLen);
|
||||
} else if (strip.now - SEGENV.step < FRAMETIME_FIXED * (uint32_t)map(SEGMENT.speed,0,255,64,4)) {
|
||||
// update only when appropriate time passes (in 42 FPS slots)
|
||||
if (paused || (strip.now - SEGENV.step < 1000 / map(SEGMENT.speed,0,255,1,42))) {
|
||||
// Redraw if paused or between updates to remove blur
|
||||
for (unsigned i = maxIndex; i--; ) {
|
||||
if (!cells[i].alive) {
|
||||
uint32_t cellColor = SEGMENT.getPixelColor(i);
|
||||
if (cellColor != bgColor) {
|
||||
uint32_t newColor;
|
||||
bool needsColor = false;
|
||||
if (cells[i].faded) { newColor = bgColor; needsColor = true; }
|
||||
else {
|
||||
uint32_t blended = color_blend(cellColor, bgColor, 2);
|
||||
if (blended == cellColor) { blended = bgColor; cells[i].faded = 1; }
|
||||
newColor = blended; needsColor = true;
|
||||
}
|
||||
if (needsColor) SEGMENT.setPixelColor(i, newColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
return FRAMETIME;
|
||||
}
|
||||
|
||||
//copy previous leds (save previous generation)
|
||||
//NOTE: using lossy getPixelColor() is a benefit as endlessly repeating patterns will eventually fade out causing a reset
|
||||
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) prevLeds[XY(x,y)] = SEGMENT.getPixelColorXY(x,y);
|
||||
// Repeat detection
|
||||
bool updateOscillator = generation % 16 == 0;
|
||||
bool updateSpaceship = gliderLength && generation % gliderLength == 0;
|
||||
bool repeatingOscillator = true, repeatingSpaceship = true, emptyGrid = true;
|
||||
|
||||
//calculate new leds
|
||||
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) {
|
||||
unsigned cIndex = maxIndex-1;
|
||||
for (unsigned y = rows; y--; ) for (unsigned x = cols; x--; cIndex--) {
|
||||
Cell& cell = cells[cIndex];
|
||||
|
||||
colorCount colorsCount[9]; // count the different colors in the 3*3 matrix
|
||||
for (int i=0; i<9; i++) colorsCount[i] = {backgroundColor, 0}; // init colorsCount
|
||||
if (cell.alive) emptyGrid = false;
|
||||
if (cell.oscillatorCheck != cell.alive) repeatingOscillator = false;
|
||||
if (cell.spaceshipCheck != cell.alive) repeatingSpaceship = false;
|
||||
if (updateOscillator) cell.oscillatorCheck = cell.alive;
|
||||
if (updateSpaceship) cell.spaceshipCheck = cell.alive;
|
||||
|
||||
// iterate through neighbors and count them and their different colors
|
||||
int neighbors = 0;
|
||||
for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // iterate through 3*3 matrix
|
||||
if (i==0 && j==0) continue; // ignore itself
|
||||
// wrap around segment
|
||||
int xx = x+i, yy = y+j;
|
||||
if (x+i < 0) xx = cols-1; else if (x+i >= cols) xx = 0;
|
||||
if (y+j < 0) yy = rows-1; else if (y+j >= rows) yy = 0;
|
||||
|
||||
unsigned xy = XY(xx, yy); // previous cell xy to check
|
||||
// count different neighbours and colors
|
||||
if (prevLeds[xy] != backgroundColor) {
|
||||
neighbors++;
|
||||
bool colorFound = false;
|
||||
int k;
|
||||
for (k=0; k<9 && colorsCount[k].count != 0; k++)
|
||||
if (colorsCount[k].color == prevLeds[xy]) {
|
||||
colorsCount[k].count++;
|
||||
colorFound = true;
|
||||
}
|
||||
if (!colorFound) colorsCount[k] = {prevLeds[xy], 1}; //add new color found in the array
|
||||
unsigned neighbors = 0, aliveParents = 0, parentIdx[3];
|
||||
// Count alive neighbors
|
||||
for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) if (i || j) {
|
||||
int nX = x + j, nY = y + i;
|
||||
if (cell.edgeCell) {
|
||||
nX = (nX + cols) % cols;
|
||||
nY = (nY + rows) % rows;
|
||||
}
|
||||
unsigned nIndex = nX + nY * cols;
|
||||
Cell& neighbor = cells[nIndex];
|
||||
if (neighbor.alive) {
|
||||
neighbors++;
|
||||
if (!neighbor.toggleStatus && neighbors < 4) { // Alive and not dying
|
||||
parentIdx[aliveParents++] = nIndex;
|
||||
}
|
||||
}
|
||||
} // i,j
|
||||
|
||||
// Rules of Life
|
||||
uint32_t col = uint32_t(prevLeds[XY(x,y)]) & 0x00FFFFFF; // uint32_t operator returns RGBA, we want RGBW -> cut off "alpha" byte
|
||||
uint32_t bgc = RGBW32(backgroundColor.r, backgroundColor.g, backgroundColor.b, 0);
|
||||
if ((col != bgc) && (neighbors < 2)) SEGMENT.setPixelColorXY(x,y, bgc); // Loneliness
|
||||
else if ((col != bgc) && (neighbors > 3)) SEGMENT.setPixelColorXY(x,y, bgc); // Overpopulation
|
||||
else if ((col == bgc) && (neighbors == 3)) { // Reproduction
|
||||
// find dominant color and assign it to a cell
|
||||
colorCount dominantColorCount = {backgroundColor, 0};
|
||||
for (int i=0; i<9 && colorsCount[i].count != 0; i++)
|
||||
if (colorsCount[i].count > dominantColorCount.count) dominantColorCount = colorsCount[i];
|
||||
// assign the dominant color w/ a bit of randomness to avoid "gliders"
|
||||
if (dominantColorCount.count > 0 && hw_random8(128)) SEGMENT.setPixelColorXY(x,y, dominantColorCount.color);
|
||||
} else if ((col == bgc) && (neighbors == 2) && !hw_random8(128)) { // Mutation
|
||||
SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 255));
|
||||
}
|
||||
// else do nothing!
|
||||
} //x,y
|
||||
|
||||
// calculate CRC16 of leds
|
||||
uint16_t crc = crc16((const unsigned char*)prevLeds, dataSize);
|
||||
// check if we had same CRC and reset if needed
|
||||
bool repetition = false;
|
||||
for (int i=0; i<crcBufferLen && !repetition; i++) repetition = (crc == crcBuffer[i]); // (Ewowi)
|
||||
// same CRC would mean image did not change or was repeating itself
|
||||
if (!repetition) SEGENV.step = strip.now; //if no repetition avoid reset
|
||||
// remember CRCs across frames
|
||||
crcBuffer[SEGENV.aux0] = crc;
|
||||
++SEGENV.aux0 %= crcBufferLen;
|
||||
uint32_t newColor;
|
||||
bool needsColor = false;
|
||||
|
||||
if (cell.alive && (neighbors < 2 || neighbors > 3)) { // Loneliness or Overpopulation
|
||||
cell.toggleStatus = 1;
|
||||
if (blur == 255) cell.faded = 1;
|
||||
newColor = cell.faded ? bgColor : color_blend(SEGMENT.getPixelColor(cIndex), bgColor, blur);
|
||||
needsColor = true;
|
||||
}
|
||||
else if (!cell.alive) {
|
||||
byte mutationRoll = mutate ? hw_random8(128) : 1; // if 0: 3 neighbor births fail and 2 neighbor births mutate
|
||||
if ((neighbors == 3 && mutationRoll) || (mutate && neighbors == 2 && !mutationRoll)) { // Reproduction or Mutation
|
||||
cell.toggleStatus = 1;
|
||||
cell.faded = 0;
|
||||
|
||||
if (aliveParents) {
|
||||
// Set color based on random neighbor
|
||||
unsigned parentIndex = parentIdx[random8(aliveParents)];
|
||||
birthColor = SEGMENT.getPixelColor(parentIndex);
|
||||
}
|
||||
newColor = birthColor;
|
||||
needsColor = true;
|
||||
}
|
||||
else if (!cell.faded) {// No change, fade dead cells
|
||||
uint32_t cellColor = SEGMENT.getPixelColor(cIndex);
|
||||
uint32_t blended = color_blend(cellColor, bgColor, blur);
|
||||
if (blended == cellColor) { blended = bgColor; cell.faded = 1; }
|
||||
newColor = blended;
|
||||
needsColor = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsColor) SEGMENT.setPixelColor(cIndex, newColor);
|
||||
}
|
||||
// Loop through cells, if toggle, swap alive status
|
||||
for (unsigned i = maxIndex; i--; ) {
|
||||
cells[i].alive ^= cells[i].toggleStatus;
|
||||
cells[i].toggleStatus = 0;
|
||||
}
|
||||
|
||||
if (repeatingOscillator || repeatingSpaceship || emptyGrid) {
|
||||
generation = 0; // reset on next call
|
||||
SEGENV.step += 1024; // pause final generation for ~1 second
|
||||
}
|
||||
else {
|
||||
++generation;
|
||||
SEGENV.step = strip.now;
|
||||
}
|
||||
return FRAMETIME;
|
||||
} // mode_2Dgameoflife()
|
||||
static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!;!,!;!;2";
|
||||
static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,,Blur,,,,,Mutation;!,!;!;2;pal=11,sx=128";
|
||||
|
||||
|
||||
/////////////////////////
|
||||
|
||||
@@ -777,6 +777,10 @@ bool verifyConfig() {
|
||||
return validateJsonFile(s_cfg_json);
|
||||
}
|
||||
|
||||
bool configBackupExists() {
|
||||
return checkBackupExists(s_cfg_json);
|
||||
}
|
||||
|
||||
// rename config file and reboot
|
||||
// if the cfg file doesn't exist, such as after a reset, do nothing
|
||||
void resetConfig() {
|
||||
|
||||
@@ -116,3 +116,62 @@ function uploadFile(fileObj, name) {
|
||||
fileObj.value = '';
|
||||
return false;
|
||||
}
|
||||
// connect to WebSocket, use parent WS or open new
|
||||
function connectWs(onOpen) {
|
||||
try {
|
||||
if (top.window.ws && top.window.ws.readyState === WebSocket.OPEN) {
|
||||
if (onOpen) onOpen();
|
||||
return top.window.ws;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
getLoc(); // ensure globals (loc, locip, locproto) are up to date
|
||||
let url = loc ? getURL('/ws').replace("http","ws") : "ws://"+window.location.hostname+"/ws";
|
||||
let ws = new WebSocket(url);
|
||||
ws.binaryType = "arraybuffer";
|
||||
if (onOpen) { ws.onopen = onOpen; }
|
||||
try { top.window.ws = ws; } catch (e) {} // store in parent for reuse
|
||||
return ws;
|
||||
}
|
||||
|
||||
// send LED colors to ESP using WebSocket and DDP protocol (RGB)
|
||||
// ws: WebSocket object
|
||||
// start: start pixel index
|
||||
// len: number of pixels to send
|
||||
// colors: Uint8Array with RGB values (3*len bytes)
|
||||
function sendDDP(ws, start, len, colors) {
|
||||
if (!colors || colors.length < len * 3) return false; // not enough color data
|
||||
let maxDDPpx = 472; // must fit into one WebSocket frame of 1428 bytes, DDP header is 10+1 bytes -> 472 RGB pixels
|
||||
//let maxDDPpx = 172; // ESP8266: must fit into one WebSocket frame of 528 bytes -> 172 RGB pixels TODO: add support for ESP8266?
|
||||
if (!ws || ws.readyState !== WebSocket.OPEN) return false;
|
||||
// send in chunks of maxDDPpx
|
||||
for (let i = 0; i < len; i += maxDDPpx) {
|
||||
let cnt = Math.min(maxDDPpx, len - i);
|
||||
let off = (start + i) * 3; // DDP pixel offset in bytes
|
||||
let dLen = cnt * 3;
|
||||
let cOff = i * 3; // offset in color buffer
|
||||
let pkt = new Uint8Array(11 + dLen); // DDP header is 10 bytes, plus 1 byte for WLED websocket protocol indicator
|
||||
pkt[0] = 0x02; // DDP protocol indicator for WLED websocket. Note: below DDP protocol bytes are offset by 1
|
||||
pkt[1] = 0x40; // flags: 0x40 = no push, 0x41 = push (i.e. render), note: this is DDP protocol byte 0
|
||||
pkt[2] = 0x00; // reserved
|
||||
pkt[3] = 0x01; // 1 = RGB (currently only supported mode)
|
||||
pkt[4] = 0x01; // destination id (not used but 0x01 is default output)
|
||||
pkt[5] = (off >> 24) & 255; // DDP protocol 4-7 is offset
|
||||
pkt[6] = (off >> 16) & 255;
|
||||
pkt[7] = (off >> 8) & 255;
|
||||
pkt[8] = off & 255;
|
||||
pkt[9] = (dLen >> 8) & 255; // DDP protocol 8-9 is data length
|
||||
pkt[10] = dLen & 255;
|
||||
pkt.set(colors.subarray(cOff, cOff + dLen), 11);
|
||||
if(i + cnt >= len) {
|
||||
pkt[1] = 0x41; //if this is last packet, set the "push" flag to render the frame
|
||||
}
|
||||
try {
|
||||
ws.send(pkt.buffer);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
<script src="common.js"></script>
|
||||
<script>
|
||||
var d = document;
|
||||
var ws;
|
||||
var tmout = null;
|
||||
var c;
|
||||
@@ -62,32 +62,14 @@
|
||||
if (window.location.href.indexOf("?ws") == -1) {update(); return;}
|
||||
|
||||
// Initialize WebSocket connection
|
||||
try {
|
||||
ws = top.window.ws;
|
||||
} catch (e) {}
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
//console.info("Peek uses top WS");
|
||||
ws.send("{'lv':true}");
|
||||
} else {
|
||||
//console.info("Peek WS opening");
|
||||
let l = window.location;
|
||||
let pathn = l.pathname;
|
||||
let paths = pathn.slice(1,pathn.endsWith('/')?-1:undefined).split("/");
|
||||
let url = l.origin.replace("http","ws");
|
||||
if (paths.length > 1) {
|
||||
url += "/" + paths[0];
|
||||
}
|
||||
ws = new WebSocket(url+"/ws");
|
||||
ws.onopen = function () {
|
||||
//console.info("Peek WS open");
|
||||
ws.send("{'lv':true}");
|
||||
}
|
||||
}
|
||||
ws.binaryType = "arraybuffer";
|
||||
ws = connectWs(function () {
|
||||
//console.info("Peek WS open");
|
||||
ws.send('{"lv":true}');
|
||||
});
|
||||
ws.addEventListener('message', (e) => {
|
||||
try {
|
||||
if (toString.call(e.data) === '[object ArrayBuffer]') {
|
||||
let leds = new Uint8Array(event.data);
|
||||
let leds = new Uint8Array(e.data);
|
||||
if (leds[0] != 76) return; //'L'
|
||||
// leds[1] = 1: 1D; leds[1] = 2: 1D/2D (leds[2]=w, leds[3]=h)
|
||||
draw(leds[1]==2 ? 4 : 2, 3, leds, (a,i) => `rgb(${a[i]},${a[i+1]},${a[i+2]})`);
|
||||
@@ -102,4 +84,4 @@
|
||||
<body onload="S()">
|
||||
<canvas id="canv"></canvas>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -10,6 +10,7 @@
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<script src="common.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canv"></canvas>
|
||||
@@ -26,30 +27,13 @@
|
||||
var ctx = c.getContext('2d');
|
||||
if (ctx) { // Access the rendering context
|
||||
// use parent WS or open new
|
||||
var ws;
|
||||
try {
|
||||
ws = top.window.ws;
|
||||
} catch (e) {}
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send("{'lv':true}");
|
||||
} else {
|
||||
let l = window.location;
|
||||
let pathn = l.pathname;
|
||||
let paths = pathn.slice(1,pathn.endsWith('/')?-1:undefined).split("/");
|
||||
let url = l.origin.replace("http","ws");
|
||||
if (paths.length > 1) {
|
||||
url += "/" + paths[0];
|
||||
}
|
||||
ws = new WebSocket(url+"/ws");
|
||||
ws.onopen = ()=>{
|
||||
ws.send("{'lv':true}");
|
||||
}
|
||||
}
|
||||
ws.binaryType = "arraybuffer";
|
||||
var ws = connectWs(()=>{
|
||||
ws.send('{"lv":true}');
|
||||
});
|
||||
ws.addEventListener('message',(e)=>{
|
||||
try {
|
||||
if (toString.call(e.data) === '[object ArrayBuffer]') {
|
||||
let leds = new Uint8Array(event.data);
|
||||
let leds = new Uint8Array(e.data);
|
||||
if (leds[0] != 76 || leds[1] != 2 || !ctx) return; //'L', set in ws.cpp
|
||||
let mW = leds[2]; // matrix width
|
||||
let mH = leds[3]; // matrix height
|
||||
|
||||
@@ -13,9 +13,8 @@
|
||||
function gT(t) { for (let type of d.ledTypes) if (t == type.i) return type; } // getType from available ledTypes
|
||||
function isPWM(t) { return gT(t).t.charAt(0) === "A"; } // is PWM type
|
||||
function isAna(t) { return gT(t).t === "" || isPWM(t); } // is analog type
|
||||
function isD1P(t) { return gT(t).t === "D"; } // is digital 1 pin type
|
||||
function isDig(t) { return gT(t).t === "D" || isD2P(t); } // is digital type
|
||||
function isD2P(t) { return gT(t).t === "2P"; } // is digital 2 pin type
|
||||
function isDig(t) { return isD1P(t) || isD2P(t); } // is digital type
|
||||
function isNet(t) { return gT(t).t === "N"; } // is network type
|
||||
function isVir(t) { return gT(t).t === "V" || isNet(t); } // is virtual type
|
||||
function isHub75(t){ return gT(t).t === "H"; } // is HUB75 type
|
||||
@@ -72,7 +71,7 @@
|
||||
return;
|
||||
}
|
||||
// ignore IP address
|
||||
if (isNet(t)) return;
|
||||
if (isNet(t)) return;
|
||||
//check for pin conflicts
|
||||
if (LC.value!="" && LC.value!="-1") {
|
||||
let p = d.rsvd.concat(d.um_p); // used pin array
|
||||
@@ -102,10 +101,10 @@
|
||||
nList[j].focus();
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return ok;
|
||||
}
|
||||
@@ -119,6 +118,7 @@
|
||||
if (bquot > 100) msg += "\n\rToo many LEDs for me to handle properly!"; if (maxM < 10000) msg += "\n\rConsider using an ESP32."; alert(msg);}
|
||||
if (!d.Sf.ABL.checked || d.Sf.PPL.checked) d.Sf.MA.value = 0; // submit 0 as ABL (PPL will handle it)
|
||||
if (d.Sf.checkValidity()) {
|
||||
d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{s.disabled=false;}); // just in case
|
||||
d.Sf.submit(); //https://stackoverflow.com/q/37323914
|
||||
}
|
||||
}
|
||||
@@ -131,7 +131,7 @@
|
||||
if (!en) {
|
||||
// limiter disabled
|
||||
d.Sf.PPL.checked = false;
|
||||
// d.Sf.querySelectorAll("#mLC select[name^=LL]").forEach((e)=>{e.selectedIndex = 0;}); // select default LED mA
|
||||
// d.Sf.querySelectorAll("#mLC select[name^=LAsel]").forEach((e)=>{e.selectedIndex = 0;}); // select default LED mA
|
||||
// d.Sf.querySelectorAll("#mLC input[name^=LA]").forEach((e)=>{e.min = 0; e.value = 0;}); // set min & value to 0
|
||||
}
|
||||
UI();
|
||||
@@ -160,10 +160,9 @@
|
||||
if (ppl) d.Sf.MA.value = sumMA; // populate UI ABL value if PPL used
|
||||
}
|
||||
// enable and update LED Amps
|
||||
function enLA(s)
|
||||
function enLA(s,n)
|
||||
{
|
||||
const abl = d.Sf.ABL.checked;
|
||||
const n = s.name.substring(2); // bus number (0-Z)
|
||||
const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT
|
||||
gId('LAdis'+n).style.display = s.selectedIndex==5 ? "inline" : "none"; // show/hide custom mA field
|
||||
if (s.value!=="0") d.Sf["LA"+n].value = s.value; // set value from select object
|
||||
@@ -178,9 +177,9 @@
|
||||
});
|
||||
d.Sf.ABL.checked = en;
|
||||
// select appropriate LED current
|
||||
d.Sf.querySelectorAll("#mLC select[name^=LL]").forEach((sel)=>{
|
||||
d.Sf.querySelectorAll("#mLC select[name^=LAsel]").forEach((sel,x)=>{
|
||||
sel.value = 0; // set custom
|
||||
var n = sel.name.substring(2); // bus number (0-Z)
|
||||
var n = chrID(x);
|
||||
if (en)
|
||||
switch (parseInt(d.Sf["LA"+n].value)) {
|
||||
case 0: break; // disable ABL
|
||||
@@ -191,7 +190,7 @@
|
||||
case 255: sel.value = 255; break;
|
||||
}
|
||||
else sel.value = 0;
|
||||
enLA(sel); // configure individual limiter
|
||||
enLA(sel,n); // configure individual limiter
|
||||
});
|
||||
enABL();
|
||||
gId('m1').innerHTML = maxM;
|
||||
@@ -215,8 +214,8 @@
|
||||
mul = 2; // ESP32 RMT uses double buffer
|
||||
} else if ((is32() || isS2() || isS3()) && toNum(n) > (parallelI2S ? 7 : 0)) {
|
||||
mul = 2; // ESP32 RMT uses double buffer
|
||||
} else if ((parallelI2S && toNum(n) < 8) || (toNum(n) == 0 && is32())) { // I2S uses extra DMA buffer
|
||||
dbl = len * ch * 3; // DMA buffer for parallel I2S (TODO: only the bus with largst LED count should be used)
|
||||
} else if ((parallelI2S && toNum(n) < 8) || (n == 0 && is32())) { // I2S uses extra DMA buffer
|
||||
dbl = len * ch * 3; // DMA buffer for parallel I2S (TODO: ony the bus with largst LED count should be used)
|
||||
}
|
||||
}
|
||||
return len * ch * mul + dbl + pbfr;
|
||||
@@ -227,22 +226,81 @@
|
||||
let gRGBW = false, memu = 0;
|
||||
let busMA = 0;
|
||||
let sLC = 0, sPC = 0, sDI = 0, maxLC = 0;
|
||||
const abl = d.Sf.ABL.checked;
|
||||
let setPinConfig = (n,t) => {
|
||||
let p0d = "GPIO:";
|
||||
let p1d = "";
|
||||
let off = "Off Refresh";
|
||||
switch (gT(t).t.charAt(0)) {
|
||||
case '2': // 2 pin digital
|
||||
p1d = "Clock "+p0d;
|
||||
// fallthrough
|
||||
case 'D': // digital
|
||||
p0d = "Data "+p0d;
|
||||
break;
|
||||
case 'A': // PWM analog
|
||||
if (numPins(t) > 1) p0d = "GPIOs:";
|
||||
off = "Dithering";
|
||||
break;
|
||||
case 'N': // network
|
||||
p0d = "IP address:";
|
||||
break;
|
||||
case 'V': // virtual/non-GPIO based
|
||||
p0d = "Config:"
|
||||
break;
|
||||
case 'H': // HUB75
|
||||
p0d = "Panel size (width x height), Panel count:"
|
||||
break;
|
||||
}
|
||||
gId("p0d"+n).innerText = p0d;
|
||||
gId("p1d"+n).innerText = p1d;
|
||||
gId("off"+n).innerText = off;
|
||||
// secondary pins show/hide (type string length is equivalent to number of pins used; except for network and on/off)
|
||||
let pins = Math.max(gT(t).t.length,1) + 3*isNet(t) + 2*isHub75(t); // fixes network pins to 4
|
||||
for (let p=1; p<5; p++) {
|
||||
var LK = d.Sf["L"+p+n];
|
||||
if (!LK) continue;
|
||||
LK.style.display = (p < pins) ? "inline" : "none";
|
||||
LK.required = (p < pins);
|
||||
if (p >= pins) LK.value="";
|
||||
}
|
||||
}
|
||||
|
||||
// enable/disable LED fields
|
||||
updateTypeDropdowns(change);
|
||||
let dC = 0; // count of digital buses (for parallel I2S)
|
||||
let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]");
|
||||
LTs.forEach((s,i)=>{
|
||||
if (i < LTs.length-1) s.disabled = true; // prevent changing type (as we can't update options)
|
||||
// is the field a LED type?
|
||||
var n = s.name.substring(2,3); // bus number (0-Z)
|
||||
var t = parseInt(s.value);
|
||||
memu += getMem(t, n); // calc memory
|
||||
dC += isD1P(t); // count digital buses
|
||||
gRGBW |= hasW(t); // RGBW checkbox
|
||||
memu += getMem(t, n); // calc memory
|
||||
dC += (isDig(t) && !isD2P(t));
|
||||
setPinConfig(n,t);
|
||||
gId("abl"+n).style.display = (!abl || !isDig(t)) ? "none" : "inline"; // show/hide individual ABL settings
|
||||
if (change) { // did we change LED type?
|
||||
gId("rf"+n).checked = (gId("rf"+n).checked || t == 31); // LEDs require data in off state (mandatory for TM1814)
|
||||
if (isAna(t)) d.Sf["LC"+n].value = 1; // for sanity change analog count just to 1 LED
|
||||
d.Sf["LA"+n].min = (!isDig(t) || !abl) ? 0 : 1; // set minimum value for LED mA
|
||||
d.Sf["MA"+n].min = (!isDig(t)) ? 0 : 250; // set minimum value for PSU mA
|
||||
}
|
||||
gId("rf"+n).onclick = mustR(t) ? (()=>{return false}) : (()=>{}); // prevent change change of "Refresh" checkmark when mandatory
|
||||
gRGBW |= hasW(t); // RGBW checkbox
|
||||
gId("co"+n).style.display = (isVir(t) || isAna(t) || isHub75(t)) ? "none":"inline"; // hide color order for PWM
|
||||
gId("dig"+n+"w").style.display = (isDig(t) && hasW(t)) ? "inline":"none"; // show swap channels dropdown
|
||||
gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping
|
||||
if (!(isDig(t) && hasW(t))) d.Sf["WO"+n].value = 0; // reset swapping
|
||||
gId("dig"+n+"c").style.display = (isAna(t) || isHub75(t)) ? "none":"inline"; // hide count for analog
|
||||
gId("dig"+n+"r").style.display = (isVir(t)) ? "none":"inline"; // hide reversed for virtual
|
||||
gId("dig"+n+"s").style.display = (isVir(t) || isAna(t) || isHub75(t)) ? "none":"inline"; // hide skip 1st for virtual & analog
|
||||
gId("dig"+n+"f").style.display = (isDig(t) || (isPWM(t) && maxL>2048)) ? "inline":"none"; // hide refresh (PWM hijacks reffresh for dithering on ESP32)
|
||||
gId("dig"+n+"a").style.display = (hasW(t)) ? "inline":"none"; // auto calculate white
|
||||
gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off)
|
||||
gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed"; // change reverse text for analog else (rotated 180°)
|
||||
//gId("psd"+n).innerHTML = isAna(t) ? "Index:":"Start:"; // change analog start description
|
||||
gId("net"+n+"h").style.display = isNet(t) && !is8266() ? "block" : "none"; // show host field for network types except on ESP8266
|
||||
if (!isNet(t) || is8266()) d.Sf["HS"+n].value = ""; // cleart host field if not network type or ESP8266
|
||||
});
|
||||
// enable/disable add/remove buttons
|
||||
gId("+").style.display = (LTs.length<36) ? "inline":"none"; // now: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".length
|
||||
gId("-").style.display = (LTs.length>1) ? "inline":"none";
|
||||
|
||||
// display global white channel overrides
|
||||
gId("wc").style.display = (gRGBW) ? 'inline':'none';
|
||||
if (!gRGBW) {
|
||||
@@ -262,16 +320,15 @@
|
||||
}
|
||||
// do we have a led count field
|
||||
if (nm=="LC") {
|
||||
LC.max = isAna(t) ? 1 : (isDig(t) ? maxPB : 16384); // set max value
|
||||
let c = parseInt(LC.value,10); //get LED count
|
||||
if (!customStarts || !startsDirty[toNum(n)]) gId("ls"+n).value = sLC; //update start value
|
||||
gId("ls"+n).disabled = !customStarts; //enable/disable field editing
|
||||
if (c) {
|
||||
let s = parseInt(gId("ls"+n).value); //start value
|
||||
if (s+c > sLC) sLC = s+c; //update total count
|
||||
if (c > maxLC) maxLC = c; //max per output
|
||||
if (!isVir(t)) sPC += c; //virtual out busses do not count towards physical LEDs
|
||||
if (isDig(t)) {
|
||||
if (c > maxLC) maxLC = c; //max per output
|
||||
sDI += c; // summarize digital LED count
|
||||
let maPL = parseInt(d.Sf["LA"+n].value);
|
||||
if (maPL == 255) maPL = 12;
|
||||
@@ -381,8 +438,8 @@
|
||||
function lastEnd(i) {
|
||||
if (i-- < 1) return 0;
|
||||
var s = chrID(i);
|
||||
v = parseInt(gN("LS"+s).value) + parseInt(gN("LC"+s).value);
|
||||
var t = parseInt(gN("LT"+s).value);
|
||||
v = parseInt(d.getElementsByName("LS"+s)[0].value) + parseInt(d.getElementsByName("LC"+s)[0].value);
|
||||
var t = parseInt(d.getElementsByName("LT"+s)[0].value);
|
||||
if (isPWM(t)) v = 1; //PWM busses
|
||||
return isNaN(v) ? 0 : v;
|
||||
}
|
||||
@@ -390,19 +447,29 @@
|
||||
{
|
||||
var o = gEBCN("iST");
|
||||
var i = o.length;
|
||||
let disable = (sel,opt) => { sel.querySelectorAll(opt).forEach((o)=>{o.disabled=true;}); }
|
||||
|
||||
var f = gId("mLC");
|
||||
let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0;
|
||||
f.querySelectorAll("select[name^=LT]").forEach((s)=>{
|
||||
let t = s.value;
|
||||
if (isDig(t) && !isD2P(t)) digitalB++;
|
||||
if (isD2P(t)) twopinB++;
|
||||
if (isPWM(t)) analogB += numPins(t); // each GPIO is assigned to a channel
|
||||
if (isVir(t)) virtB++;
|
||||
});
|
||||
|
||||
if ((n==1 && i>=36) || (n==-1 && i==0)) return; // used to be i>=maxB+maxV when virtual buses were limited (now :"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
var s = chrID(i);
|
||||
|
||||
if (n==1) {
|
||||
// npm run build has trouble minimizing spaces inside string
|
||||
var cn = `<div class="iST" draggable="true" ondragstart="hDS(event)" id="l${s}" style="cursor:grab;">
|
||||
var cn = `<div class="iST">
|
||||
<hr class="sml">
|
||||
<span id="n${s}">${++i}</span>:
|
||||
${i+1}:
|
||||
<select name="LT${s}" onchange="UI(true)"></select><br>
|
||||
<div id="abl${s}">
|
||||
mA/LED: <select name="LL${s}" onchange="enLA(this);UI();">
|
||||
mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
|
||||
<option value="55" selected>55mA (typ. 5V WS281x)</option>
|
||||
<option value="35">35mA (eco WS2812)</option>
|
||||
<option value="30">30mA (typ. 12V)</option>
|
||||
@@ -411,7 +478,7 @@ mA/LED: <select name="LL${s}" onchange="enLA(this);UI();">
|
||||
<option value="0">Custom</option>
|
||||
</select><br>
|
||||
<div id="LAdis${s}" style="display: none;">max. mA/LED: <input name="LA${s}" type="number" min="1" max="255" oninput="UI()"> mA<br></div>
|
||||
<div id="PSU${s}">PSU: <input name="MA${s}" type="number" class="xl" min="250" max="65500" oninput="UI()" value="250"> mA<br></div>
|
||||
<div id="PSU${s}">PSU: <input name="MA${s}" type="number" class="xl" min="250" max="65000" oninput="UI()" value="250"> mA<br></div>
|
||||
</div>
|
||||
<div id="co${s}" style="display:inline">Color Order:
|
||||
<select name="CO${s}">
|
||||
@@ -425,7 +492,7 @@ mA/LED: <select name="LL${s}" onchange="enLA(this);UI();">
|
||||
<div id="dig${s}w" style="display:none">Swap: <select name="WO${s}"><option value="0">None</option><option value="1">W & B</option><option value="2">W & G</option><option value="3">W & R</option><option data-opt="CCT" value="4">WW & CW</option></select></div>
|
||||
<div id="dig${s}l" style="display:none">Clock: <select name="SP${s}"><option value="0">Slowest</option><option value="1">Slow</option><option value="2">Normal</option><option value="3">Fast</option><option value="4">Fastest</option></select></div>
|
||||
<div>
|
||||
<span id="psd${s}">Start:</span> <input type="number" name="LS${s}" id="ls${s}" class="l starts" min="0" max="8191" value="${lastEnd(i-1)}" oninput="startsDirty[${i}]=true;UI();" required />
|
||||
<span id="psd${s}">Start:</span> <input type="number" name="LS${s}" id="ls${s}" class="l starts" min="0" max="8191" value="${lastEnd(i)}" oninput="startsDirty[${i}]=true;UI();" required />
|
||||
<div id="dig${s}c" style="display:inline">Length: <input type="number" name="LC${s}" class="l" min="1" max="${maxPB}" value="1" required oninput="UI()" /></div><br>
|
||||
</div>
|
||||
<span id="p0d${s}">GPIO:</span><input type="number" name="L0${s}" required class="s" onchange="UI();pinUpd(this);"/>
|
||||
@@ -441,29 +508,40 @@ mA/LED: <select name="LL${s}" onchange="enLA(this);UI();">
|
||||
</div>`;
|
||||
f.insertAdjacentHTML("beforeend", cn);
|
||||
// fill led types (credit @netmindz)
|
||||
let sel = f.lastElementChild.querySelector("select[name^=LT]"); // get recently added LED type select
|
||||
for (let type of d.ledTypes) {
|
||||
let opt = cE("option");
|
||||
opt.value = type.i;
|
||||
opt.text = type.n;
|
||||
if (type.t != undefined && type.t != "") {
|
||||
opt.setAttribute('data-type', type.t);
|
||||
f.querySelectorAll("select[name^=LT]").forEach((sel,n)=>{
|
||||
if (sel.length == 0) { // ignore already updated
|
||||
for (let type of d.ledTypes) {
|
||||
let opt = cE("option");
|
||||
opt.value = type.i;
|
||||
opt.text = type.n;
|
||||
if (type.t != undefined && type.t != "") {
|
||||
opt.setAttribute('data-type', type.t);
|
||||
}
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
}
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
enLA(gN("LL"+s)); // update LED mA
|
||||
if (!init) {
|
||||
// we used add button
|
||||
sel.selectedIndex = sel.querySelector('option:not([data-type])').index; // select On/Off by default (prevent selecting first type which may be invalid)
|
||||
updateTypeDropdowns(true, sel);
|
||||
sel.selectedIndex = sel.querySelector('option:not(:disabled)').index; // select 1st valid
|
||||
}
|
||||
});
|
||||
enLA(d.Sf["LAsel"+s],s); // update LED mA
|
||||
// disable inappropriate LED types
|
||||
let sel = d.getElementsByName("LT"+s)[0];
|
||||
// 32 & S2 supports mono I2S as well as parallel so we need to take that into account; S3 only supports parallel
|
||||
let maxDB = maxD - (is32() || isS2() || isS3() ? (!d.Sf["PR"].checked)*8 - (!isS3()) : 0); // adjust max digital buses if parallel I2S is not used
|
||||
if (digitalB >= maxDB) disable(sel,'option[data-type="D"]'); // NOTE: see isDig()
|
||||
if (twopinB >= 2) disable(sel,'option[data-type="2P"]'); // NOTE: see isD2P() (we will only allow 2 2pin buses)
|
||||
disable(sel,`option[data-type^="${'A'.repeat(maxA-analogB+1)}"]`); // NOTE: see isPWM()
|
||||
sel.selectedIndex = sel.querySelector('option:not(:disabled)').index;
|
||||
}
|
||||
if (n==-1) {
|
||||
o[--i].remove();
|
||||
o[--i].remove();--i;
|
||||
o[i].querySelector("[name^=LT]").disabled = false;
|
||||
}
|
||||
|
||||
gId("+").style.display = (i<35) ? "inline":"none"; // was maxB+maxV-1 when virtual buses were limited (now :"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
gId("-").style.display = (i>0) ? "inline":"none";
|
||||
|
||||
if (!init) {
|
||||
UI();
|
||||
}
|
||||
// if called from + or - button, update UI
|
||||
if (!init) UI();
|
||||
}
|
||||
|
||||
function addCOM(start=0,len=1,co=0) {
|
||||
@@ -539,43 +617,6 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
c += `<span style="cursor: pointer;" onclick="off('BT${s}')"> ✕</span><br>`;
|
||||
gId("btns").innerHTML = c;
|
||||
}
|
||||
function hDS(e) {
|
||||
e.dataTransfer.setData('text', e.currentTarget.id);
|
||||
}
|
||||
function hDO(e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
function hDrop(e) {
|
||||
e.preventDefault();
|
||||
let t = e.target;
|
||||
if (t.id === "mLC") t = t.children[0]; // dropped on a container, not on an element
|
||||
else while (t && !t.classList.contains("iST")) t = t.parentNode; // find target element
|
||||
if (!t || t.id === "") return false; // not dropping on a valid target
|
||||
const id = e.dataTransfer.getData("text");
|
||||
t.parentNode.insertBefore(gId(id), t);
|
||||
recalcIds();
|
||||
UI();
|
||||
}
|
||||
function recalcIds() {
|
||||
gId("mLC").querySelectorAll(".iST").forEach((e,i)=>{
|
||||
let sOld = e.id.substring(1);
|
||||
let sNew = chrID(i);
|
||||
// update all element IDs and names
|
||||
e.id = "l"+sNew;
|
||||
e.querySelector("#n"+sOld).innerText = (i+1);
|
||||
// names: LT,LL,LA,MA,CO,WO,SP,LS,LC,L0,L1,L2,L3,L4,HS,CV,SL,RF,AW
|
||||
["LT","LL","LA","MA","CO","WO","SP","LS","LC","L0","L1","L2","L3","L4","HS","CV","SL","RF","AW"].forEach((n)=>{
|
||||
let el = e.querySelector("[name^="+n+"]");
|
||||
if (el) el.name = n + sNew;
|
||||
});
|
||||
// IDs: l,n,abl,LAdis,PSU,co,ls,dig?w,dig?l,psd,dig?c,p0d,p1d,p2d,p3d,p4d,net?h,dig?r,dig?s,dig?f,dig?a
|
||||
["l","n","abl","LAdis","PSU","co","ls","dig?w","dig?l","psd","dig?c","p0d","p1d","p2d","p3d","p4d","net?h","dig?r","dig?s","dig?f","dig?a"].forEach((n)=>{
|
||||
if (n.indexOf("?") < 0) n += "?";
|
||||
let el = e.querySelector("#"+n.replace("?", sOld));
|
||||
if (el) el.id = n.replace("?", sNew);
|
||||
});
|
||||
});
|
||||
}
|
||||
function tglSi(cs) {
|
||||
customStarts = cs;
|
||||
if (!customStarts) startsDirty = []; //set all starts to clean
|
||||
@@ -624,23 +665,23 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
let l = c.hw.led;
|
||||
l.ins.forEach((v,i,a)=>{
|
||||
addLEDs(1);
|
||||
for (var j=0; j<v.pin.length; j++) gN(`L${j}${i}`).value = v.pin[j];
|
||||
gN("LT"+i).value = v.type;
|
||||
gN("LS"+i).value = v.start;
|
||||
gN("LC"+i).value = v.len;
|
||||
gN("CO"+i).value = v.order & 0x0F;
|
||||
gN("SL"+i).value = v.skip;
|
||||
gN("RF"+i).checked = v.ref;
|
||||
gN("CV"+i).checked = v.rev;
|
||||
gN("AW"+i).value = v.rgbwm;
|
||||
gN("WO"+i).value = (v.order>>4) & 0x0F;
|
||||
gN("SP"+i).value = v.freq;
|
||||
gN("LA"+i).value = v.ledma;
|
||||
gN("MA"+i).value = v.maxpwr;
|
||||
for (var j=0; j<v.pin.length; j++) d.getElementsByName(`L${j}${i}`)[0].value = v.pin[j];
|
||||
d.getElementsByName("LT"+i)[0].value = v.type;
|
||||
d.getElementsByName("LS"+i)[0].value = v.start;
|
||||
d.getElementsByName("LC"+i)[0].value = v.len;
|
||||
d.getElementsByName("CO"+i)[0].value = v.order & 0x0F;
|
||||
d.getElementsByName("SL"+i)[0].value = v.skip;
|
||||
d.getElementsByName("RF"+i)[0].checked = v.ref;
|
||||
d.getElementsByName("CV"+i)[0].checked = v.rev;
|
||||
d.getElementsByName("AW"+i)[0].value = v.rgbwm;
|
||||
d.getElementsByName("WO"+i)[0].value = (v.order>>4) & 0x0F;
|
||||
d.getElementsByName("SP"+i)[0].value = v.freq;
|
||||
d.getElementsByName("LA"+i)[0].value = v.ledma;
|
||||
d.getElementsByName("MA"+i)[0].value = v.maxpwr;
|
||||
});
|
||||
gN("PR").checked = l.prl | 0;
|
||||
gN("MA").value = l.maxpwr;
|
||||
gN("ABL").checked = l.maxpwr > 0;
|
||||
d.getElementsByName("PR")[0].checked = l.prl | 0;
|
||||
d.getElementsByName("MA")[0].value = l.maxpwr;
|
||||
d.getElementsByName("ABL")[0].checked = l.maxpwr > 0;
|
||||
}
|
||||
if(c.hw.com) {
|
||||
resetCOM();
|
||||
@@ -654,22 +695,22 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
b.ins.forEach((v,i,a)=>{
|
||||
addBtn(i,v.pin[0],v.type);
|
||||
});
|
||||
gN("TT").value = b.tt;
|
||||
d.getElementsByName("TT")[0].value = b.tt;
|
||||
}
|
||||
let ir = c.hw.ir;
|
||||
if (ir) {
|
||||
gN("IR").value = ir.pin;
|
||||
gN("IT").value = ir.type;
|
||||
d.getElementsByName("IR")[0].value = ir.pin;
|
||||
d.getElementsByName("IT")[0].value = ir.type;
|
||||
}
|
||||
let rl = c.hw.relay;
|
||||
if (rl) {
|
||||
gN("RL").value = rl.pin;
|
||||
gN("RM").checked = rl.rev;
|
||||
gN("RO").checked = rl.odrain;
|
||||
d.getElementsByName("RL")[0].value = rl.pin;
|
||||
d.getElementsByName("RM")[0].checked = rl.rev;
|
||||
d.getElementsByName("RO")[0].checked = rl.odrain;
|
||||
}
|
||||
let li = c.light;
|
||||
if (li) {
|
||||
gN("MS").checked = li.aseg;
|
||||
d.getElementsByName("MS")[0].checked = li.aseg;
|
||||
}
|
||||
UI();
|
||||
}
|
||||
@@ -738,7 +779,7 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
function addDropdown(field) {
|
||||
let sel = cE('select');
|
||||
sel.classList.add("pin");
|
||||
let inp = gN(field);
|
||||
let inp = d.getElementsByName(field)[0];
|
||||
if (inp && inp.tagName === "INPUT" && (inp.type === "text" || inp.type === "number")) { // may also use nodeName
|
||||
let v = inp.value;
|
||||
let n = inp.name;
|
||||
@@ -771,94 +812,6 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
}
|
||||
return opt;
|
||||
}
|
||||
// dynamically enforce bus type availability based on current usage
|
||||
function updateTypeDropdowns(change=false, sel=undefined) { // change=true if called after a type change
|
||||
const abl = d.Sf.ABL.checked;
|
||||
let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]");
|
||||
let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0;
|
||||
let setPinConfig = (n,t) => {
|
||||
let p0d = "GPIO: ";
|
||||
let p1d = "";
|
||||
let off = "Off Refresh";
|
||||
switch (gT(t).t.charAt(0)) {
|
||||
case '2': // 2 pin digital
|
||||
p1d = "Clock "+p0d;
|
||||
// fallthrough
|
||||
case 'D': // digital
|
||||
p0d = "Data "+p0d;
|
||||
break;
|
||||
case 'A': // PWM analog
|
||||
if (numPins(t) > 1) p0d = "GPIOs: ";
|
||||
off = "Dithering";
|
||||
break;
|
||||
case 'N': // network
|
||||
p0d = "IP address: ";
|
||||
break;
|
||||
case 'V': // virtual/non-GPIO based
|
||||
p0d = "Config: "
|
||||
break;
|
||||
}
|
||||
gId("p0d"+n).innerText = p0d;
|
||||
gId("p1d"+n).innerText = p1d;
|
||||
gId("off"+n).innerText = off;
|
||||
// secondary pins show/hide (type string length is equivalent to number of pins used; except for network and on/off)
|
||||
let pins = Math.max(gT(t).t.length,1) + 3*isNet(t); // fixes network pins to 4
|
||||
for (let p=1; p<5; p++) {
|
||||
var LK = gN("L"+p+n);
|
||||
if (!LK) continue;
|
||||
LK.style.display = (p < pins) ? "inline" : "none";
|
||||
LK.required = (p < pins);
|
||||
if (p >= pins) LK.value="";
|
||||
}
|
||||
}
|
||||
// count currently used buses (including last added bus)
|
||||
LTs.forEach((s,i) => {
|
||||
let t = parseInt(s.value);
|
||||
if (isD1P(t)) digitalB++;
|
||||
if (isPWM(t)) analogB += numPins(t);
|
||||
if (isD2P(t)) twopinB++;
|
||||
if (isVir(t)) virtB++;
|
||||
});
|
||||
// now enable/disable type options according to limits in dropdowns
|
||||
if (sel) LTs = [sel]; // only update the changed one
|
||||
LTs.forEach((s,i) => {
|
||||
const t = parseInt(s.value);
|
||||
const disable = (q) => s.querySelectorAll(q).forEach(o => o.disabled = true);
|
||||
const enable = (q) => s.querySelectorAll(q).forEach(o => o.disabled = false);
|
||||
enable('option'); // reset all first
|
||||
// max digital count
|
||||
let maxDB = maxD - ((is32() || isS2() || isS3()) ? (!d.Sf["PR"].checked) * 8 - (!isS3()) : 0);
|
||||
// disallow adding more of a type that has reached its limit
|
||||
if (digitalB >= maxDB && !isD1P(t)) disable('option[data-type="D"]');
|
||||
if (twopinB >= 2 && !isD2P(t)) disable('option[data-type="2P"]');
|
||||
// determine available analog pins
|
||||
disable(`option[data-type^="${'A'.repeat(maxA - analogB + (isPWM(t) ? numPins(t) : 0) + 1)}"]`);
|
||||
// update UI elements
|
||||
let n = s.name.substring(2,3); // bus number (0-Z)
|
||||
setPinConfig(n,t);
|
||||
gId("abl"+n).style.display = (!abl || !isDig(t)) ? "none" : "inline"; // show/hide individual ABL settings
|
||||
if (change) { // did we change LED type?
|
||||
gId("rf"+n).checked = (gId("rf"+n).checked || t == 31); // LEDs require data in off state (mandatory for TM1814)
|
||||
if (isAna(t)) gN("LC"+n).value = 1; // for sanity change analog count just to 1 LED
|
||||
gN("LA"+n).min = (!isDig(t) || !abl) ? 0 : 1; // set minimum value for LED mA
|
||||
gN("MA"+n).min = (!isDig(t)) ? 0 : 250; // set minimum value for PSU mA
|
||||
}
|
||||
gId("rf"+n).onclick = mustR(t) ? (()=>{return false}) : (()=>{}); // prevent change change of "Refresh" checkmark when mandatory
|
||||
gId("co"+n).style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide color order for PWM
|
||||
gId("dig"+n+"w").style.display = (isDig(t) && hasW(t)) ? "inline":"none"; // show swap channels dropdown
|
||||
gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping
|
||||
if (!(isDig(t) && hasW(t))) gN("WO"+n).value = 0; // reset swapping
|
||||
gId("dig"+n+"c").style.display = (isAna(t)) ? "none":"inline"; // hide count for analog
|
||||
gId("dig"+n+"r").style.display = (isVir(t)) ? "none":"inline"; // hide reversed for virtual
|
||||
gId("dig"+n+"s").style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide skip 1st for virtual & analog
|
||||
gId("dig"+n+"f").style.display = (isDig(t) || (isPWM(t) && maxL>2048)) ? "inline":"none"; // hide refresh (PWM hijacks reffresh for dithering on ESP32)
|
||||
gId("dig"+n+"a").style.display = (hasW(t)) ? "inline":"none"; // auto calculate white
|
||||
gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off)
|
||||
gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed"; // change reverse text for analog else (rotated 180°)
|
||||
gId("net"+n+"h").style.display = isNet(t) && !is8266() ? "block" : "none"; // show host field for network types except on ESP8266
|
||||
if (!isNet(t) || is8266()) gN("HS"+n).value = ""; // cleart host field if not network type or ESP8266
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style>@import url("style.css");</style>
|
||||
</head>
|
||||
@@ -880,7 +833,7 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
Keep at <1A if powering LEDs directly from the ESP 5V pin!<br>
|
||||
If using multiple outputs it is recommended to use per-output limiter.<br>
|
||||
Analog (PWM) and virtual LEDs cannot use automatic brightness limiter.<br></i>
|
||||
<div id="psuMA">Maximum PSU Current: <input name="MA" type="number" class="xl" min="250" max="65500" oninput="UI()" required> mA<br></div>
|
||||
<div id="psuMA">Maximum PSU Current: <input name="MA" type="number" class="xl" min="250" max="65000" oninput="UI()" required> mA<br></div>
|
||||
Use per-output limiter: <input type="checkbox" name="PPL" onchange="UI()"><br>
|
||||
<div id="ppldis" style="display:none;">
|
||||
<i>Make sure you enter correct value for each LED output.<br>
|
||||
@@ -894,7 +847,7 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
</div>
|
||||
</div>
|
||||
<h3>Hardware setup</h3>
|
||||
<div id="mLC" ondragover="hDO(event)" ondrop="hDrop(event)">LED outputs:</div>
|
||||
<div id="mLC">LED outputs:</div>
|
||||
<hr class="sml">
|
||||
<button type="button" id="+" onclick="addLEDs(1,false)">+</button>
|
||||
<button type="button" id="-" onclick="addLEDs(-1,false)">-</button><br>
|
||||
@@ -906,7 +859,7 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
</div>
|
||||
<hr class="sml">
|
||||
<div id="prl" class="hide">Use parallel I2S: <input type="checkbox" name="PR"><br></div>
|
||||
Make a segment for each output: <input type="checkbox" name="MS" onchange="UI()"><br>
|
||||
Make a segment for each output: <input type="checkbox" name="MS"><br>
|
||||
Custom bus start indices: <input type="checkbox" onchange="tglSi(this.checked)" id="si"><br>
|
||||
<hr class="sml">
|
||||
<div id="color_order_mapping">
|
||||
@@ -914,7 +867,7 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
<div id="com_entries"></div>
|
||||
<hr class="sml">
|
||||
<button type="button" id="com_add" onclick="addCOM()">+</button>
|
||||
<button type="button" id="com_rem" onclick="remCOM()">-</button>
|
||||
<button type="button" id="com_rem" onclick="remCOM()">-</button><br>
|
||||
</div>
|
||||
<hr class="sml">
|
||||
<div id="btns"></div>
|
||||
@@ -943,9 +896,9 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
Turn LEDs on after power up/reset: <input type="checkbox" name="BO"><br>
|
||||
Default brightness: <input name="CA" type="number" class="m" min="1" max="255" required> (1-255)<br><br>
|
||||
Apply preset <input name="BP" type="number" class="m" min="0" max="250" required> at boot (0 uses values from above)<br><br>
|
||||
Use Gamma correction for color: <input type="checkbox" name="GC"> (recommended)<br>
|
||||
Use Gamma correction for color: <input type="checkbox" name="GC"> (strongly recommended)<br>
|
||||
Use Gamma correction for brightness: <input type="checkbox" name="GB"> (not recommended)<br>
|
||||
Use Gamma value: <input name="GV" type="number" class="m" min="1" max="3" step="0.1" required><br><br>
|
||||
Use Gamma value: <input name="GV" type="number" class="m" placeholder="2.8" min="1" max="3" step="0.1" required><br><br>
|
||||
Brightness factor: <input name="BF" type="number" class="m" min="1" max="255" required> %
|
||||
<h3>Transitions</h3>
|
||||
Default transition time: <input name="TD" type="number" class="xl" min="0" max="65500"> ms<br>
|
||||
|
||||
@@ -34,7 +34,12 @@
|
||||
function SetVal(){switch(parseInt(d.Sf.EP.value)){case 5568: d.Sf.DI.value = 5568; break; case 6454: d.Sf.DI.value = 6454; break; case 4048: d.Sf.DI.value = 4048; break; }; SP();FC();}
|
||||
function S(){
|
||||
getLoc();
|
||||
loadJS(getURL('/settings/s.js?p=4'), false, undefined, ()=>{SetVal();}); // If we set async false, file is loaded and executed, then next statement is processed
|
||||
loadJS(getURL('/settings/s.js?p=4'), false, ()=>{
|
||||
d.um_p = [];
|
||||
d.rsvd = [];
|
||||
d.ro_gpio = [];
|
||||
d.max_gpio = 50;
|
||||
}, ()=>{SetVal();pinDropdowns();}); // If we set async false, file is loaded and executed, then next statement is processed
|
||||
if (loc) d.Sf.action = getURL('/settings/sync');
|
||||
}
|
||||
function getURL(path) {
|
||||
@@ -42,6 +47,88 @@
|
||||
}
|
||||
function hideDMXInput(){gId("dmxInput").style.display="none";}
|
||||
function hideNoDMXInput(){gId("dmxInputOff").style.display="none";}
|
||||
function pinDropdowns() {
|
||||
let fields = ["IDMR","IDMT","IDME"]; // DMX input pins
|
||||
for (let i of d.Sf.elements) {
|
||||
if (i.type === "number" && fields.includes(i.name)) { //select all pin select elements
|
||||
let v = parseInt(i.value);
|
||||
let sel = addDropdown(i.name);
|
||||
for (var j = -1; j < d.max_gpio; j++) {
|
||||
if (d.rsvd.includes(j)) continue;
|
||||
let foundPin = d.um_p.indexOf(j);
|
||||
let txt = (j === -1) ? "unused" : `${j}`;
|
||||
if (foundPin >= 0 && j !== v) txt += ` used`; // already reserved pin
|
||||
if (d.ro_gpio.includes(j)) txt += " (R/O)";
|
||||
let opt = addOption(sel, txt, j);
|
||||
if (j === v) opt.selected = true; // this is "our" pin
|
||||
else if (d.um_p.includes(j)) opt.disabled = true; // someone else's pin
|
||||
}
|
||||
}
|
||||
}
|
||||
// update select options
|
||||
d.Sf.querySelectorAll("select.pin").forEach((e)=>{pinUpd(e);});
|
||||
}
|
||||
function pinUpd(e) {
|
||||
// update changed select options across DMX pins
|
||||
let oldV = parseInt(e.dataset.val);
|
||||
e.dataset.val = e.value;
|
||||
let txt = e.name;
|
||||
let selects = d.Sf.querySelectorAll("select.pin");
|
||||
for (let sel of selects) {
|
||||
if (sel == e) continue;
|
||||
Array.from(sel.options).forEach((i)=>{
|
||||
if (!(i.value==oldV || i.value==e.value)) return;
|
||||
if (i.value == -1) {
|
||||
i.text = "unused";
|
||||
return;
|
||||
}
|
||||
i.text = i.value;
|
||||
if (i.value==oldV) {
|
||||
i.disabled = false;
|
||||
}
|
||||
if (i.value==e.value) {
|
||||
i.disabled = true;
|
||||
i.text += ` ${txt}`;
|
||||
}
|
||||
if (d.ro_gpio.includes(parseInt(i.value))) i.text += " (R/O)";
|
||||
});
|
||||
}
|
||||
}
|
||||
function addDropdown(field) {
|
||||
let sel = cE('select');
|
||||
sel.classList.add("pin");
|
||||
let inp = d.getElementsByName(field)[0];
|
||||
if (inp && inp.tagName === "INPUT" && (inp.type === "text" || inp.type === "number")) {
|
||||
let v = inp.value;
|
||||
let n = inp.name;
|
||||
// copy the existing input element's attributes to the new select element
|
||||
for (var i = 0; i < inp.attributes.length; ++ i) {
|
||||
var att = inp.attributes[i];
|
||||
// type and value don't apply, so skip them
|
||||
if (att.name != 'type' && att.name != 'value' && att.name != 'class' && att.name != 'style') {
|
||||
sel.setAttribute(att.name, att.value);
|
||||
}
|
||||
}
|
||||
sel.setAttribute("data-val", v);
|
||||
sel.setAttribute("onchange", "pinUpd(this)");
|
||||
// finally, replace the old input element with the new select element
|
||||
inp.parentElement.replaceChild(sel, inp);
|
||||
return sel;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function addOption(sel,txt,val) {
|
||||
if (sel===null) return; // select object missing
|
||||
let opt = cE("option");
|
||||
opt.value = val;
|
||||
opt.text = txt;
|
||||
sel.appendChild(opt);
|
||||
for (let i=0; i<sel.childNodes.length; i++) {
|
||||
let c = sel.childNodes[i];
|
||||
if (c.value == sel.dataset.val) sel.selectedIndex = i;
|
||||
}
|
||||
return opt;
|
||||
}
|
||||
</script>
|
||||
<style>@import url("style.css");</style>
|
||||
</head>
|
||||
|
||||
@@ -30,11 +30,19 @@ void handleDDPPacket(e131_packet_t* p) {
|
||||
|
||||
uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed;
|
||||
start += DMXAddress / ddpChannelsPerLed;
|
||||
unsigned stop = start + htons(p->dataLen) / ddpChannelsPerLed;
|
||||
uint16_t dataLen = htons(p->dataLen);
|
||||
unsigned stop = start + dataLen / ddpChannelsPerLed;
|
||||
uint8_t* data = p->data;
|
||||
unsigned c = 0;
|
||||
if (p->flags & DDP_TIMECODE_FLAG) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later
|
||||
|
||||
unsigned numLeds = stop - start; // stop >= start is guaranteed
|
||||
unsigned maxDataIndex = c + numLeds * ddpChannelsPerLed; // validate bounds before accessing data array
|
||||
if (maxDataIndex > dataLen) {
|
||||
DEBUG_PRINTLN(F("DDP packet data bounds exceeded, rejecting."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (realtimeMode != REALTIME_MODE_DDP) ddpSeenPush = false; // just starting, no push yet
|
||||
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP);
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ void IRAM_ATTR touchButtonISR();
|
||||
bool backupConfig();
|
||||
bool restoreConfig();
|
||||
bool verifyConfig();
|
||||
bool configBackupExists();
|
||||
void resetConfig();
|
||||
bool deserializeConfig(JsonObject doc, bool fromFS = false);
|
||||
bool deserializeConfigFromFS();
|
||||
@@ -103,6 +104,7 @@ inline bool readObjectFromFile(const String &file, const char* key, JsonDocument
|
||||
bool copyFile(const char* src_path, const char* dst_path);
|
||||
bool backupFile(const char* filename);
|
||||
bool restoreFile(const char* filename);
|
||||
bool checkBackupExists(const char* filename);
|
||||
bool validateJsonFile(const char* filename);
|
||||
void dumpFilesToSerial();
|
||||
|
||||
|
||||
@@ -557,6 +557,12 @@ bool restoreFile(const char* filename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool checkBackupExists(const char* filename) {
|
||||
char backupname[32];
|
||||
snprintf_P(backupname, sizeof(backupname), s_backup_fmt, filename + 1); // skip leading '/' in filename
|
||||
return WLED_FS.exists(backupname);
|
||||
}
|
||||
|
||||
bool validateJsonFile(const char* filename) {
|
||||
if (!WLED_FS.exists(filename)) return false;
|
||||
File file = WLED_FS.open(filename, "r");
|
||||
|
||||
@@ -474,7 +474,7 @@ void WLED::setup()
|
||||
|
||||
if (needsCfgSave) serializeConfigToFS(); // usermods required new parameters; need to wait for strip to be initialised #4752
|
||||
|
||||
if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0)
|
||||
if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0 && !configBackupExists())
|
||||
showWelcomePage = true;
|
||||
WiFi.persistent(false);
|
||||
WiFi.onEvent(WiFiEvent);
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
*/
|
||||
#ifdef WLED_ENABLE_WEBSOCKETS
|
||||
|
||||
// define some constants for binary protocols, dont use defines but C++ style constexpr
|
||||
constexpr uint8_t BINARY_PROTOCOL_GENERIC = 0xFF; // generic / auto detect NOT IMPLEMENTED
|
||||
constexpr uint8_t BINARY_PROTOCOL_E131 = P_E131; // = 0, untested!
|
||||
constexpr uint8_t BINARY_PROTOCOL_ARTNET = P_ARTNET; // = 1, untested!
|
||||
constexpr uint8_t BINARY_PROTOCOL_DDP = P_DDP; // = 2
|
||||
|
||||
uint16_t wsLiveClientId = 0;
|
||||
unsigned long wsLastLiveTime = 0;
|
||||
//uint8_t* wsFrameBuffer = nullptr;
|
||||
@@ -25,7 +31,7 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
|
||||
// data packet
|
||||
AwsFrameInfo * info = (AwsFrameInfo*)arg;
|
||||
if(info->final && info->index == 0 && info->len == len){
|
||||
// the whole message is in a single frame and we got all of its data (max. 1450 bytes)
|
||||
// the whole message is in a single frame and we got all of its data (max. 1428 bytes / ESP8266: 528 bytes)
|
||||
if(info->opcode == WS_TEXT)
|
||||
{
|
||||
if (len > 0 && len < 10 && data[0] == 'p') {
|
||||
@@ -71,8 +77,29 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
|
||||
// force broadcast in 500ms after updating client
|
||||
//lastInterfaceUpdate = millis() - (INTERFACE_UPDATE_COOLDOWN -500); // ESP8266 does not like this
|
||||
}
|
||||
}else if (info->opcode == WS_BINARY) {
|
||||
// first byte determines protocol. Note: since e131_packet_t is "packed", the compiler handles alignment issues
|
||||
//DEBUG_PRINTF_P(PSTR("WS binary message: len %u, byte0: %u\n"), len, data[0]);
|
||||
int offset = 1; // offset to skip protocol byte
|
||||
switch (data[0]) {
|
||||
case BINARY_PROTOCOL_E131:
|
||||
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_E131);
|
||||
break;
|
||||
case BINARY_PROTOCOL_ARTNET:
|
||||
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_ARTNET);
|
||||
break;
|
||||
case BINARY_PROTOCOL_DDP:
|
||||
if (len < 10 + offset) return; // DDP header is 10 bytes (+1 protocol byte)
|
||||
size_t ddpDataLen = (data[8+offset] << 8) | data[9+offset]; // data length in bytes from DDP header
|
||||
uint8_t flags = data[0+offset];
|
||||
if ((flags & DDP_TIMECODE_FLAG) ) ddpDataLen += 4; // timecode flag adds 4 bytes to data length
|
||||
if (len < (10 + offset + ddpDataLen)) return; // not enough data, prevent out of bounds read
|
||||
// could be a valid DDP packet, forward to handler
|
||||
handleE131Packet((e131_packet_t*)&data[offset], client->remoteIP(), P_DDP);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DEBUG_PRINTF_P(PSTR("WS multipart message: final %u index %u len %u total %u\n"), info->final, info->index, len, (uint32_t)info->len);
|
||||
//message is comprised of multiple frames or the frame is split into multiple packets
|
||||
//if(info->index == 0){
|
||||
//if (!wsFrameBuffer && len < 4096) wsFrameBuffer = new uint8_t[4096];
|
||||
|
||||
Reference in New Issue
Block a user