Compare commits

..

9 Commits

Author SHA1 Message Date
Blaž Kristan
1da2692c34 Add segment checkmarks to differs() check 2025-11-02 18:00:48 +01:00
Will Tatam
b60313e1f8 Merge pull request #5012 from DedeHai/improved_welcompage_check
Improvement to "Welcome Page" check
2025-10-22 20:40:06 +01:00
Will Tatam
1fff61b726 Merge pull request #4995 from Brandon502/GoLRework
Game of Life Rework
2025-10-21 20:43:28 +01:00
Damian Schneider
c4850aed08 Adding DDP over WS, moving duplicate WS-connection to common.js (#4997)
- Enabling DDP over WebSocket: this allows for UI or html tools to stream data to the LEDs much faster than through the JSON API.
- first byte of data array is used to determine protocol for future use
- Moved the duplicate function to establish a WS connection from the live-view htm files to common.js
- add better safety check for DDP: prevent OOB reads of buffer
2025-10-21 19:41:57 +02:00
Damian Schneider
43eb2d6f47 check config backup as welcome page gate 2025-10-19 07:10:05 +02:00
Damian Schneider
0ec4488dd1 adding function to check if a backup exists 2025-10-19 07:08:18 +02:00
Benjam Welker
ca5debef32 Fix blank area issue with Twinkle (#5005)
* Fix blank area issue with Twinkle
2025-10-17 07:31:00 +02:00
Brandon502
cc0230f83f Game of Life Optimizations
Adjust mutation logic. Use 1D get/set. Reduce code size.
2025-10-14 22:58:22 -04:00
Brandon502
65b91762fd Game of Life Rework
RAM and speed optimizations. Better repeat detection. Mutation toggle. Blur option added.
2025-10-08 22:48:53 -04:00
22 changed files with 528 additions and 445 deletions

View File

@@ -313,11 +313,11 @@ class MyExampleUsermod : public Usermod {
yield();
// ignore certain button types as they may have other consequences
if (!enabled
|| buttons[b].type == BTN_TYPE_NONE
|| buttons[b].type == BTN_TYPE_RESERVED
|| buttons[b].type == BTN_TYPE_PIR_SENSOR
|| buttons[b].type == BTN_TYPE_ANALOG
|| buttons[b].type == BTN_TYPE_ANALOG_INVERTED) {
|| buttonType[b] == BTN_TYPE_NONE
|| buttonType[b] == BTN_TYPE_RESERVED
|| buttonType[b] == BTN_TYPE_PIR_SENSOR
|| buttonType[b] == BTN_TYPE_ANALOG
|| buttonType[b] == BTN_TYPE_ANALOG_INVERTED) {
return false;
}

View File

@@ -1530,7 +1530,7 @@ class AudioReactive : public Usermod {
// better would be for AudioSource to implement getType()
if (enabled
&& dmType == 0 && audioPin>=0
&& (buttons[b].type == BTN_TYPE_ANALOG || buttons[b].type == BTN_TYPE_ANALOG_INVERTED)
&& (buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED)
) {
return true;
}

View File

@@ -562,11 +562,11 @@ void MultiRelay::loop() {
bool MultiRelay::handleButton(uint8_t b) {
yield();
if (!enabled
|| buttons[b].type == BTN_TYPE_NONE
|| buttons[b].type == BTN_TYPE_RESERVED
|| buttons[b].type == BTN_TYPE_PIR_SENSOR
|| buttons[b].type == BTN_TYPE_ANALOG
|| buttons[b].type == BTN_TYPE_ANALOG_INVERTED) {
|| buttonType[b] == BTN_TYPE_NONE
|| buttonType[b] == BTN_TYPE_RESERVED
|| buttonType[b] == BTN_TYPE_PIR_SENSOR
|| buttonType[b] == BTN_TYPE_ANALOG
|| buttonType[b] == BTN_TYPE_ANALOG_INVERTED) {
return false;
}
@@ -581,20 +581,20 @@ bool MultiRelay::handleButton(uint8_t b) {
unsigned long now = millis();
//button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0)
if (buttons[b].type == BTN_TYPE_SWITCH) {
if (buttonType[b] == BTN_TYPE_SWITCH) {
//handleSwitch(b);
if (buttons[b].pressedBefore != isButtonPressed(b)) {
buttons[b].pressedTime = now;
buttons[b].pressedBefore = !buttons[b].pressedBefore;
if (buttonPressedBefore[b] != isButtonPressed(b)) {
buttonPressedTime[b] = now;
buttonPressedBefore[b] = !buttonPressedBefore[b];
}
if (buttons[b].longPressed == buttons[b].pressedBefore) return handled;
if (buttonLongPressed[b] == buttonPressedBefore[b]) return handled;
if (now - buttons[b].pressedTime > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
if (now - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].button == b) {
switchRelay(i, buttons[b].pressedBefore);
buttons[b].longPressed = buttons[b].pressedBefore; //save the last "long term" switch state
switchRelay(i, buttonPressedBefore[b]);
buttonLongPressed[b] = buttonPressedBefore[b]; //save the last "long term" switch state
}
}
}
@@ -604,40 +604,40 @@ bool MultiRelay::handleButton(uint8_t b) {
//momentary button logic
if (isButtonPressed(b)) { //pressed
if (!buttons[b].pressedBefore) buttons[b].pressedTime = now;
buttons[b].pressedBefore = true;
if (!buttonPressedBefore[b]) buttonPressedTime[b] = now;
buttonPressedBefore[b] = true;
if (now - buttons[b].pressedTime > 600) { //long press
if (now - buttonPressedTime[b] > 600) { //long press
//longPressAction(b); //not exposed
//handled = false; //use if you want to pass to default behaviour
buttons[b].longPressed = true;
buttonLongPressed[b] = true;
}
} else if (!isButtonPressed(b) && buttons[b].pressedBefore) { //released
} else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released
long dur = now - buttons[b].pressedTime;
long dur = now - buttonPressedTime[b];
if (dur < WLED_DEBOUNCE_THRESHOLD) {
buttons[b].pressedBefore = false;
buttonPressedBefore[b] = false;
return handled;
} //too short "press", debounce
bool doublePress = buttons[b].waitTime; //did we have short press before?
buttons[b].waitTime = 0;
bool doublePress = buttonWaitTime[b]; //did we have short press before?
buttonWaitTime[b] = 0;
if (!buttons[b].longPressed) { //short press
if (!buttonLongPressed[b]) { //short press
// if this is second release within 350ms it is a double press (buttonWaitTime!=0)
if (doublePress) {
//doublePressAction(b); //not exposed
//handled = false; //use if you want to pass to default behaviour
} else {
buttons[b].waitTime = now;
buttonWaitTime[b] = now;
}
}
buttons[b].pressedBefore = false;
buttons[b].longPressed = false;
buttonPressedBefore[b] = false;
buttonLongPressed[b] = false;
}
// if 350ms elapsed since last press/release it is a short press
if (buttons[b].waitTime && now - buttons[b].waitTime > 350 && !buttons[b].pressedBefore) {
buttons[b].waitTime = 0;
if (buttonWaitTime[b] && now - buttonWaitTime[b] > 350 && !buttonPressedBefore[b]) {
buttonWaitTime[b] = 0;
//shortPressAction(b); //not exposed
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
if (_relay[i].button == b) {

View File

@@ -461,11 +461,11 @@ class PixelsDiceTrayUsermod : public Usermod {
#if USING_TFT_DISPLAY
bool handleButton(uint8_t b) override {
if (!enabled || b > 1 // buttons 0,1 only
|| buttons[b].type == BTN_TYPE_SWITCH || buttons[b].type == BTN_TYPE_NONE ||
buttons[b].type == BTN_TYPE_RESERVED ||
buttons[b].type == BTN_TYPE_PIR_SENSOR ||
buttons[b].type == BTN_TYPE_ANALOG ||
buttons[b].type == BTN_TYPE_ANALOG_INVERTED) {
|| buttonType[b] == BTN_TYPE_SWITCH || buttonType[b] == BTN_TYPE_NONE ||
buttonType[b] == BTN_TYPE_RESERVED ||
buttonType[b] == BTN_TYPE_PIR_SENSOR ||
buttonType[b] == BTN_TYPE_ANALOG ||
buttonType[b] == BTN_TYPE_ANALOG_INVERTED) {
return false;
}
@@ -476,43 +476,43 @@ class PixelsDiceTrayUsermod : public Usermod {
static unsigned long buttonWaitTime[2] = {0};
//momentary button logic
if (!buttons[b].longPressed && isButtonPressed(b)) { //pressed
if (!buttons[b].pressedBefore) {
buttons[b].pressedTime = now;
if (!buttonLongPressed[b] && isButtonPressed(b)) { //pressed
if (!buttonPressedBefore[b]) {
buttonPressedTime[b] = now;
}
buttons[b].pressedBefore = true;
buttonPressedBefore[b] = true;
if (now - buttons[b].pressedTime > WLED_LONG_PRESS) { //long press
if (now - buttonPressedTime[b] > WLED_LONG_PRESS) { //long press
menu_ctrl.HandleButton(ButtonType::LONG, b);
buttons[b].longPressed = true;
buttonLongPressed[b] = true;
return true;
}
} else if (!isButtonPressed(b) && buttons[b].pressedBefore) { //released
} else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released
long dur = now - buttons[b].pressedTime;
long dur = now - buttonPressedTime[b];
if (dur < WLED_DEBOUNCE_THRESHOLD) {
buttons[b].pressedBefore = false;
buttonPressedBefore[b] = false;
return true;
} //too short "press", debounce
bool doublePress = buttons[b].waitTime; //did we have short press before?
buttons[b].waitTime = 0;
bool doublePress = buttonWaitTime[b]; //did we have short press before?
buttonWaitTime[b] = 0;
if (!buttons[b].longPressed) { //short press
if (!buttonLongPressed[b]) { //short press
// if this is second release within 350ms it is a double press (buttonWaitTime!=0)
if (doublePress) {
menu_ctrl.HandleButton(ButtonType::DOUBLE, b);
} else {
buttons[b].waitTime = now;
buttonWaitTime[b] = now;
}
}
buttons[b].pressedBefore = false;
buttons[b].longPressed = false;
buttonPressedBefore[b] = false;
buttonLongPressed[b] = false;
}
// if 350ms elapsed since last press/release it is a short press
if (buttons[b].waitTime && now - buttons[b].waitTime > WLED_DOUBLE_PRESS &&
!buttons[b].pressedBefore) {
buttons[b].waitTime = 0;
if (buttonWaitTime[b] && now - buttonWaitTime[b] > WLED_DOUBLE_PRESS &&
!buttonPressedBefore[b]) {
buttonWaitTime[b] = 0;
menu_ctrl.HandleButton(ButtonType::SINGLE, b);
}

View File

@@ -749,12 +749,12 @@ bool FourLineDisplayUsermod::handleButton(uint8_t b) {
yield();
if (!enabled
|| b // button 0 only
|| buttons[b].type == BTN_TYPE_SWITCH
|| buttons[b].type == BTN_TYPE_NONE
|| buttons[b].type == BTN_TYPE_RESERVED
|| buttons[b].type == BTN_TYPE_PIR_SENSOR
|| buttons[b].type == BTN_TYPE_ANALOG
|| buttons[b].type == BTN_TYPE_ANALOG_INVERTED) {
|| buttonType[b] == BTN_TYPE_SWITCH
|| buttonType[b] == BTN_TYPE_NONE
|| buttonType[b] == BTN_TYPE_RESERVED
|| buttonType[b] == BTN_TYPE_PIR_SENSOR
|| buttonType[b] == BTN_TYPE_ANALOG
|| buttonType[b] == BTN_TYPE_ANALOG_INVERTED) {
return false;
}

View File

@@ -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";
/////////////////////////

View File

@@ -17,13 +17,13 @@ static bool buttonBriDirection = false; // true: increase brightness, false: dec
void shortPressAction(uint8_t b)
{
if (!buttons[b].macroButton) {
if (!macroButton[b]) {
switch (b) {
case 0: toggleOnOff(); stateUpdated(CALL_MODE_BUTTON); break;
case 1: ++effectCurrent %= strip.getModeCount(); stateChanged = true; colorUpdated(CALL_MODE_BUTTON); break;
}
} else {
applyPreset(buttons[b].macroButton, CALL_MODE_BUTTON_PRESET);
applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET);
}
#ifndef WLED_DISABLE_MQTT
@@ -38,7 +38,7 @@ void shortPressAction(uint8_t b)
void longPressAction(uint8_t b)
{
if (!buttons[b].macroLongPress) {
if (!macroLongPress[b]) {
switch (b) {
case 0: setRandomColor(colPri); colorUpdated(CALL_MODE_BUTTON); break;
case 1:
@@ -52,11 +52,11 @@ void longPressAction(uint8_t b)
else bri -= WLED_LONG_BRI_STEPS;
}
stateUpdated(CALL_MODE_BUTTON);
buttons[b].pressedTime = millis();
buttonPressedTime[b] = millis();
break; // repeatable action
}
} else {
applyPreset(buttons[b].macroLongPress, CALL_MODE_BUTTON_PRESET);
applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET);
}
#ifndef WLED_DISABLE_MQTT
@@ -71,13 +71,13 @@ void longPressAction(uint8_t b)
void doublePressAction(uint8_t b)
{
if (!buttons[b].macroDoublePress) {
if (!macroDoublePress[b]) {
switch (b) {
//case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break; //instant short press on button 0 if no macro set
case 1: ++effectPalette %= getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break;
}
} else {
applyPreset(buttons[b].macroDoublePress, CALL_MODE_BUTTON_PRESET);
applyPreset(macroDoublePress[b], CALL_MODE_BUTTON_PRESET);
}
#ifndef WLED_DISABLE_MQTT
@@ -92,10 +92,10 @@ void doublePressAction(uint8_t b)
bool isButtonPressed(uint8_t b)
{
if (buttons[b].pin < 0) return false;
unsigned pin = buttons[b].pin;
if (btnPin[b]<0) return false;
unsigned pin = btnPin[b];
switch (buttons[b].type) {
switch (buttonType[b]) {
case BTN_TYPE_NONE:
case BTN_TYPE_RESERVED:
break;
@@ -113,7 +113,7 @@ bool isButtonPressed(uint8_t b)
#ifdef SOC_TOUCH_VERSION_2 //ESP32 S2 and S3 provide a function to check touch state (state is updated in interrupt)
if (touchInterruptGetLastStatus(pin)) return true;
#else
if (digitalPinToTouchChannel(pin) >= 0 && touchRead(pin) <= touchThreshold) return true;
if (digitalPinToTouchChannel(btnPin[b]) >= 0 && touchRead(pin) <= touchThreshold) return true;
#endif
#endif
break;
@@ -124,25 +124,25 @@ bool isButtonPressed(uint8_t b)
void handleSwitch(uint8_t b)
{
// isButtonPressed() handles inverted/noninverted logic
if (buttons[b].pressedBefore != isButtonPressed(b)) {
if (buttonPressedBefore[b] != isButtonPressed(b)) {
DEBUG_PRINTF_P(PSTR("Switch: State changed %u\n"), b);
buttons[b].pressedTime = millis();
buttons[b].pressedBefore = !buttons[b].pressedBefore; // toggle pressed state
buttonPressedTime[b] = millis();
buttonPressedBefore[b] = !buttonPressedBefore[b];
}
if (buttons[b].longPressed == buttons[b].pressedBefore) return;
if (buttonLongPressed[b] == buttonPressedBefore[b]) return;
if (millis() - buttons[b].pressedTime > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
if (millis() - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
DEBUG_PRINTF_P(PSTR("Switch: Activating %u\n"), b);
if (!buttons[b].pressedBefore) { // on -> off
if (!buttonPressedBefore[b]) { // on -> off
DEBUG_PRINTF_P(PSTR("Switch: On -> Off (%u)\n"), b);
if (buttons[b].macroButton) applyPreset(buttons[b].macroButton, CALL_MODE_BUTTON_PRESET);
if (macroButton[b]) applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET);
else { //turn on
if (!bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);}
}
} else { // off -> on
DEBUG_PRINTF_P(PSTR("Switch: Off -> On (%u)\n"), b);
if (buttons[b].macroLongPress) applyPreset(buttons[b].macroLongPress, CALL_MODE_BUTTON_PRESET);
if (macroLongPress[b]) applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET);
else { //turn off
if (bri) {toggleOnOff(); stateUpdated(CALL_MODE_BUTTON);}
}
@@ -152,13 +152,13 @@ void handleSwitch(uint8_t b)
// publish MQTT message
if (buttonPublishMqtt && WLED_MQTT_CONNECTED) {
char subuf[MQTT_MAX_TOPIC_LEN + 32];
if (buttons[b].type == BTN_TYPE_PIR_SENSOR) sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)b);
if (buttonType[b] == BTN_TYPE_PIR_SENSOR) sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)b);
else sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b);
mqtt->publish(subuf, 0, false, !buttons[b].pressedBefore ? "off" : "on");
mqtt->publish(subuf, 0, false, !buttonPressedBefore[b] ? "off" : "on");
}
#endif
buttons[b].longPressed = buttons[b].pressedBefore; //save the last "long term" switch state
buttonLongPressed[b] = buttonPressedBefore[b]; //save the last "long term" switch state
}
}
@@ -178,17 +178,17 @@ void handleAnalog(uint8_t b)
#ifdef ESP8266
rawReading = analogRead(A0) << 2; // convert 10bit read to 12bit
#else
if ((buttons[b].pin < 0) /*|| (digitalPinToAnalogChannel(buttons[b].pin) < 0)*/) return; // pin must support analog ADC - newer esp32 frameworks throw lots of warnings otherwise
rawReading = analogRead(buttons[b].pin); // collect at full 12bit resolution
if ((btnPin[b] < 0) /*|| (digitalPinToAnalogChannel(btnPin[b]) < 0)*/) return; // pin must support analog ADC - newer esp32 frameworks throw lots of warnings otherwise
rawReading = analogRead(btnPin[b]); // collect at full 12bit resolution
#endif
yield(); // keep WiFi task running - analog read may take several millis on ESP8266
filteredReading[b] += POT_SMOOTHING * ((float(rawReading) / 16.0f) - filteredReading[b]); // filter raw input, and scale to [0..255]
unsigned aRead = max(min(int(filteredReading[b]), 255), 0); // squash into 8bit
if (aRead <= POT_SENSITIVITY) aRead = 0; // make sure that 0 and 255 are used
if (aRead >= 255-POT_SENSITIVITY) aRead = 255;
if(aRead <= POT_SENSITIVITY) aRead = 0; // make sure that 0 and 255 are used
if(aRead >= 255-POT_SENSITIVITY) aRead = 255;
if (buttons[b].type == BTN_TYPE_ANALOG_INVERTED) aRead = 255 - aRead;
if (buttonType[b] == BTN_TYPE_ANALOG_INVERTED) aRead = 255 - aRead;
// remove noise & reduce frequency of UI updates
if (abs(int(aRead) - int(oldRead[b])) <= POT_SENSITIVITY) return; // no significant change in reading
@@ -206,10 +206,10 @@ void handleAnalog(uint8_t b)
oldRead[b] = aRead;
// if no macro for "short press" and "long press" is defined use brightness control
if (!buttons[b].macroButton && !buttons[b].macroLongPress) {
DEBUG_PRINTF_P(PSTR("Analog: Action = %u\n"), buttons[b].macroDoublePress);
if (!macroButton[b] && !macroLongPress[b]) {
DEBUG_PRINTF_P(PSTR("Analog: Action = %u\n"), macroDoublePress[b]);
// if "double press" macro defines which option to change
if (buttons[b].macroDoublePress >= 250) {
if (macroDoublePress[b] >= 250) {
// global brightness
if (aRead == 0) {
briLast = bri;
@@ -218,30 +218,27 @@ void handleAnalog(uint8_t b)
if (bri == 0) strip.restartRuntime();
bri = aRead;
}
} else if (buttons[b].macroDoublePress == 249) {
} else if (macroDoublePress[b] == 249) {
// effect speed
effectSpeed = aRead;
} else if (buttons[b].macroDoublePress == 248) {
} else if (macroDoublePress[b] == 248) {
// effect intensity
effectIntensity = aRead;
} else if (buttons[b].macroDoublePress == 247) {
} else if (macroDoublePress[b] == 247) {
// selected palette
effectPalette = map(aRead, 0, 252, 0, getPaletteCount()-1);
effectPalette = constrain(effectPalette, 0, getPaletteCount()-1); // map is allowed to "overshoot", so we need to contrain the result
} else if (buttons[b].macroDoublePress == 200) {
} else if (macroDoublePress[b] == 200) {
// primary color, hue, full saturation
colorHStoRGB(aRead*256, 255, colPri);
colorHStoRGB(aRead*256,255,colPri);
} else {
// otherwise use "double press" for segment selection
Segment& seg = strip.getSegment(buttons[b].macroDoublePress);
Segment& seg = strip.getSegment(macroDoublePress[b]);
if (aRead == 0) {
seg.on = false; // do not use transition
//seg.setOption(SEG_OPTION_ON, false); // off (use transition)
seg.setOption(SEG_OPTION_ON, false); // off (use transition)
} else {
seg.opacity = aRead; // set brightness (opacity) of segment
seg.on = true;
//seg.setOpacity(aRead);
//seg.setOption(SEG_OPTION_ON, true); // on (use transition)
seg.setOpacity(aRead);
seg.setOption(SEG_OPTION_ON, true); // on (use transition)
}
// this will notify clients of update (websockets,mqtt,etc)
updateInterfaces(CALL_MODE_BUTTON);
@@ -264,16 +261,16 @@ void handleButton()
if (strip.isUpdating() && (now - lastRun < ANALOG_BTN_READ_CYCLE+1)) return; // don't interfere with strip update (unless strip is updating continuously, e.g. very long strips)
lastRun = now;
for (unsigned b = 0; b < buttons.size(); b++) {
for (unsigned b=0; b<WLED_MAX_BUTTONS; b++) {
#ifdef ESP8266
if ((buttons[b].pin < 0 && !(buttons[b].type == BTN_TYPE_ANALOG || buttons[b].type == BTN_TYPE_ANALOG_INVERTED)) || buttons[b].type == BTN_TYPE_NONE) continue;
if ((btnPin[b]<0 && !(buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED)) || buttonType[b] == BTN_TYPE_NONE) continue;
#else
if (buttons[b].pin < 0 || buttons[b].type == BTN_TYPE_NONE) continue;
if (btnPin[b]<0 || buttonType[b] == BTN_TYPE_NONE) continue;
#endif
if (UsermodManager::handleButton(b)) continue; // did usermod handle buttons
if (buttons[b].type == BTN_TYPE_ANALOG || buttons[b].type == BTN_TYPE_ANALOG_INVERTED) { // button is not a button but a potentiometer
if (buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) { // button is not a button but a potentiometer
if (now - lastAnalogRead > ANALOG_BTN_READ_CYCLE) {
handleAnalog(b);
}
@@ -281,7 +278,7 @@ void handleButton()
}
// button is not momentary, but switch. This is only suitable on pins whose on-boot state does not matter (NOT gpio0)
if (buttons[b].type == BTN_TYPE_SWITCH || buttons[b].type == BTN_TYPE_TOUCH_SWITCH || buttons[b].type == BTN_TYPE_PIR_SENSOR) {
if (buttonType[b] == BTN_TYPE_SWITCH || buttonType[b] == BTN_TYPE_TOUCH_SWITCH || buttonType[b] == BTN_TYPE_PIR_SENSOR) {
handleSwitch(b);
continue;
}
@@ -290,39 +287,40 @@ void handleButton()
if (isButtonPressed(b)) { // pressed
// if all macros are the same, fire action immediately on rising edge
if (buttons[b].macroButton && buttons[b].macroButton == buttons[b].macroLongPress && buttons[b].macroButton == buttons[b].macroDoublePress) {
if (!buttons[b].pressedBefore) shortPressAction(b);
buttons[b].pressedBefore = true;
buttons[b].pressedTime = now; // continually update (for debouncing to work in release handler)
if (macroButton[b] && macroButton[b] == macroLongPress[b] && macroButton[b] == macroDoublePress[b]) {
if (!buttonPressedBefore[b])
shortPressAction(b);
buttonPressedBefore[b] = true;
buttonPressedTime[b] = now; // continually update (for debouncing to work in release handler)
continue;
}
if (!buttons[b].pressedBefore) buttons[b].pressedTime = now;
buttons[b].pressedBefore = true;
if (!buttonPressedBefore[b]) buttonPressedTime[b] = now;
buttonPressedBefore[b] = true;
if (now - buttons[b].pressedTime > WLED_LONG_PRESS) { //long press
if (!buttons[b].longPressed) {
if (now - buttonPressedTime[b] > WLED_LONG_PRESS) { //long press
if (!buttonLongPressed[b]) {
buttonBriDirection = !buttonBriDirection; //toggle brightness direction on long press
longPressAction(b);
} else if (b) { //repeatable action (~5 times per s) on button > 0
longPressAction(b);
buttons[b].pressedTime = now - WLED_LONG_REPEATED_ACTION; //200ms
buttonPressedTime[b] = now - WLED_LONG_REPEATED_ACTION; //200ms
}
buttons[b].longPressed = true;
buttonLongPressed[b] = true;
}
} else if (buttons[b].pressedBefore) { //released
long dur = now - buttons[b].pressedTime;
} else if (buttonPressedBefore[b]) { //released
long dur = now - buttonPressedTime[b];
// released after rising-edge short press action
if (buttons[b].macroButton && buttons[b].macroButton == buttons[b].macroLongPress && buttons[b].macroButton == buttons[b].macroDoublePress) {
if (dur > WLED_DEBOUNCE_THRESHOLD) buttons[b].pressedBefore = false; // debounce, blocks button for 50 ms once it has been released
if (macroButton[b] && macroButton[b] == macroLongPress[b] && macroButton[b] == macroDoublePress[b]) {
if (dur > WLED_DEBOUNCE_THRESHOLD) buttonPressedBefore[b] = false; // debounce, blocks button for 50 ms once it has been released
continue;
}
if (dur < WLED_DEBOUNCE_THRESHOLD) {buttons[b].pressedBefore = false; continue;} // too short "press", debounce
bool doublePress = buttons[b].waitTime; //did we have a short press before?
buttons[b].waitTime = 0;
if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore[b] = false; continue;} // too short "press", debounce
bool doublePress = buttonWaitTime[b]; //did we have a short press before?
buttonWaitTime[b] = 0;
if (b == 0 && dur > WLED_LONG_AP) { // long press on button 0 (when released)
if (dur > WLED_LONG_FACTORY_RESET) { // factory reset if pressed > 10 seconds
@@ -334,25 +332,25 @@ void handleButton()
} else {
WLED::instance().initAP(true);
}
} else if (!buttons[b].longPressed) { //short press
} else if (!buttonLongPressed[b]) { //short press
//NOTE: this interferes with double click handling in usermods so usermod needs to implement full button handling
if (b != 1 && !buttons[b].macroDoublePress) { //don't wait for double press on buttons without a default action if no double press macro set
if (b != 1 && !macroDoublePress[b]) { //don't wait for double press on buttons without a default action if no double press macro set
shortPressAction(b);
} else { //double press if less than 350 ms between current press and previous short press release (buttonWaitTime!=0)
if (doublePress) {
doublePressAction(b);
} else {
buttons[b].waitTime = now;
buttonWaitTime[b] = now;
}
}
}
buttons[b].pressedBefore = false;
buttons[b].longPressed = false;
buttonPressedBefore[b] = false;
buttonLongPressed[b] = false;
}
//if 350ms elapsed since last short press release it is a short press
if (buttons[b].waitTime && now - buttons[b].waitTime > WLED_DOUBLE_PRESS && !buttons[b].pressedBefore) {
buttons[b].waitTime = 0;
if (buttonWaitTime[b] && now - buttonWaitTime[b] > WLED_DOUBLE_PRESS && !buttonPressedBefore[b]) {
buttonWaitTime[b] = 0;
shortPressAction(b);
}
}

View File

@@ -345,91 +345,97 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonArray hw_btn_ins = btn_obj["ins"];
if (!hw_btn_ins.isNull()) {
// deallocate existing button pins
for (const auto &button : buttons) PinManager::deallocatePin(button.pin, PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button
buttons.clear(); // clear existing buttons
for (unsigned b = 0; b < WLED_MAX_BUTTONS; b++) PinManager::deallocatePin(btnPin[b], PinOwner::Button); // does nothing if trying to deallocate a pin with PinOwner != Button
unsigned s = 0;
for (JsonObject btn : hw_btn_ins) {
uint8_t type = btn["type"] | BTN_TYPE_NONE;
int8_t pin = btn["pin"][0] | -1;
CJSON(buttonType[s], btn["type"]);
int8_t pin = btn["pin"][0] | -1;
if (pin > -1 && PinManager::allocatePin(pin, false, PinOwner::Button)) {
#ifdef ARDUINO_ARCH_ESP32
btnPin[s] = pin;
#ifdef ARDUINO_ARCH_ESP32
// ESP32 only: check that analog button pin is a valid ADC gpio
if ((type == BTN_TYPE_ANALOG) || (type == BTN_TYPE_ANALOG_INVERTED)) {
if (digitalPinToAnalogChannel(pin) < 0) {
if ((buttonType[s] == BTN_TYPE_ANALOG) || (buttonType[s] == BTN_TYPE_ANALOG_INVERTED)) {
if (digitalPinToAnalogChannel(btnPin[s]) < 0) {
// not an ADC analog pin
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), pin, s);
PinManager::deallocatePin(pin, PinOwner::Button);
pin = -1;
continue;
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), btnPin[s], s);
btnPin[s] = -1;
PinManager::deallocatePin(pin,PinOwner::Button);
} else {
analogReadResolution(12); // see #4040
}
} else if ((type == BTN_TYPE_TOUCH || type == BTN_TYPE_TOUCH_SWITCH)) {
if (digitalPinToTouchChannel(pin) < 0) {
}
else if ((buttonType[s] == BTN_TYPE_TOUCH || buttonType[s] == BTN_TYPE_TOUCH_SWITCH))
{
if (digitalPinToTouchChannel(btnPin[s]) < 0) {
// not a touch pin
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not a touch pin!\n"), pin, s);
PinManager::deallocatePin(pin, PinOwner::Button);
pin = -1;
continue;
}
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not a touch pin!\n"), btnPin[s], s);
btnPin[s] = -1;
PinManager::deallocatePin(pin,PinOwner::Button);
}
//if touch pin, enable the touch interrupt on ESP32 S2 & S3
#ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state but need to attach an interrupt to do so
else touchAttachInterrupt(pin, touchButtonISR, touchThreshold << 4); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000)
else
{
touchAttachInterrupt(btnPin[s], touchButtonISR, touchThreshold << 4); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000)
}
#endif
} else
#endif
}
else
#endif
{
// regular buttons and switches
if (disablePullUp) {
pinMode(pin, INPUT);
pinMode(btnPin[s], INPUT);
} else {
#ifdef ESP32
pinMode(pin, type==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
pinMode(btnPin[s], buttonType[s]==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
#else
pinMode(pin, INPUT_PULLUP);
pinMode(btnPin[s], INPUT_PULLUP);
#endif
}
}
JsonArray hw_btn_ins_0_macros = btn["macros"];
uint8_t press = hw_btn_ins_0_macros[0] | 0;
uint8_t longPress = hw_btn_ins_0_macros[1] | 0;
uint8_t doublePress = hw_btn_ins_0_macros[2] | 0;
buttons.emplace_back(pin, type, press, longPress, doublePress); // add button to vector
} else {
btnPin[s] = -1;
}
JsonArray hw_btn_ins_0_macros = btn["macros"];
CJSON(macroButton[s], hw_btn_ins_0_macros[0]);
CJSON(macroLongPress[s],hw_btn_ins_0_macros[1]);
CJSON(macroDoublePress[s], hw_btn_ins_0_macros[2]);
if (++s >= WLED_MAX_BUTTONS) break; // max buttons reached
}
// clear remaining buttons
for (; s<WLED_MAX_BUTTONS; s++) {
btnPin[s] = -1;
buttonType[s] = BTN_TYPE_NONE;
macroButton[s] = 0;
macroLongPress[s] = 0;
macroDoublePress[s] = 0;
}
} else if (fromFS) {
// new install/missing configuration (button 0 has defaults)
// relies upon only being called once with fromFS == true, which is currently true.
constexpr uint8_t defTypes[] = {BTNTYPE};
constexpr int8_t defPins[] = {BTNPIN};
constexpr unsigned numTypes = (sizeof(defTypes) / sizeof(defTypes[0]));
constexpr unsigned numPins = (sizeof(defPins) / sizeof(defPins[0]));
// check if the number of pins and types are valid; count of pins must be greater than or equal to types
static_assert(numTypes <= numPins, "The default button pins defined in BTNPIN do not match the button types defined in BTNTYPE");
uint8_t type = BTN_TYPE_NONE;
buttons.clear(); // clear existing buttons (just in case)
for (size_t s = 0; s < WLED_MAX_BUTTONS && s < numPins; s++) {
type = defTypes[s < numTypes ? s : numTypes - 1]; // use last known type to set current type if types less than pins
if (type == BTN_TYPE_NONE || defPins[s] < 0 || !PinManager::allocatePin(defPins[s], false, PinOwner::Button)) {
if (buttons.size() == 0) buttons.emplace_back(-1, BTN_TYPE_NONE); // add disabled button to vector (so we have at least one button defined)
continue; // pin not available or invalid, skip configuring this GPIO
for (size_t s = 0; s < WLED_MAX_BUTTONS; s++) {
if (buttonType[s] == BTN_TYPE_NONE || btnPin[s] < 0 || !PinManager::allocatePin(btnPin[s], false, PinOwner::Button)) {
btnPin[s] = -1;
buttonType[s] = BTN_TYPE_NONE;
}
if (disablePullUp) {
pinMode(defPins[s], INPUT);
} else {
#ifdef ESP32
pinMode(defPins[s], type==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
#else
pinMode(defPins[s], INPUT_PULLUP);
#endif
if (btnPin[s] >= 0) {
if (disablePullUp) {
pinMode(btnPin[s], INPUT);
} else {
#ifdef ESP32
pinMode(btnPin[s], buttonType[s]==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
#else
pinMode(btnPin[s], INPUT_PULLUP);
#endif
}
}
buttons.emplace_back(defPins[s], type); // add button to vector
macroButton[s] = 0;
macroLongPress[s] = 0;
macroDoublePress[s] = 0;
}
}
CJSON(buttonPublishMqtt, btn_obj["mqtt"]);
CJSON(buttonPublishMqtt,btn_obj["mqtt"]);
#ifndef WLED_DISABLE_INFRARED
int hw_ir_pin = hw["ir"]["pin"] | -2; // 4
@@ -771,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() {
@@ -1006,15 +1016,15 @@ void serializeConfig(JsonObject root) {
JsonArray hw_btn_ins = hw_btn.createNestedArray("ins");
// configuration for all buttons
for (const auto &button : buttons) {
for (int i = 0; i < WLED_MAX_BUTTONS; i++) {
JsonObject hw_btn_ins_0 = hw_btn_ins.createNestedObject();
hw_btn_ins_0["type"] = button.type;
hw_btn_ins_0["type"] = buttonType[i];
JsonArray hw_btn_ins_0_pin = hw_btn_ins_0.createNestedArray("pin");
hw_btn_ins_0_pin.add(button.pin);
hw_btn_ins_0_pin.add(btnPin[i]);
JsonArray hw_btn_ins_0_macros = hw_btn_ins_0.createNestedArray("macros");
hw_btn_ins_0_macros.add(button.macroButton);
hw_btn_ins_0_macros.add(button.macroLongPress);
hw_btn_ins_0_macros.add(button.macroDoublePress);
hw_btn_ins_0_macros.add(macroButton[i]);
hw_btn_ins_0_macros.add(macroLongPress[i]);
hw_btn_ins_0_macros.add(macroDoublePress[i]);
}
hw_btn[F("tt")] = touchThreshold;

View File

@@ -102,9 +102,9 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#ifndef WLED_MAX_BUTTONS
#ifdef ESP8266
#define WLED_MAX_BUTTONS 10
#define WLED_MAX_BUTTONS 2
#else
#define WLED_MAX_BUTTONS 32
#define WLED_MAX_BUTTONS 4
#endif
#else
#if WLED_MAX_BUTTONS < 2

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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

View File

@@ -6,7 +6,7 @@
<title>LED Settings</title>
<script src="common.js" type="text/javascript"></script>
<script>
var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5,maxBT=4; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var customStarts=false,startsDirty=[];
function off(n) { gN(n).value = -1;}
// these functions correspond to C macros found in const.h
@@ -43,7 +43,7 @@
}); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/leds');
}
function bLimits(b,v,p,m,l,o=5,d=2,a=6,n=4) {
function bLimits(b,v,p,m,l,o=5,d=2,a=6) {
maxB = b; // maxB - max physical (analog + digital) buses: 32 - ESP32, 14 - S3/S2, 6 - C3, 4 - 8266
maxV = v; // maxV - min virtual buses: 6 - ESP32/S3, 4 - S2/C3, 3 - ESP8266 (only used to distinguish S2/S3)
maxPB = p; // maxPB - max LEDs per bus
@@ -52,7 +52,6 @@
maxCO = o; // maxCO - max Color Order mappings
maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S): 16 - ESP32, 12 - S3/S2, 2 - C3, 3 - 8266
maxA = a; // maxA - max analog channels: 16 - ESP32, 8 - S3/S2, 6 - C3, 5 - 8266
maxBT = n; // maxBT - max buttons
}
function is8266() { return maxA == 5 && maxD == 3; } // NOTE: see const.h
function is32() { return maxA == 16 && maxD == 16; } // NOTE: see const.h
@@ -601,9 +600,9 @@ Swap: <select id="xw${s}" name="XW${s}">
}
function addBtn(i,p,t) {
var b = gId("btns");
var c = gId("btns").innerHTML;
var s = chrID(i);
var c = `<div id="btn${i}">#${i} GPIO: <input type="number" name="BT${s}" onchange="UI()" min="-1" max="${d.max_gpio}" class="xs" value="${p}">`;
c += `Button ${i} GPIO: <input type="number" name="BT${s}" onchange="UI()" class="xs" value="${p}">`;
c += `&nbsp;<select name="BE${s}">`
c += `<option value="0" ${t==0?"selected":""}>Disabled</option>`;
c += `<option value="2" ${t==2?"selected":""}>Pushbutton</option>`;
@@ -615,24 +614,8 @@ Swap: <select id="xw${s}" name="XW${s}">
c += `<option value="8" ${t==8?"selected":""}>Analog inverted</option>`;
c += `<option value="9" ${t==9?"selected":""}>Touch (switch)</option>`;
c += `</select>`;
c += `<span style="cursor: pointer;" onclick="off('BT${s}')">&nbsp;&#x2715;</span><br></div>`;
b.insertAdjacentHTML("beforeend", c);
btnBtn();
pinDropdowns();
UI();
}
function remBtn() {
var b = gId("btns");
if (b.children.length <= 1) return;
b.lastElementChild.remove();
btnBtn();
pinDropdowns();
UI();
}
function btnBtn() {
var b = gId("btns");
gId("btn_rem").style.display = (b.children.length > 1) ? "inline" : "none";
gId("btn_add").style.display = (b.children.length < maxBT) ? "inline" : "none";
c += `<span style="cursor: pointer;" onclick="off('BT${s}')">&nbsp;&#x2715;</span><br>`;
gId("btns").innerHTML = c;
}
function tglSi(cs) {
customStarts = cs;
@@ -884,16 +867,10 @@ 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="btn_wrap">
Buttons:
<div id="btns"></div>
<hr class="sml">
<button type="button" id="btn_add" onclick="addBtn(gId('btns').children.length,-1,0)">+</button>
<button type="button" id="btn_rem" onclick="remBtn()">-</button>
</div>
<div id="btns"></div>
Disable internal pull-up/down: <input type="checkbox" name="IP"><br>
Touch threshold: <input type="number" class="s" min="0" max="100" name="TT" required><br>
<hr class="sml">

View File

@@ -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);

View File

@@ -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();

View File

@@ -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");

View File

@@ -51,6 +51,9 @@ namespace {
if (a.custom1 != b.custom1) d |= SEG_DIFFERS_FX;
if (a.custom2 != b.custom2) d |= SEG_DIFFERS_FX;
if (a.custom3 != b.custom3) d |= SEG_DIFFERS_FX;
if (a.check1 != b.check1) d |= SEG_DIFFERS_FX;
if (a.check2 != b.check2) d |= SEG_DIFFERS_FX;
if (a.check3 != b.check3) d |= SEG_DIFFERS_FX;
if (a.startY != b.startY) d |= SEG_DIFFERS_BOUNDS;
if (a.stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS;

View File

@@ -128,12 +128,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
PinManager::deallocatePin(irPin, PinOwner::IR);
}
#endif
for (const auto &button : buttons) {
if (button.pin >= 0 && PinManager::isPinAllocated(button.pin, PinOwner::Button)) {
PinManager::deallocatePin(button.pin, PinOwner::Button);
for (unsigned s=0; s<WLED_MAX_BUTTONS; s++) {
if (btnPin[s]>=0 && PinManager::isPinAllocated(btnPin[s], PinOwner::Button)) {
PinManager::deallocatePin(btnPin[s], PinOwner::Button);
#ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a function to check touch state, detach interrupt
if (digitalPinToTouchChannel(button.pin) >= 0) // if touch capable pin
touchDetachInterrupt(button.pin); // if not assigned previously, this will do nothing
if (digitalPinToTouchChannel(btnPin[s]) >= 0) // if touch capable pin
touchDetachInterrupt(btnPin[s]); // if not assigned previously, this will do nothing
#endif
}
}
@@ -280,56 +280,54 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
char bt[4] = "BT"; bt[2] = offset+i; bt[3] = 0; // button pin (use A,B,C,... if WLED_MAX_BUTTONS>10)
char be[4] = "BE"; be[2] = offset+i; be[3] = 0; // button type (use A,B,C,... if WLED_MAX_BUTTONS>10)
int hw_btn_pin = request->arg(bt).toInt();
if (i >= buttons.size()) buttons.emplace_back(hw_btn_pin, request->arg(be).toInt()); // add button to vector
else {
buttons[i].pin = hw_btn_pin;
buttons[i].type = request->arg(be).toInt();
}
if (buttons[i].pin >= 0 && PinManager::allocatePin(buttons[i].pin, false, PinOwner::Button)) {
#ifdef ARDUINO_ARCH_ESP32
if (hw_btn_pin >= 0 && PinManager::allocatePin(hw_btn_pin,false,PinOwner::Button)) {
btnPin[i] = hw_btn_pin;
buttonType[i] = request->arg(be).toInt();
#ifdef ARDUINO_ARCH_ESP32
// ESP32 only: check that button pin is a valid gpio
if ((buttons[i].type == BTN_TYPE_ANALOG) || (buttons[i].type == BTN_TYPE_ANALOG_INVERTED)) {
if (digitalPinToAnalogChannel(buttons[i].pin) < 0) {
if ((buttonType[i] == BTN_TYPE_ANALOG) || (buttonType[i] == BTN_TYPE_ANALOG_INVERTED))
{
if (digitalPinToAnalogChannel(btnPin[i]) < 0) {
// not an ADC analog pin
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), buttons[i].pin, i);
PinManager::deallocatePin(buttons[i].pin, PinOwner::Button);
buttons[i].type = BTN_TYPE_NONE;
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for analog button #%d is not an analog pin!\n"), btnPin[i], i);
btnPin[i] = -1;
PinManager::deallocatePin(hw_btn_pin,PinOwner::Button);
} else {
analogReadResolution(12); // see #4040
}
} else if ((buttons[i].type == BTN_TYPE_TOUCH || buttons[i].type == BTN_TYPE_TOUCH_SWITCH)) {
if (digitalPinToTouchChannel(buttons[i].pin) < 0) {
}
else if ((buttonType[i] == BTN_TYPE_TOUCH || buttonType[i] == BTN_TYPE_TOUCH_SWITCH))
{
if (digitalPinToTouchChannel(btnPin[i]) < 0)
{
// not a touch pin
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not an touch pin!\n"), buttons[i].pin, i);
PinManager::deallocatePin(buttons[i].pin, PinOwner::Button);
buttons[i].type = BTN_TYPE_NONE;
DEBUG_PRINTF_P(PSTR("PIN ALLOC error: GPIO%d for touch button #%d is not an touch pin!\n"), btnPin[i], i);
btnPin[i] = -1;
PinManager::deallocatePin(hw_btn_pin,PinOwner::Button);
}
#ifdef SOC_TOUCH_VERSION_2 // ESP32 S2 and S3 have a fucntion to check touch state but need to attach an interrupt to do so
else touchAttachInterrupt(buttons[i].pin, touchButtonISR, touchThreshold << 4); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000)
#endif
} else
#endif
else
{
touchAttachInterrupt(btnPin[i], touchButtonISR, touchThreshold << 4); // threshold on Touch V2 is much higher (1500 is a value given by Espressif example, I measured changes of over 5000)
}
#endif
}
else
#endif
{
// regular buttons and switches
if (disablePullUp) {
pinMode(buttons[i].pin, INPUT);
pinMode(btnPin[i], INPUT);
} else {
#ifdef ESP32
pinMode(buttons[i].pin, buttons[i].type==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
pinMode(btnPin[i], buttonType[i]==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP);
#else
pinMode(buttons[i].pin, INPUT_PULLUP);
pinMode(btnPin[i], INPUT_PULLUP);
#endif
}
}
} else {
buttons[i].pin = -1;
buttons[i].type = BTN_TYPE_NONE;
}
}
// we should remove all unused buttons from the vector
for (int i = buttons.size()-1; i > 0; i--) {
if (buttons[i].pin < 0 && buttons[i].type == BTN_TYPE_NONE) {
buttons.erase(buttons.begin() + i); // remove button from vector
btnPin[i] = -1;
buttonType[i] = BTN_TYPE_NONE;
}
}
@@ -533,16 +531,14 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
macroAlexaOff = request->arg(F("A1")).toInt();
macroCountdown = request->arg(F("MC")).toInt();
macroNl = request->arg(F("MN")).toInt();
int i = 0;
for (auto &button : buttons) {
char mp[4] = "MP"; mp[2] = (i<10?'0':'A'-10)+i; mp[3] = 0; // short
char ml[4] = "ML"; ml[2] = (i<10?'0':'A'-10)+i; ml[3] = 0; // long
char md[4] = "MD"; md[2] = (i<10?'0':'A'-10)+i; md[3] = 0; // double
for (unsigned i=0; i<WLED_MAX_BUTTONS; i++) {
char mp[4] = "MP"; mp[2] = (i<10?48:55)+i; mp[3] = 0; // short
char ml[4] = "ML"; ml[2] = (i<10?48:55)+i; ml[3] = 0; // long
char md[4] = "MD"; md[2] = (i<10?48:55)+i; md[3] = 0; // double
//if (!request->hasArg(mp)) break;
button.macroButton = request->arg(mp).toInt(); // these will default to 0 if not present
button.macroLongPress = request->arg(ml).toInt();
button.macroDoublePress = request->arg(md).toInt();
i++;
macroButton[i] = request->arg(mp).toInt(); // these will default to 0 if not present
macroLongPress[i] = request->arg(ml).toInt();
macroDoublePress[i] = request->arg(md).toInt();
}
char k[3]; k[2] = 0;

View File

@@ -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);

View File

@@ -296,10 +296,10 @@ WLED_GLOBAL char otaPass[33] _INIT(DEFAULT_OTA_PASS);
// Hardware and pin config
#ifndef BTNPIN
#define BTNPIN 0
#define BTNPIN 0,-1
#endif
#ifndef BTNTYPE
#define BTNTYPE BTN_TYPE_PUSH
#define BTNTYPE BTN_TYPE_PUSH,BTN_TYPE_NONE
#endif
#ifndef RLYPIN
WLED_GLOBAL int8_t rlyPin _INIT(-1);
@@ -581,6 +581,9 @@ WLED_GLOBAL byte countdownMin _INIT(0) , countdownSec _INIT(0);
WLED_GLOBAL byte macroNl _INIT(0); // after nightlight delay over
WLED_GLOBAL byte macroCountdown _INIT(0);
WLED_GLOBAL byte macroAlexaOn _INIT(0), macroAlexaOff _INIT(0);
WLED_GLOBAL byte macroButton[WLED_MAX_BUTTONS] _INIT({0});
WLED_GLOBAL byte macroLongPress[WLED_MAX_BUTTONS] _INIT({0});
WLED_GLOBAL byte macroDoublePress[WLED_MAX_BUTTONS] _INIT({0});
// Security CONFIG
#ifdef WLED_OTA_PASS
@@ -646,32 +649,13 @@ WLED_GLOBAL byte briLast _INIT(128); // brightness before
WLED_GLOBAL byte whiteLast _INIT(128); // white channel before turned off. Used for toggle function in ir.cpp
// button
struct Button {
unsigned long pressedTime; // time button was pressed
unsigned long waitTime; // time to wait for next button press
int8_t pin; // pin number
struct {
uint8_t type : 6; // button type (push, long, double, etc.)
bool pressedBefore : 1; // button was pressed before
bool longPressed : 1; // button was long pressed
};
uint8_t macroButton; // macro/preset to call on button press
uint8_t macroLongPress; // macro/preset to call on long press
uint8_t macroDoublePress; // macro/preset to call on double press
Button(int8_t p, uint8_t t, uint8_t mB = 0, uint8_t mLP = 0, uint8_t mDP = 0)
: pressedTime(0)
, waitTime(0)
, pin(p)
, type(t)
, pressedBefore(false)
, longPressed(false)
, macroButton(mB)
, macroLongPress(mLP)
, macroDoublePress(mDP) {}
};
WLED_GLOBAL std::vector<Button> buttons; // vector of button structs
WLED_GLOBAL int8_t btnPin[WLED_MAX_BUTTONS] _INIT({BTNPIN});
WLED_GLOBAL byte buttonType[WLED_MAX_BUTTONS] _INIT({BTNTYPE});
WLED_GLOBAL bool buttonPublishMqtt _INIT(false);
WLED_GLOBAL bool buttonPressedBefore[WLED_MAX_BUTTONS] _INIT({false});
WLED_GLOBAL bool buttonLongPressed[WLED_MAX_BUTTONS] _INIT({false});
WLED_GLOBAL unsigned long buttonPressedTime[WLED_MAX_BUTTONS] _INIT({0});
WLED_GLOBAL unsigned long buttonWaitTime[WLED_MAX_BUTTONS] _INIT({0});
WLED_GLOBAL bool disablePullUp _INIT(false);
WLED_GLOBAL byte touchThreshold _INIT(TOUCH_THRESHOLD);

View File

@@ -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];

View File

@@ -291,7 +291,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
settingsScript.printf_P(PSTR("d.ledTypes=%s;"), BusManager::getLEDTypesJSONString().c_str());
// set limits
settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d,%d);"),
settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d);"),
WLED_MAX_BUSSES,
WLED_MIN_VIRTUAL_BUSSES, // irrelevant, but kept to distinguish S2/S3 in UI
MAX_LEDS_PER_BUS,
@@ -299,8 +299,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
MAX_LEDS,
WLED_MAX_COLOR_ORDER_MAPPINGS,
WLED_MAX_DIGITAL_CHANNELS,
WLED_MAX_ANALOG_CHANNELS,
WLED_MAX_BUTTONS
WLED_MAX_ANALOG_CHANNELS
);
printSetFormCheckbox(settingsScript,PSTR("MS"),strip.autoSegments);
@@ -404,9 +403,8 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,PSTR("RL"),rlyPin);
printSetFormCheckbox(settingsScript,PSTR("RM"),rlyMde);
printSetFormCheckbox(settingsScript,PSTR("RO"),rlyOpenDrain);
int i = 0;
for (const auto &button : buttons) {
settingsScript.printf_P(PSTR("addBtn(%d,%d,%d);"), i++, button.pin, button.type);
for (int i = 0; i < WLED_MAX_BUTTONS; i++) {
settingsScript.printf_P(PSTR("addBtn(%d,%d,%d);"), i, btnPin[i], buttonType[i]);
}
printSetFormCheckbox(settingsScript,PSTR("IP"),disablePullUp);
printSetFormValue(settingsScript,PSTR("TT"),touchThreshold);
@@ -580,9 +578,8 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormValue(settingsScript,PSTR("A1"),macroAlexaOff);
printSetFormValue(settingsScript,PSTR("MC"),macroCountdown);
printSetFormValue(settingsScript,PSTR("MN"),macroNl);
int i = 0;
for (const auto &button : buttons) {
settingsScript.printf_P(PSTR("addRow(%d,%d,%d,%d);"), i++, button.macroButton, button.macroLongPress, button.macroDoublePress);
for (unsigned i=0; i<WLED_MAX_BUTTONS; i++) {
settingsScript.printf_P(PSTR("addRow(%d,%d,%d,%d);"), i, macroButton[i], macroLongPress[i], macroDoublePress[i]);
}
char k[4];