mirror of
				https://github.com/wled/WLED.git
				synced 2025-10-31 06:38:52 +00:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			main
			...
			dnd-output
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | a18b133942 | ||
|   | 00fab2e86d | 
							
								
								
									
										220
									
								
								wled00/FX.cpp
									
									
									
									
									
								
							
							
						
						
									
										220
									
								
								wled00/FX.cpp
									
									
									
									
									
								
							| @@ -678,7 +678,7 @@ uint16_t mode_twinkle(void) { | ||||
|     SEGENV.step = it; | ||||
|   } | ||||
|  | ||||
|   uint16_t PRNG16 = SEGENV.aux1; | ||||
|   unsigned PRNG16 = SEGENV.aux1; | ||||
|  | ||||
|   for (unsigned i = 0; i < SEGENV.aux0; i++) | ||||
|   { | ||||
| @@ -5196,162 +5196,112 @@ static const char _data_FX_MODE_2DFRIZZLES[] PROGMEM = "Frizzles@X frequency,Y f | ||||
| /////////////////////////////////////////// | ||||
| //   2D Cellular Automata Game of life   // | ||||
| /////////////////////////////////////////// | ||||
| typedef struct Cell { | ||||
|     uint8_t alive : 1, faded : 1, toggleStatus : 1, edgeCell: 1, oscillatorCheck : 1, spaceshipCheck : 1, unused : 2; | ||||
| } Cell; | ||||
| typedef struct ColorCount { | ||||
|   CRGB color; | ||||
|   int8_t count; | ||||
| } colorCount; | ||||
|  | ||||
| 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 | ||||
| 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 | ||||
|   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; | ||||
|  | ||||
|   if (!SEGENV.allocateData(SEGMENT.length() * sizeof(Cell))) return mode_static(); // allocation failed | ||||
|   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) | ||||
|  | ||||
|   Cell *cells = reinterpret_cast<Cell*> (SEGENV.data); | ||||
|   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);  | ||||
|  | ||||
|   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); | ||||
|   CRGB backgroundColor = SEGCOLOR(1); | ||||
|  | ||||
|   uint32_t bgColor    = SEGCOLOR(1); | ||||
|   uint32_t birthColor = SEGMENT.color_from_palette(128, false, PALETTE_SOLID_WRAP, 255); | ||||
|   if (SEGENV.call == 0 || strip.now - SEGMENT.step > 3000) { | ||||
|     SEGENV.step = strip.now; | ||||
|     SEGENV.aux0 = 0; | ||||
|  | ||||
|   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); | ||||
|     //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)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   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); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     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) | ||||
|     return FRAMETIME; | ||||
|   } | ||||
|  | ||||
|   // Repeat detection | ||||
|   bool updateOscillator = generation % 16 == 0; | ||||
|   bool updateSpaceship  = gliderLength && generation % gliderLength == 0; | ||||
|   bool repeatingOscillator = true, repeatingSpaceship = true, emptyGrid = true; | ||||
|   //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); | ||||
|  | ||||
|   unsigned cIndex = maxIndex-1; | ||||
|   for (unsigned y = rows; y--; ) for (unsigned x = cols; x--; cIndex--) { | ||||
|     Cell& cell = cells[cIndex]; | ||||
|   //calculate new leds | ||||
|   for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { | ||||
|  | ||||
|     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; | ||||
|     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 | ||||
|  | ||||
|     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) { | ||||
|     // 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++; | ||||
|         if (!neighbor.toggleStatus && neighbors < 4) { // Alive and not dying | ||||
|           parentIdx[aliveParents++] = nIndex; | ||||
|         } | ||||
|         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 | ||||
|       } | ||||
|     } // 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 | ||||
|  | ||||
|     uint32_t newColor; | ||||
|     bool needsColor = false; | ||||
|   // 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; | ||||
|  | ||||
|     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@!,,Blur,,,,,Mutation;!,!;!;2;pal=11,sx=128"; | ||||
| static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!;!,!;!;2"; | ||||
|  | ||||
|  | ||||
| ///////////////////////// | ||||
|   | ||||
| @@ -777,10 +777,6 @@ 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,62 +116,3 @@ 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,14 +62,32 @@ | ||||
|       if (window.location.href.indexOf("?ws") == -1) {update(); return;} | ||||
|  | ||||
|       // Initialize WebSocket connection | ||||
|       ws = connectWs(function () { | ||||
|         //console.info("Peek WS open"); | ||||
|         ws.send('{"lv":true}'); | ||||
|       }); | ||||
|       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.addEventListener('message', (e) => { | ||||
|         try { | ||||
|           if (toString.call(e.data) === '[object ArrayBuffer]') { | ||||
|             let leds = new Uint8Array(e.data); | ||||
|             let leds = new Uint8Array(event.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]})`); | ||||
|   | ||||
| @@ -10,7 +10,6 @@ | ||||
| 		margin: 0; | ||||
| 	} | ||||
| 	</style> | ||||
| 	<script src="common.js"></script> | ||||
| </head> | ||||
| <body> | ||||
| 	<canvas id="canv"></canvas> | ||||
| @@ -27,13 +26,30 @@ | ||||
| 		var ctx = c.getContext('2d'); | ||||
| 		if (ctx) { // Access the rendering context | ||||
| 			// use parent WS or open new | ||||
| 			var ws = connectWs(()=>{ | ||||
| 				ws.send('{"lv":true}'); | ||||
| 			}); | ||||
| 			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"; | ||||
| 			ws.addEventListener('message',(e)=>{ | ||||
| 				try { | ||||
| 					if (toString.call(e.data) === '[object ArrayBuffer]') { | ||||
| 						let leds = new Uint8Array(e.data); | ||||
| 						let leds = new Uint8Array(event.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 | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| <!DOCTYPE html> | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
| 	<meta charset="utf-8"> | ||||
| @@ -131,7 +131,7 @@ | ||||
| 			if (!en) { | ||||
| 				// limiter disabled | ||||
| 				d.Sf.PPL.checked = false; | ||||
| //				d.Sf.querySelectorAll("#mLC select[name^=LAsel]").forEach((e)=>{e.selectedIndex = 0;}); // select default LED mA | ||||
| //				d.Sf.querySelectorAll("#mLC select[name^=LL]").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,9 +160,10 @@ | ||||
| 			if (ppl) d.Sf.MA.value = sumMA; // populate UI ABL value if PPL used | ||||
| 		} | ||||
| 		// enable and update LED Amps | ||||
| 		function enLA(s,n) | ||||
| 		function enLA(s) | ||||
| 		{ | ||||
| 			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 | ||||
| @@ -177,9 +178,9 @@ | ||||
| 			}); | ||||
| 			d.Sf.ABL.checked = en; | ||||
| 			// select appropriate LED current | ||||
| 			d.Sf.querySelectorAll("#mLC select[name^=LAsel]").forEach((sel,x)=>{ | ||||
| 			d.Sf.querySelectorAll("#mLC select[name^=LL]").forEach((sel)=>{ | ||||
| 				sel.value = 0; // set custom | ||||
| 				var n = chrID(x); | ||||
| 				var n = sel.name.substring(2); // bus number (0-Z) | ||||
| 				if (en) | ||||
| 					switch (parseInt(d.Sf["LA"+n].value)) { | ||||
| 						case 0: break; // disable ABL | ||||
| @@ -190,7 +191,7 @@ | ||||
| 						case 255: sel.value = 255; break; | ||||
| 					} | ||||
| 				else sel.value = 0; | ||||
| 				enLA(sel,n); // configure individual limiter | ||||
| 				enLA(sel); // configure individual limiter | ||||
| 			}); | ||||
| 			enABL(); | ||||
| 			gId('m1').innerHTML = maxM; | ||||
| @@ -270,7 +271,7 @@ | ||||
| 			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) | ||||
| 				s.disabled = (i < LTs.length-1); // 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); | ||||
| @@ -464,12 +465,12 @@ | ||||
|  | ||||
| 			if (n==1) { | ||||
| // npm run build has trouble minimizing spaces inside string | ||||
| 				var cn = `<div class="iST"> | ||||
| 				var cn = `<div class="iST" draggable="true" ondragstart="hDS(event)" id="l${s}" style="cursor:grab;"> | ||||
| <hr class="sml"> | ||||
| ${i+1}: | ||||
| <span id="n${s}">${i+1}</span>: | ||||
| <select name="LT${s}" onchange="UI(true)"></select><br> | ||||
| <div id="abl${s}"> | ||||
| mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();"> | ||||
| mA/LED: <select name="LL${s}" onchange="enLA(this);UI();"> | ||||
| <option value="55" selected>55mA (typ. 5V WS281x)</option> | ||||
| <option value="35">35mA (eco WS2812)</option> | ||||
| <option value="30">30mA (typ. 12V)</option> | ||||
| @@ -521,7 +522,7 @@ mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();"> | ||||
| 						} | ||||
| 					} | ||||
| 				}); | ||||
| 				enLA(d.Sf["LAsel"+s],s); // update LED mA | ||||
| 				enLA(gN("LL"+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 | ||||
| @@ -617,6 +618,43 @@ 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 | ||||
| @@ -847,7 +885,7 @@ Swap: <select id="xw${s}" name="XW${s}"> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 		<h3>Hardware setup</h3> | ||||
| 		<div id="mLC">LED outputs:</div> | ||||
| 		<div id="mLC" ondragover="hDO(event)" ondrop="hDrop(event)">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> | ||||
|   | ||||
| @@ -30,19 +30,11 @@ void handleDDPPacket(e131_packet_t* p) { | ||||
|  | ||||
|   uint32_t start =  htonl(p->channelOffset) / ddpChannelsPerLed; | ||||
|   start += DMXAddress / ddpChannelsPerLed; | ||||
|   uint16_t dataLen = htons(p->dataLen); | ||||
|   unsigned stop = start + dataLen / ddpChannelsPerLed; | ||||
|   unsigned stop = start + htons(p->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,7 +27,6 @@ void IRAM_ATTR touchButtonISR(); | ||||
| bool backupConfig(); | ||||
| bool restoreConfig(); | ||||
| bool verifyConfig(); | ||||
| bool configBackupExists(); | ||||
| void resetConfig(); | ||||
| bool deserializeConfig(JsonObject doc, bool fromFS = false); | ||||
| bool deserializeConfigFromFS(); | ||||
| @@ -104,7 +103,6 @@ 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,12 +557,6 @@ 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 && !configBackupExists()) | ||||
|   if (strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) == 0) | ||||
|     showWelcomePage = true; | ||||
|   WiFi.persistent(false); | ||||
|   WiFi.onEvent(WiFiEvent); | ||||
|   | ||||
| @@ -5,12 +5,6 @@ | ||||
|  */ | ||||
| #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; | ||||
| @@ -31,7 +25,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. 1428 bytes / ESP8266: 528 bytes) | ||||
|       // the whole message is in a single frame and we got all of its data (max. 1450 bytes) | ||||
|       if(info->opcode == WS_TEXT) | ||||
|       { | ||||
|         if (len > 0 && len < 10 && data[0] == 'p') { | ||||
| @@ -77,29 +71,8 @@ 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