Skip to content

Commit 60838a6

Browse files
authored
Merge branch 'wled:main' into main
2 parents 834acc0 + b60313e commit 60838a6

File tree

10 files changed

+258
-136
lines changed

10 files changed

+258
-136
lines changed

wled00/FX.cpp

Lines changed: 137 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -5196,112 +5196,162 @@ static const char _data_FX_MODE_2DFRIZZLES[] PROGMEM = "Frizzles@X frequency,Y f
51965196
///////////////////////////////////////////
51975197
// 2D Cellular Automata Game of life //
51985198
///////////////////////////////////////////
5199-
typedef struct ColorCount {
5200-
CRGB color;
5201-
int8_t count;
5202-
} colorCount;
5199+
typedef struct Cell {
5200+
uint8_t alive : 1, faded : 1, toggleStatus : 1, edgeCell: 1, oscillatorCheck : 1, spaceshipCheck : 1, unused : 2;
5201+
} Cell;
52035202

5204-
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
5203+
uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/
5204+
// and https://github.com/DougHaber/nlife-color , Modified By: Brandon Butler
52055205
if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up
5206+
const int cols = SEG_W, rows = SEG_H;
5207+
const unsigned maxIndex = cols * rows;
52065208

5207-
const int cols = SEG_W;
5208-
const int rows = SEG_H;
5209-
const auto XY = [&](int x, int y) { return (x%cols) + (y%rows) * cols; };
5210-
const unsigned dataSize = sizeof(CRGB) * SEGMENT.length(); // using width*height prevents reallocation if mirroring is enabled
5211-
const int crcBufferLen = 2; //(SEGMENT.width() + SEGMENT.height())*71/100; // roughly sqrt(2)/2 for better repetition detection (Ewowi)
5209+
if (!SEGENV.allocateData(SEGMENT.length() * sizeof(Cell))) return mode_static(); // allocation failed
52125210

5213-
if (!SEGENV.allocateData(dataSize + sizeof(uint16_t)*crcBufferLen)) return mode_static(); //allocation failed
5214-
CRGB *prevLeds = reinterpret_cast<CRGB*>(SEGENV.data);
5215-
uint16_t *crcBuffer = reinterpret_cast<uint16_t*>(SEGENV.data + dataSize);
5211+
Cell *cells = reinterpret_cast<Cell*> (SEGENV.data);
52165212

5217-
CRGB backgroundColor = SEGCOLOR(1);
5213+
uint16_t& generation = SEGENV.aux0, &gliderLength = SEGENV.aux1; // rename aux variables for clarity
5214+
bool mutate = SEGMENT.check3;
5215+
uint8_t blur = map(SEGMENT.custom1, 0, 255, 255, 4);
52185216

5219-
if (SEGENV.call == 0 || strip.now - SEGMENT.step > 3000) {
5220-
SEGENV.step = strip.now;
5221-
SEGENV.aux0 = 0;
5217+
uint32_t bgColor = SEGCOLOR(1);
5218+
uint32_t birthColor = SEGMENT.color_from_palette(128, false, PALETTE_SOLID_WRAP, 255);
52225219

5223-
//give the leds random state and colors (based on intensity, colors from palette or all posible colors are chosen)
5224-
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) {
5225-
unsigned state = hw_random8()%2;
5226-
if (state == 0)
5227-
SEGMENT.setPixelColorXY(x,y, backgroundColor);
5228-
else
5229-
SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 255));
5220+
bool setup = SEGENV.call == 0;
5221+
if (setup) {
5222+
// Calculate glider length LCM(rows,cols)*4 once
5223+
unsigned a = rows, b = cols;
5224+
while (b) { unsigned t = b; b = a % b; a = t; }
5225+
gliderLength = (cols * rows / a) << 2;
5226+
}
5227+
5228+
if (abs(long(strip.now) - long(SEGENV.step)) > 2000) SEGENV.step = 0; // Timebase jump fix
5229+
bool paused = SEGENV.step > strip.now;
5230+
5231+
// Setup New Game of Life
5232+
if ((!paused && generation == 0) || setup) {
5233+
SEGENV.step = strip.now + 1280; // show initial state for 1.28 seconds
5234+
generation = 1;
5235+
paused = true;
5236+
//Setup Grid
5237+
memset(cells, 0, maxIndex * sizeof(Cell));
5238+
5239+
for (unsigned i = 0; i < maxIndex; i++) {
5240+
bool isAlive = !hw_random8(3); // ~33%
5241+
cells[i].alive = isAlive;
5242+
cells[i].faded = !isAlive;
5243+
unsigned x = i % cols, y = i / cols;
5244+
cells[i].edgeCell = (x == 0 || x == cols-1 || y == 0 || y == rows-1);
5245+
5246+
SEGMENT.setPixelColor(i, isAlive ? SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 0) : bgColor);
52305247
}
5248+
}
52315249

5232-
for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) prevLeds[XY(x,y)] = CRGB::Black;
5233-
memset(crcBuffer, 0, sizeof(uint16_t)*crcBufferLen);
5234-
} else if (strip.now - SEGENV.step < FRAMETIME_FIXED * (uint32_t)map(SEGMENT.speed,0,255,64,4)) {
5235-
// update only when appropriate time passes (in 42 FPS slots)
5250+
if (paused || (strip.now - SEGENV.step < 1000 / map(SEGMENT.speed,0,255,1,42))) {
5251+
// Redraw if paused or between updates to remove blur
5252+
for (unsigned i = maxIndex; i--; ) {
5253+
if (!cells[i].alive) {
5254+
uint32_t cellColor = SEGMENT.getPixelColor(i);
5255+
if (cellColor != bgColor) {
5256+
uint32_t newColor;
5257+
bool needsColor = false;
5258+
if (cells[i].faded) { newColor = bgColor; needsColor = true; }
5259+
else {
5260+
uint32_t blended = color_blend(cellColor, bgColor, 2);
5261+
if (blended == cellColor) { blended = bgColor; cells[i].faded = 1; }
5262+
newColor = blended; needsColor = true;
5263+
}
5264+
if (needsColor) SEGMENT.setPixelColor(i, newColor);
5265+
}
5266+
}
5267+
}
52365268
return FRAMETIME;
52375269
}
52385270

5239-
//copy previous leds (save previous generation)
5240-
//NOTE: using lossy getPixelColor() is a benefit as endlessly repeating patterns will eventually fade out causing a reset
5241-
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) prevLeds[XY(x,y)] = SEGMENT.getPixelColorXY(x,y);
5242-
5243-
//calculate new leds
5244-
for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) {
5271+
// Repeat detection
5272+
bool updateOscillator = generation % 16 == 0;
5273+
bool updateSpaceship = gliderLength && generation % gliderLength == 0;
5274+
bool repeatingOscillator = true, repeatingSpaceship = true, emptyGrid = true;
5275+
5276+
unsigned cIndex = maxIndex-1;
5277+
for (unsigned y = rows; y--; ) for (unsigned x = cols; x--; cIndex--) {
5278+
Cell& cell = cells[cIndex];
5279+
5280+
if (cell.alive) emptyGrid = false;
5281+
if (cell.oscillatorCheck != cell.alive) repeatingOscillator = false;
5282+
if (cell.spaceshipCheck != cell.alive) repeatingSpaceship = false;
5283+
if (updateOscillator) cell.oscillatorCheck = cell.alive;
5284+
if (updateSpaceship) cell.spaceshipCheck = cell.alive;
5285+
5286+
unsigned neighbors = 0, aliveParents = 0, parentIdx[3];
5287+
// Count alive neighbors
5288+
for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) if (i || j) {
5289+
int nX = x + j, nY = y + i;
5290+
if (cell.edgeCell) {
5291+
nX = (nX + cols) % cols;
5292+
nY = (nY + rows) % rows;
5293+
}
5294+
unsigned nIndex = nX + nY * cols;
5295+
Cell& neighbor = cells[nIndex];
5296+
if (neighbor.alive) {
5297+
neighbors++;
5298+
if (!neighbor.toggleStatus && neighbors < 4) { // Alive and not dying
5299+
parentIdx[aliveParents++] = nIndex;
5300+
}
5301+
}
5302+
}
52455303

5246-
colorCount colorsCount[9]; // count the different colors in the 3*3 matrix
5247-
for (int i=0; i<9; i++) colorsCount[i] = {backgroundColor, 0}; // init colorsCount
5304+
uint32_t newColor;
5305+
bool needsColor = false;
52485306

5249-
// iterate through neighbors and count them and their different colors
5250-
int neighbors = 0;
5251-
for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // iterate through 3*3 matrix
5252-
if (i==0 && j==0) continue; // ignore itself
5253-
// wrap around segment
5254-
int xx = x+i, yy = y+j;
5255-
if (x+i < 0) xx = cols-1; else if (x+i >= cols) xx = 0;
5256-
if (y+j < 0) yy = rows-1; else if (y+j >= rows) yy = 0;
5307+
if (cell.alive && (neighbors < 2 || neighbors > 3)) { // Loneliness or Overpopulation
5308+
cell.toggleStatus = 1;
5309+
if (blur == 255) cell.faded = 1;
5310+
newColor = cell.faded ? bgColor : color_blend(SEGMENT.getPixelColor(cIndex), bgColor, blur);
5311+
needsColor = true;
5312+
}
5313+
else if (!cell.alive) {
5314+
byte mutationRoll = mutate ? hw_random8(128) : 1; // if 0: 3 neighbor births fail and 2 neighbor births mutate
5315+
if ((neighbors == 3 && mutationRoll) || (mutate && neighbors == 2 && !mutationRoll)) { // Reproduction or Mutation
5316+
cell.toggleStatus = 1;
5317+
cell.faded = 0;
52575318

5258-
unsigned xy = XY(xx, yy); // previous cell xy to check
5259-
// count different neighbours and colors
5260-
if (prevLeds[xy] != backgroundColor) {
5261-
neighbors++;
5262-
bool colorFound = false;
5263-
int k;
5264-
for (k=0; k<9 && colorsCount[k].count != 0; k++)
5265-
if (colorsCount[k].color == prevLeds[xy]) {
5266-
colorsCount[k].count++;
5267-
colorFound = true;
5268-
}
5269-
if (!colorFound) colorsCount[k] = {prevLeds[xy], 1}; //add new color found in the array
5319+
if (aliveParents) {
5320+
// Set color based on random neighbor
5321+
unsigned parentIndex = parentIdx[random8(aliveParents)];
5322+
birthColor = SEGMENT.getPixelColor(parentIndex);
5323+
}
5324+
newColor = birthColor;
5325+
needsColor = true;
52705326
}
5271-
} // i,j
5272-
5273-
// Rules of Life
5274-
uint32_t col = uint32_t(prevLeds[XY(x,y)]) & 0x00FFFFFF; // uint32_t operator returns RGBA, we want RGBW -> cut off "alpha" byte
5275-
uint32_t bgc = RGBW32(backgroundColor.r, backgroundColor.g, backgroundColor.b, 0);
5276-
if ((col != bgc) && (neighbors < 2)) SEGMENT.setPixelColorXY(x,y, bgc); // Loneliness
5277-
else if ((col != bgc) && (neighbors > 3)) SEGMENT.setPixelColorXY(x,y, bgc); // Overpopulation
5278-
else if ((col == bgc) && (neighbors == 3)) { // Reproduction
5279-
// find dominant color and assign it to a cell
5280-
colorCount dominantColorCount = {backgroundColor, 0};
5281-
for (int i=0; i<9 && colorsCount[i].count != 0; i++)
5282-
if (colorsCount[i].count > dominantColorCount.count) dominantColorCount = colorsCount[i];
5283-
// assign the dominant color w/ a bit of randomness to avoid "gliders"
5284-
if (dominantColorCount.count > 0 && hw_random8(128)) SEGMENT.setPixelColorXY(x,y, dominantColorCount.color);
5285-
} else if ((col == bgc) && (neighbors == 2) && !hw_random8(128)) { // Mutation
5286-
SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(hw_random8(), false, PALETTE_SOLID_WRAP, 255));
5287-
}
5288-
// else do nothing!
5289-
} //x,y
5290-
5291-
// calculate CRC16 of leds
5292-
uint16_t crc = crc16((const unsigned char*)prevLeds, dataSize);
5293-
// check if we had same CRC and reset if needed
5294-
bool repetition = false;
5295-
for (int i=0; i<crcBufferLen && !repetition; i++) repetition = (crc == crcBuffer[i]); // (Ewowi)
5296-
// same CRC would mean image did not change or was repeating itself
5297-
if (!repetition) SEGENV.step = strip.now; //if no repetition avoid reset
5298-
// remember CRCs across frames
5299-
crcBuffer[SEGENV.aux0] = crc;
5300-
++SEGENV.aux0 %= crcBufferLen;
5327+
else if (!cell.faded) {// No change, fade dead cells
5328+
uint32_t cellColor = SEGMENT.getPixelColor(cIndex);
5329+
uint32_t blended = color_blend(cellColor, bgColor, blur);
5330+
if (blended == cellColor) { blended = bgColor; cell.faded = 1; }
5331+
newColor = blended;
5332+
needsColor = true;
5333+
}
5334+
}
5335+
5336+
if (needsColor) SEGMENT.setPixelColor(cIndex, newColor);
5337+
}
5338+
// Loop through cells, if toggle, swap alive status
5339+
for (unsigned i = maxIndex; i--; ) {
5340+
cells[i].alive ^= cells[i].toggleStatus;
5341+
cells[i].toggleStatus = 0;
5342+
}
53015343

5344+
if (repeatingOscillator || repeatingSpaceship || emptyGrid) {
5345+
generation = 0; // reset on next call
5346+
SEGENV.step += 1024; // pause final generation for ~1 second
5347+
}
5348+
else {
5349+
++generation;
5350+
SEGENV.step = strip.now;
5351+
}
53025352
return FRAMETIME;
53035353
} // mode_2Dgameoflife()
5304-
static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!;!,!;!;2";
5354+
static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,,Blur,,,,,Mutation;!,!;!;2;pal=11,sx=128";
53055355

53065356

53075357
/////////////////////////

wled00/cfg.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,10 @@ bool verifyConfig() {
777777
return validateJsonFile(s_cfg_json);
778778
}
779779

780+
bool configBackupExists() {
781+
return checkBackupExists(s_cfg_json);
782+
}
783+
780784
// rename config file and reboot
781785
// if the cfg file doesn't exist, such as after a reset, do nothing
782786
void resetConfig() {

wled00/data/common.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,62 @@ function uploadFile(fileObj, name) {
116116
fileObj.value = '';
117117
return false;
118118
}
119+
// connect to WebSocket, use parent WS or open new
120+
function connectWs(onOpen) {
121+
try {
122+
if (top.window.ws && top.window.ws.readyState === WebSocket.OPEN) {
123+
if (onOpen) onOpen();
124+
return top.window.ws;
125+
}
126+
} catch (e) {}
127+
128+
getLoc(); // ensure globals (loc, locip, locproto) are up to date
129+
let url = loc ? getURL('/ws').replace("http","ws") : "ws://"+window.location.hostname+"/ws";
130+
let ws = new WebSocket(url);
131+
ws.binaryType = "arraybuffer";
132+
if (onOpen) { ws.onopen = onOpen; }
133+
try { top.window.ws = ws; } catch (e) {} // store in parent for reuse
134+
return ws;
135+
}
136+
137+
// send LED colors to ESP using WebSocket and DDP protocol (RGB)
138+
// ws: WebSocket object
139+
// start: start pixel index
140+
// len: number of pixels to send
141+
// colors: Uint8Array with RGB values (3*len bytes)
142+
function sendDDP(ws, start, len, colors) {
143+
if (!colors || colors.length < len * 3) return false; // not enough color data
144+
let maxDDPpx = 472; // must fit into one WebSocket frame of 1428 bytes, DDP header is 10+1 bytes -> 472 RGB pixels
145+
//let maxDDPpx = 172; // ESP8266: must fit into one WebSocket frame of 528 bytes -> 172 RGB pixels TODO: add support for ESP8266?
146+
if (!ws || ws.readyState !== WebSocket.OPEN) return false;
147+
// send in chunks of maxDDPpx
148+
for (let i = 0; i < len; i += maxDDPpx) {
149+
let cnt = Math.min(maxDDPpx, len - i);
150+
let off = (start + i) * 3; // DDP pixel offset in bytes
151+
let dLen = cnt * 3;
152+
let cOff = i * 3; // offset in color buffer
153+
let pkt = new Uint8Array(11 + dLen); // DDP header is 10 bytes, plus 1 byte for WLED websocket protocol indicator
154+
pkt[0] = 0x02; // DDP protocol indicator for WLED websocket. Note: below DDP protocol bytes are offset by 1
155+
pkt[1] = 0x40; // flags: 0x40 = no push, 0x41 = push (i.e. render), note: this is DDP protocol byte 0
156+
pkt[2] = 0x00; // reserved
157+
pkt[3] = 0x01; // 1 = RGB (currently only supported mode)
158+
pkt[4] = 0x01; // destination id (not used but 0x01 is default output)
159+
pkt[5] = (off >> 24) & 255; // DDP protocol 4-7 is offset
160+
pkt[6] = (off >> 16) & 255;
161+
pkt[7] = (off >> 8) & 255;
162+
pkt[8] = off & 255;
163+
pkt[9] = (dLen >> 8) & 255; // DDP protocol 8-9 is data length
164+
pkt[10] = dLen & 255;
165+
pkt.set(colors.subarray(cOff, cOff + dLen), 11);
166+
if(i + cnt >= len) {
167+
pkt[1] = 0x41; //if this is last packet, set the "push" flag to render the frame
168+
}
169+
try {
170+
ws.send(pkt.buffer);
171+
} catch (e) {
172+
console.error(e);
173+
return false;
174+
}
175+
}
176+
return true;
177+
}

wled00/data/liveview.htm

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
position: absolute;
1818
}
1919
</style>
20+
<script src="common.js"></script>
2021
<script>
21-
var d = document;
2222
var ws;
2323
var tmout = null;
2424
var c;
@@ -62,32 +62,14 @@
6262
if (window.location.href.indexOf("?ws") == -1) {update(); return;}
6363

6464
// Initialize WebSocket connection
65-
try {
66-
ws = top.window.ws;
67-
} catch (e) {}
68-
if (ws && ws.readyState === WebSocket.OPEN) {
69-
//console.info("Peek uses top WS");
70-
ws.send("{'lv':true}");
71-
} else {
72-
//console.info("Peek WS opening");
73-
let l = window.location;
74-
let pathn = l.pathname;
75-
let paths = pathn.slice(1,pathn.endsWith('/')?-1:undefined).split("/");
76-
let url = l.origin.replace("http","ws");
77-
if (paths.length > 1) {
78-
url += "/" + paths[0];
79-
}
80-
ws = new WebSocket(url+"/ws");
81-
ws.onopen = function () {
82-
//console.info("Peek WS open");
83-
ws.send("{'lv':true}");
84-
}
85-
}
86-
ws.binaryType = "arraybuffer";
65+
ws = connectWs(function () {
66+
//console.info("Peek WS open");
67+
ws.send('{"lv":true}');
68+
});
8769
ws.addEventListener('message', (e) => {
8870
try {
8971
if (toString.call(e.data) === '[object ArrayBuffer]') {
90-
let leds = new Uint8Array(event.data);
72+
let leds = new Uint8Array(e.data);
9173
if (leds[0] != 76) return; //'L'
9274
// leds[1] = 1: 1D; leds[1] = 2: 1D/2D (leds[2]=w, leds[3]=h)
9375
draw(leds[1]==2 ? 4 : 2, 3, leds, (a,i) => `rgb(${a[i]},${a[i+1]},${a[i+2]})`);
@@ -102,4 +84,4 @@
10284
<body onload="S()">
10385
<canvas id="canv"></canvas>
10486
</body>
105-
</html>
87+
</html>

0 commit comments

Comments
 (0)