Christian Schwinne 2024-03-19 22:03:55 +01:00
commit 0481eb38ab
35 zmienionych plików z 1729 dodań i 334 usunięć

Wyświetl plik

@ -1,5 +1,23 @@
## WLED changelog
#### Build 2403191
- Add Image effect (GIF playback from FS support)
#### Build 2403190
- limit max PWM frequency (fix incorrect PWM resolution)
- Segment UI bugfix
- Updated AsyncWebServer (by @wlillmmiles)
- Simpler boot preset (fix for #3806)
- Effect: Fix for 2D Drift animation (#3816 by @BaptisteHudyma)
- Effect: Add twin option to 2D Drift
- MQTT cleanup
- DDP: Support sources that don't push (#3833 by @willmmiles)
- Usermod: Tetris AI usermod (#3711 by @muebau)
#### Build 2403171
- merge 0.14.2 changes into 0.15
#### Build 2403070
- Add additional segment options when controlling over e1.31 (#3616 by @demophoon)
- LockedJsonResponse: Release early if possible (#3760 by @willmmiles)
@ -120,6 +138,26 @@
- send UDP/WS on segment change
- pop_back() when removing last segment
#### Build 2403170
- WLED 0.14.2 release
#### Build 2403110
- Beta WLED 0.14.2-b2
- New AsyncWebServer (improved performance and reduced memory use)
- New builds for ESP8266 with 160MHz CPU clock
- Fixing stairway usermod and adding buildflags (#3758 by @lost-hope)
- Fixing a potential array bounds violation in ESPDMX
- Reduced RAM usage (moved strings and TZ data (by @willmmiles) to PROGMEM)
- LockedJsonResponse: Release early if possible (by @willmmiles)
#### Build 2402120
- Beta WLED 0.14.2-b1
- Possible fix for #3589 & partial fix for #3605
- Prevent JSON buffer clear after failed lock attempt
- Multiple analog button fix for #3549
- UM Audioreactive: add two compiler options (#3732 by @wled-install)
- Fix for #3693
#### Build 2401141
- Official release of WLED 0.14.1
- Fix for #3566, #3665, #3672

Wyświetl plik

@ -2,6 +2,20 @@
Here are a few suggestions to make it easier for you to contribute!
### Describe your PR
Please add a description of your proposed code changes. It does not need to be an exhaustive essay, however a PR with no description or just a few words might not get accepted, simply because very basic information is missing.
A good description helps us to review and understand your proposed changes. For example, you could say a few words about
* what you try to achieve (new feature, fixing a bug, refactoring, security enhancements, etc.)
* how your code works (short technical summary - focus on important aspects that might not be obvious when reading the code)
* testing you performed, known limitations, open ends you possibly could not solve.
* any areas where you like to get help from an experienced maintainer (yes WLED has become big 😉)
### Target branch for pull requests
Please make all PRs against the `0_15` branch.
### Code style
When in doubt, it is easiest to replicate the code style you find in the files you want to edit :)
@ -73,6 +87,6 @@ Good:
<!-- This is an HTML comment -->
```
There is no set character limit for a comment within a line,
though as a rule of thumb you should wrap your comment if it exceeds the width of your editor window.
There is no hard character limit for a comment within a line,
though as a rule of thumb consider wrapping after 120 characters.
Inline comments are OK if they describe that line only and are not exceedingly wide.

569
package-lock.json wygenerowano

Plik diff jest za duży Load Diff

Wyświetl plik

@ -10,7 +10,7 @@
# ------------------------------------------------------------------------------
# CI/release binaries
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi, esp32_wrover
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi, esp32_wrover
src_dir = ./wled00
data_dir = ./wled00/data
@ -144,7 +144,7 @@ lib_deps =
fastled/FastLED @ 3.6.0
IRremoteESP8266 @ 2.8.2
makuna/NeoPixelBus @ 2.7.5
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7
https://github.com/Aircoookie/ESPAsyncWebServer.git @ ^2.1.0
# for I2C interface
;Wire
# ESP-NOW library
@ -316,6 +316,11 @@ build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 #-DWLED
lib_deps = ${esp8266.lib_deps}
monitor_filters = esp8266_exception_decoder
[env:nodemcuv2_160]
extends = env:nodemcuv2
board_build.f_cpu = 160000000L
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D
[env:esp8266_2m]
board = esp_wroom_02
platform = ${common.platform_wled_default}
@ -325,6 +330,11 @@ build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02
lib_deps = ${esp8266.lib_deps}
[env:esp8266_2m_160]
extends = env:esp8266_2m
board_build.f_cpu = 160000000L
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02_160
[env:esp01_1m_full]
board = esp01_1m
platform = ${common.platform_wled_default}
@ -335,6 +345,12 @@ build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_D
; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM
lib_deps = ${esp8266.lib_deps}
[env:esp01_1m_full_160]
extends = env:esp01_1m_full
board_build.f_cpu = 160000000L
build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA
; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM
[env:esp32dev]
board = esp32dev
platform = ${esp32.platform}

Wyświetl plik

@ -30,14 +30,12 @@ const output = ["wled00/html_ui.h", "wled00/html_pixart.h", "wled00/html_cpal.h"
// \x1b[34m is blue, \x1b[36m is cyan, \x1b[0m is reset
const wledBanner = `
\t\x1b[34m## ## ## ######## ########
\t\x1b[34m## ## ## ## ## ## ##
\t\x1b[34m## ## ## ## ## ## ##
\t\x1b[34m## ## ## ## ###### ## ##
\t\x1b[34m## ## ## ## ## ## ##
\t\x1b[34m## ## ## ## ## ## ##
\t\x1b[34m ### ### ######## ######## ########
\t\t\x1b[36mbuild script for web UI
\t\x1b[34m ## ## ## ###### ######
\t\x1b[34m## ## ## ## ## ## ##
\t\x1b[34m## ## ## ## ###### ## ##
\t\x1b[34m## ## ## ## ## ## ##
\t\x1b[34m ## ## ###### ###### ######
\t\t\x1b[36m build script for web UI
\x1b[0m`;
const singleHeader = `/*

Wyświetl plik

@ -0,0 +1,117 @@
/******************************************************************************
* @file : gridbw.h
* @brief : contains the tetris grid as binary so black and white version
******************************************************************************
* @attention
*
* Copyright (c) muebau 2023
* All rights reserved.</center></h2>
*
******************************************************************************
*/
#ifndef __GRIDBW_H__
#define __GRIDBW_H__
#include <iterator>
#include <vector>
#include "pieces.h"
using namespace std;
class GridBW
{
private:
public:
uint8_t width;
uint8_t height;
std::vector<uint32_t> pixels;
GridBW(uint8_t width, uint8_t height):
width(width),
height(height),
pixels(height)
{
if (width > 32)
{
throw std::invalid_argument("maximal width is 32");
}
}
void placePiece(Piece* piece, uint8_t x, uint8_t y)
{
for (uint8_t row = 4 - piece->getRotation().height; row < 4; row++)
{
pixels[y + (row - (4 - piece->getRotation().height))] |= piece->getGridRow(x, row, width);
}
}
void erasePiece(Piece* piece, uint8_t x, uint8_t y)
{
for (uint8_t row = 4 - piece->getRotation().height; row < 4; row++)
{
pixels[y + (row - (4 - piece->getRotation().height))] &= ~piece->getGridRow(x, row, width);
}
}
bool noCollision(Piece* piece, uint8_t x, uint8_t y)
{
//if it touches a wall it is a collision
if (x > (this->width - piece->getRotation().width) || y > this->height - piece->getRotation().height)
{
return false;
}
for (uint8_t row = 4 - piece->getRotation().height; row < 4; row++)
{
if (piece->getGridRow(x, row, width) & pixels[y + (row - (4 - piece->getRotation().height))])
{
return false;
}
}
return true;
}
void findLandingPosition(Piece* piece)
{
// move down until the piece bumps into some occupied pixels or the 'wall'
while (noCollision(piece, piece->x, piece->landingY))
{
piece->landingY++;
}
//at this point the positon is 'in the wall' or 'over some occupied pixel'
//so the previous position was the last correct one (clamped to 0 as minimum).
piece->landingY = piece->landingY > 0 ? piece->landingY - 1 : 0;
}
void cleanupFullLines()
{
uint8_t offset = 0;
//from "height - 1" to "0", so from bottom row to top
for (uint8_t row = height; row-- > 0; )
{
//full line?
if (isLineFull(row))
{
offset++;
pixels[row] = 0x0;
continue;
}
if (offset > 0)
{
pixels[row + offset] = pixels[row];
pixels[row] = 0x0;
}
}
}
bool isLineFull(uint8_t y)
{
return pixels[y] == (uint32_t)((1 << width) - 1);
}
};
#endif /* __GRIDBW_H__ */

Wyświetl plik

@ -0,0 +1,132 @@
/******************************************************************************
* @file : gridcolor.h
* @brief : contains the tetris grid as 8bit indexed color version
******************************************************************************
* @attention
*
* Copyright (c) muebau 2023
* All rights reserved.</center></h2>
*
******************************************************************************
*/
#ifndef __GRIDCOLOR_H__
#define __GRIDCOLOR_H__
#include <stdint.h>
#include <stdbool.h>
#include <vector>
#include "gridbw.h"
#include "gridcolor.h"
using namespace std;
class GridColor
{
private:
public:
uint8_t width;
uint8_t height;
GridBW gridBW;
std::vector<uint8_t> pixels;
GridColor(uint8_t width, uint8_t height):
width(width),
height(height),
gridBW(width, height),
pixels(width* height)
{}
void clear()
{
for (uint8_t y = 0; y < height; y++)
{
gridBW.pixels[y] = 0x0;
for (int8_t x = 0; x < width; x++)
{
*getPixel(x, y) = 0;
}
}
}
void placePiece(Piece* piece, uint8_t x, uint8_t y)
{
for (uint8_t pieceY = 0; pieceY < piece->getRotation().height; pieceY++)
{
for (uint8_t pieceX = 0; pieceX < piece->getRotation().width; pieceX++)
{
if (piece->getPixel(pieceX, pieceY))
{
*getPixel(x + pieceX, y + pieceY) = piece->pieceData->colorIndex;
}
}
}
}
void erasePiece(Piece* piece, uint8_t x, uint8_t y)
{
for (uint8_t pieceY = 0; pieceY < piece->getRotation().height; pieceY++)
{
for (uint8_t pieceX = 0; pieceX < piece->getRotation().width; pieceX++)
{
if (piece->getPixel(pieceX, pieceY))
{
*getPixel(x + pieceX, y + pieceY) = 0;
}
}
}
}
void cleanupFullLines()
{
uint8_t offset = 0;
//from "height - 1" to "0", so from bottom row to top
for (uint8_t y = height; y-- > 0; )
{
if (gridBW.isLineFull(y))
{
offset++;
for (uint8_t x = 0; x < width; x++)
{
pixels[y * width + x] = 0;
}
continue;
}
if (offset > 0)
{
if (gridBW.pixels[y])
{
for (uint8_t x = 0; x < width; x++)
{
pixels[(y + offset) * width + x] = pixels[y * width + x];
pixels[y * width + x] = 0;
}
}
}
}
gridBW.cleanupFullLines();
}
uint8_t* getPixel(uint8_t x, uint8_t y)
{
return &pixels[y * width + x];
}
void sync()
{
for (uint8_t y = 0; y < height; y++)
{
gridBW.pixels[y] = 0x0;
for (int8_t x = 0; x < width; x++)
{
gridBW.pixels[y] <<= 1;
if (*getPixel(x, y) != 0)
{
gridBW.pixels[y] |= 0x1;
}
}
}
}
};
#endif /* __GRIDCOLOR_H__ */

Wyświetl plik

@ -0,0 +1,184 @@
/******************************************************************************
* @file : pieces.h
* @brief : contains the tetris pieces with their colors indecies
******************************************************************************
* @attention
*
* Copyright (c) muebau 2022
* All rights reserved.</center></h2>
*
******************************************************************************
*/
#ifndef __PIECES_H__
#define __PIECES_H__
#include <stdint.h>
#include <stdbool.h>
#include <bitset>
#include <cstddef>
#include <cassert>
#include <iostream>
#define numPieces 7
struct PieceRotation
{
uint8_t width;
uint8_t height;
uint16_t rows;
};
struct PieceData
{
uint8_t rotCount;
uint8_t colorIndex;
PieceRotation rotations[4];
};
PieceData piecesData[numPieces] = {
// I
{
2,
1,
{
{ 1, 4, 0b0001000100010001},
{ 4, 1, 0b0000000000001111}
}
},
// O
{
1,
2,
{
{ 2, 2, 0b0000000000110011}
}
},
// Z
{
2,
3,
{
{ 3, 2, 0b0000000001100011},
{ 2, 3, 0b0000000100110010}
}
},
// S
{
2,
4,
{
{ 3, 2, 0b0000000000110110},
{ 2, 3, 0b0000001000110001}
}
},
// L
{
4,
5,
{
{ 2, 3, 0b0000001000100011},
{ 3, 2, 0b0000000001110100},
{ 2, 3, 0b0000001100010001},
{ 3, 2, 0b0000000000010111}
}
},
// J
{
4,
6,
{
{ 2, 3, 0b0000000100010011},
{ 3, 2, 0b0000000001000111},
{ 2, 3, 0b0000001100100010},
{ 3, 2, 0b0000000001110001}
}
},
// T
{
4,
7,
{
{ 3, 2, 0b0000000001110010},
{ 2, 3, 0b0000000100110001},
{ 3, 2, 0b0000000000100111},
{ 2, 3, 0b0000001000110010}
}
},
};
class Piece
{
private:
public:
uint8_t x;
uint8_t y;
PieceData* pieceData;
uint8_t rotation;
uint8_t landingY;
Piece(uint8_t pieceIndex = 0):
x(0),
y(0),
rotation(0),
landingY(0)
{
this->pieceData = &piecesData[pieceIndex];
}
void reset()
{
this->rotation = 0;
this->x = 0;
this->y = 0;
this->landingY = 0;
}
uint32_t getGridRow(uint8_t x, uint8_t y, uint8_t width)
{
if (x < width)
{
//shift the row with the "top-left" position to the "x" position
auto shiftx = (width - 1) - x;
auto topleftx = (getRotation().width - 1);
auto finalShift = shiftx - topleftx;
auto row = getRow(y);
auto finalResult = row << finalShift;
return finalResult;
}
return 0xffffffff;
}
uint8_t getRow(uint8_t y)
{
if (y < 4)
{
return (getRotation().rows >> (12 - (4 * y))) & 0xf;
}
return 0xf;
}
bool getPixel(uint8_t x, uint8_t y)
{
if(x > getRotation().width - 1 || y > getRotation().height - 1 )
{
return false;
}
if (x < 4 && y < 4)
{
return (getRow((4 - getRotation().height) + y) >> (3 - ((4 - getRotation().width) + x))) & 0x1;
}
return false;
}
PieceRotation getRotation()
{
return this->pieceData->rotations[rotation];
}
};
#endif /* __PIECES_H__ */

Wyświetl plik

@ -0,0 +1,64 @@
/******************************************************************************
* @file : rating.h
* @brief : contains the tetris rating of a grid
******************************************************************************
* @attention
*
* Copyright (c) muebau 2022
* All rights reserved.</center></h2>
*
******************************************************************************
*/
#ifndef __RATING_H__
#define __RATING_H__
#include <stdint.h>
#include <float.h>
#include <stdbool.h>
#include <math.h>
#include <vector>
#include "rating.h"
using namespace std;
class Rating
{
private:
public:
uint8_t minHeight;
uint8_t maxHeight;
uint16_t holes;
uint8_t fullLines;
uint16_t bumpiness;
uint16_t aggregatedHeight;
double score;
uint8_t width;
std::vector<uint8_t> lineHights;
Rating(uint8_t width):
width(width),
lineHights(width)
{
reset();
}
void reset()
{
this->minHeight = 0;
this->maxHeight = 0;
for (uint8_t line = 0; line < this->width; line++)
{
this->lineHights[line] = 0;
}
this->holes = 0;
this->fullLines = 0;
this->bumpiness = 0;
this->aggregatedHeight = 0;
this->score = -DBL_MAX;
}
};
#endif /* __RATING_H__ */

Wyświetl plik

@ -0,0 +1,33 @@
# Tetris AI effect usermod
This usermod brings you a effect brings a self playing Tetris game. The mod needs version 0.14 or above as it is based on matrix support. The effect was tested on an ESP32 with a WS2812B 16x16 matrix.
Version 1.0
## Installation
Just activate the usermod with `-D USERMOD_TETRISAI` and the effect will become available under the name 'Tetris AI'.
## Usage
It is best to set the background color to black, the border color to light grey and the game over color (foreground) to dark grey.
### Sliders and boxes
#### Sliders
* speed: speed the game plays
* look ahead: how many pieces is the AI allowed to know the next pieces (0 - 2)
* intelligence: how good the AI will play
* Rotate color: make the colors shift (rotate) every few cicles
* Mistakes free: how many good moves between mistakes (if activated)
#### Checkboxes
* show next: if true a space of 5 pixels from the right is used to show the next pieces. The whole segment is used for the grid otherwise.
* show border: if true an additional column of 1 pixel is used to draw a border between the grid and the next pieces
* mistakes: if true the worst instead of the best move is choosen every few moves (read above)
## Best results
If the speed is set to be a little bit faster than a good human could play with maximal intelligence and very few mistakes it makes people furious/happy at a party.

Wyświetl plik

@ -0,0 +1,302 @@
/******************************************************************************
* @file : ai.h
* @brief : contains the heuristic
******************************************************************************
* @attention
*
* Copyright (c) muebau 2023
* All rights reserved.</center></h2>
*
******************************************************************************
*/
#ifndef __AI_H__
#define __AI_H__
#include "gridbw.h"
#include "rating.h"
using namespace std;
class TetrisAI
{
private:
public:
double aHeight;
double fullLines;
double holes;
double bumpiness;
bool findWorstMove = false;
uint8_t countOnes(uint32_t vector)
{
uint8_t count = 0;
while (vector)
{
vector &= (vector - 1);
count++;
}
return count;
}
void updateRating(GridBW grid, Rating* rating)
{
rating->minHeight = 0;
rating->maxHeight = 0;
rating->holes = 0;
rating->fullLines = 0;
rating->bumpiness = 0;
rating->aggregatedHeight = 0;
fill(rating->lineHights.begin(), rating->lineHights.end(), 0);
uint32_t columnvector = 0x0;
uint32_t lastcolumnvector = 0x0;
for (uint8_t row = 0; row < grid.height; row++)
{
columnvector |= grid.pixels[row];
//first (highest) column makes it
if (rating->maxHeight == 0 && columnvector)
{
rating->maxHeight = grid.height - row;
}
//if column vector is full we found the minimal height (or it stays zero)
if (rating->minHeight == 0 && (columnvector == (uint32_t)((1 << grid.width) - 1)))
{
rating->minHeight = grid.height - row;
}
//line full if all ones in mask :-)
if (grid.isLineFull(row))
{
rating->fullLines++;
}
//holes are basically a XOR with the "full" columns
rating->holes += countOnes(columnvector ^ grid.pixels[row]);
//calculate the difference (XOR) between the current column vector and the last one
uint32_t columnDelta = columnvector ^ lastcolumnvector;
//process every new column
uint8_t index = 0;
while (columnDelta)
{
//if this is a new column
if (columnDelta & 0x1)
{
//update hight of this column
rating->lineHights[(grid.width - 1) - index] = grid.height - row;
// update aggregatedHeight
rating->aggregatedHeight += grid.height - row;
}
index++;
columnDelta >>= 1;
}
lastcolumnvector = columnvector;
}
//compare every two columns to get the difference and add them up
for (uint8_t column = 1; column < grid.width; column++)
{
rating->bumpiness += abs(rating->lineHights[column - 1] - rating->lineHights[column]);
}
rating->score = (aHeight * (rating->aggregatedHeight)) + (fullLines * (rating->fullLines)) + (holes * (rating->holes)) + (bumpiness * (rating->bumpiness));
}
TetrisAI(): TetrisAI(-0.510066, 0.760666, -0.35663, -0.184483)
{}
TetrisAI(double aHeight, double fullLines, double holes, double bumpiness):
aHeight(aHeight),
fullLines(fullLines),
holes(holes),
bumpiness(bumpiness)
{}
void findBestMove(GridBW grid, Piece *piece)
{
vector<Piece> pieces = {*piece};
findBestMove(grid, &pieces);
*piece = pieces[0];
}
void findBestMove(GridBW grid, std::vector<Piece> *pieces)
{
findBestMove(grid, pieces->begin(), pieces->end());
}
void findBestMove(GridBW grid, std::vector<Piece>::iterator start, std::vector<Piece>::iterator end)
{
Rating bestRating(grid.width);
findBestMove(grid, start, end, &bestRating);
}
void findBestMove(GridBW grid, std::vector<Piece>::iterator start, std::vector<Piece>::iterator end, Rating* bestRating)
{
grid.cleanupFullLines();
Rating curRating(grid.width);
Rating deeperRating(grid.width);
Piece piece = *start;
// for every rotation of the piece
for (piece.rotation = 0; piece.rotation < piece.pieceData->rotCount; piece.rotation++)
{
// put piece to top left corner
piece.x = 0;
piece.y = 0;
//test for every column
for (piece.x = 0; piece.x <= grid.width - piece.getRotation().width; piece.x++)
{
//todo optimise by the use of the previous grids height
piece.landingY = 0;
//will set landingY to final position
grid.findLandingPosition(&piece);
// draw piece
grid.placePiece(&piece, piece.x, piece.landingY);
if(start == end - 1)
{
//at the deepest level
updateRating(grid, &curRating);
}
else
{
//go deeper to take another piece into account
findBestMove(grid, start + 1, end, &deeperRating);
curRating = deeperRating;
}
// eraese piece
grid.erasePiece(&piece, piece.x, piece.landingY);
if(findWorstMove)
{
//init rating for worst
if(bestRating->score == -DBL_MAX)
{
bestRating->score = DBL_MAX;
}
// update if we found a worse one
if (bestRating->score > curRating.score)
{
*bestRating = curRating;
(*start) = piece;
}
}
else
{
// update if we found a better one
if (bestRating->score < curRating.score)
{
*bestRating = curRating;
(*start) = piece;
}
}
}
}
}
bool findBestMoveNonBlocking(GridBW grid, std::vector<Piece>::iterator start, std::vector<Piece>::iterator end, Rating* bestRating)
{
//vector with pieces
//for every piece
//for every
switch (expression)
{
case INIT:
break;
default:
break;
}
}
bool findBestMoveNonBlocking(GridBW grid, std::vector<Piece>::iterator start, std::vector<Piece>::iterator end, Rating* bestRating)
{
//INIT
grid.cleanupFullLines();
Rating curRating(grid.width);
Rating deeperRating(grid.width);
Piece piece = *start;
// for every rotation of the piece
piece.rotation = 0;
//HANDLE
while (piece.rotation < piece.pieceData->rotCount)
{
// put piece to top left corner
piece.x = 0;
piece.y = 0;
//test for every column
piece.x = 0;
while (piece.x <= grid.width - piece.getRotation().width)
{
//todo optimise by the use of the previous grids height
piece.landingY = 0;
//will set landingY to final position
grid.findLandingPosition(&piece);
// draw piece
grid.placePiece(&piece, piece.x, piece.landingY);
if(start == end - 1)
{
//at the deepest level
updateRating(grid, &curRating);
}
else
{
//go deeper to take another piece into account
findBestMove(grid, start + 1, end, &deeperRating);
curRating = deeperRating;
}
// eraese piece
grid.erasePiece(&piece, piece.x, piece.landingY);
if(findWorstMove)
{
//init rating for worst
if(bestRating->score == -DBL_MAX)
{
bestRating->score = DBL_MAX;
}
// update if we found a worse one
if (bestRating->score > curRating.score)
{
*bestRating = curRating;
(*start) = piece;
}
}
else
{
// update if we found a better one
if (bestRating->score < curRating.score)
{
*bestRating = curRating;
(*start) = piece;
}
}
piece.x++;
}
piece.rotation++;
}
//EXIT
return true;
}
};
#endif /* __AI_H__ */

Wyświetl plik

@ -0,0 +1,150 @@
/******************************************************************************
* @file : tetrisaigame.h
* @brief : main tetris functions
******************************************************************************
* @attention
*
* Copyright (c) muebau 2022
* All rights reserved.</center></h2>
*
******************************************************************************
*/
#ifndef __TETRISAIGAME_H__
#define __TETRISAIGAME_H__
#include <stdint.h>
#include <stdbool.h>
#include <vector>
#include "pieces.h"
#include "gridcolor.h"
#include "tetrisbag.h"
#include "tetrisai.h"
using namespace std;
class TetrisAIGame
{
private:
bool animateFallOfPiece(Piece* piece, bool skip)
{
if (skip || piece->y >= piece->landingY)
{
piece->y = piece->landingY;
grid.gridBW.placePiece(piece, piece->x, piece->landingY);
grid.placePiece(piece, piece->x, piece->y);
return false;
}
else
{
// eraese last drawing
grid.erasePiece(piece, piece->x, piece->y);
//move piece down
piece->y++;
// draw piece
grid.placePiece(piece, piece->x, piece->y);
return true;
}
}
public:
uint8_t width;
uint8_t height;
uint8_t nLookAhead;
TetrisBag bag;
GridColor grid;
TetrisAI ai;
Piece curPiece;
PieceData* piecesData;
enum States { INIT, TEST_GAME_OVER, GET_NEXT_PIECE, FIND_BEST_MOVE, ANIMATE_MOVE, ANIMATE_GAME_OVER } state = INIT;
TetrisAIGame(uint8_t width, uint8_t height, uint8_t nLookAhead, PieceData* piecesData, uint8_t nPieces):
width(width),
height(height),
nLookAhead(nLookAhead),
bag(nPieces, 1, nLookAhead),
grid(width, height + 4),
ai(),
piecesData(piecesData)
{
}
void nextPiece()
{
grid.cleanupFullLines();
bag.queuePiece();
}
void findBestMove()
{
ai.findBestMove(grid.gridBW, &bag.piecesQueue);
}
bool animateFall(bool skip)
{
return animateFallOfPiece(&(bag.piecesQueue[0]), skip);
}
bool isGameOver()
{
//if there is something in the 4 lines of the hidden area the game is over
return grid.gridBW.pixels[0] || grid.gridBW.pixels[1] || grid.gridBW.pixels[2] || grid.gridBW.pixels[3];
}
void poll()
{
switch (state)
{
case INIT:
reset();
state = TEST_GAME_OVER;
break;
case TEST_GAME_OVER:
if (isGameOver())
{
state = ANIMATE_GAME_OVER;
}
else
{
state = GET_NEXT_PIECE;
}
break;
case GET_NEXT_PIECE:
nextPiece();
state = FIND_BEST_MOVE;
break;
case FIND_BEST_MOVE:
findBestMove();
state = ANIMATE_MOVE;
break;
case ANIMATE_MOVE:
if (!animateFall(false))
{
state = TEST_GAME_OVER;
}
break;
case ANIMATE_GAME_OVER:
static auto curPixel = grid.pixels.size();
grid.pixels[curPixel] = 254;
if (curPixel == 0)
{
state = INIT;
curPixel = grid.pixels.size();
}
curPixel--;
break;
}
}
void reset()
{
grid.clear();
bag.init();
}
};
#endif /* __TETRISAIGAME_H__ */

Wyświetl plik

@ -0,0 +1,100 @@
/******************************************************************************
* @file : tetrisbag.h
* @brief : the tetris implementation of a random piece generator
******************************************************************************
* @attention
*
* Copyright (c) muebau 2022
* All rights reserved.</center></h2>
*
******************************************************************************
*/
#ifndef __TETRISBAG_H__
#define __TETRISBAG_H__
#include <stdint.h>
#include <vector>
#include <algorithm>
#include "tetrisbag.h"
class TetrisBag
{
private:
public:
uint8_t nPieces;
uint8_t nBagLength;
uint8_t bagIdx;
std::vector<uint8_t> bag;
std::vector<Piece> piecesQueue;
TetrisBag(uint8_t nPieces, uint8_t nBagLength, uint8_t queueLength):
nPieces(nPieces),
nBagLength(nBagLength),
bag(nPieces * nBagLength),
piecesQueue(queueLength)
{
init();
}
void init()
{
//will shuffle the bag at first use
bagIdx = nPieces - 1;
for (uint8_t bagIndex = 0; bagIndex < nPieces * nBagLength; bagIndex++)
{
bag[bagIndex] = bagIndex % nPieces;
}
//will init the queue
for (uint8_t index = 0; index < piecesQueue.size(); index++)
{
queuePiece();
}
}
void shuffleBag()
{
uint8_t temp;
uint8_t swapIdx;
for (int index = nPieces - 1; index > 0; index--)
{
//get candidate to swap
swapIdx = rand() % index;
//swap it!
temp = bag[swapIdx];
bag[swapIdx] = bag[index];
bag[index] = temp;
}
}
Piece getNextPiece()
{
bagIdx++;
if (bagIdx >= nPieces)
{
shuffleBag();
bagIdx = 0;
}
return Piece(bag[bagIdx]);
}
void queuePiece()
{
//move vector to left
std::rotate(piecesQueue.begin(), piecesQueue.begin() + 1, piecesQueue.end());
piecesQueue[piecesQueue.size() - 1] = getNextPiece();
}
void queuePiece(uint8_t idx)
{
//move vector to left
std::rotate(piecesQueue.begin(), piecesQueue.begin() + 1, piecesQueue.end());
piecesQueue[piecesQueue.size() - 1] = Piece(idx % nPieces);
}
};
#endif /* __TETRISBAG_H__ */

Wyświetl plik

@ -0,0 +1,222 @@
#pragma once
#include "wled.h"
#include "FX.h"
#include "fcn_declare.h"
#include "tetrisaigame.h"
// By: muebau
typedef struct TetrisAI_data
{
unsigned long lastTime = 0;
TetrisAIGame tetris;
uint8_t intelligence;
uint8_t rotate;
bool showNext;
bool showBorder;
uint8_t colorOffset;
uint8_t colorInc;
uint8_t mistaceCountdown;
} tetrisai_data;
void drawGrid(TetrisAIGame* tetris, TetrisAI_data* tetrisai_data)
{
SEGMENT.fill(SEGCOLOR(1));
//GRID
for (auto index_y = 4; index_y < tetris->grid.height; index_y++)
{
for (auto index_x = 0; index_x < tetris->grid.width; index_x++)
{
CRGB color;
if (*tetris->grid.getPixel(index_x, index_y) == 0)
{
//BG color
color = SEGCOLOR(1);
}
//game over animation
else if(*tetris->grid.getPixel(index_x, index_y) == 254)
{
//use fg
color = SEGCOLOR(0);
}
else
{
//spread the color over the whole palette
uint8_t colorIndex = *tetris->grid.getPixel(index_x, index_y) * 32;
colorIndex += tetrisai_data->colorOffset;
color = ColorFromPalette(SEGPALETTE, colorIndex, 255, NOBLEND);
}
SEGMENT.setPixelColorXY(index_x, index_y - 4, color);
}
}
tetrisai_data->colorOffset += tetrisai_data->colorInc;
//NEXT PIECE AREA
if (tetrisai_data->showNext)
{
//BORDER
if (tetrisai_data->showBorder)
{
//draw a line 6 pixels from right with the border color
for (auto index_y = 0; index_y < SEGMENT.virtualHeight(); index_y++)
{
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() - 6, index_y, SEGCOLOR(2));
}
}
//NEXT PIECE
int piecesOffsetX = SEGMENT.virtualWidth() - 4;
int piecesOffsetY = 1;
for (uint8_t nextPieceIdx = 1; nextPieceIdx < tetris->nLookAhead; nextPieceIdx++)
{
uint8_t pieceNbrOffsetY = (nextPieceIdx - 1) * 5;
Piece piece(tetris->bag.piecesQueue[nextPieceIdx]);
for (uint8_t pieceY = 0; pieceY < piece.getRotation().height; pieceY++)
{
for (uint8_t pieceX = 0; pieceX < piece.getRotation().width; pieceX++)
{
if (piece.getPixel(pieceX, pieceY))
{
uint8_t colIdx = ((piece.pieceData->colorIndex * 32) + tetrisai_data->colorOffset);
SEGMENT.setPixelColorXY(piecesOffsetX + pieceX, piecesOffsetY + pieceNbrOffsetY + pieceY, ColorFromPalette(SEGPALETTE, colIdx, 255, NOBLEND));
}
}
}
}
}
}
////////////////////////////
// 2D Tetris AI //
////////////////////////////
uint16_t mode_2DTetrisAI()
{
if (!strip.isMatrix || !SEGENV.allocateData(sizeof(tetrisai_data)))
{
// not a 2D set-up
SEGMENT.fill(SEGCOLOR(0));
return 350;
}
TetrisAI_data* tetrisai_data = reinterpret_cast<TetrisAI_data*>(SEGENV.data);
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
//range 0 - 1024ms => 1024/255 ~ 4
uint16_t msDelayMove = 1024 - (4 * SEGMENT.speed);
int16_t msDelayGameOver = msDelayMove / 4;
//range 0 - 2 (not including current)
uint8_t nLookAhead = SEGMENT.intensity ? (SEGMENT.intensity >> 7) + 2 : 1;
//range 0 - 16
tetrisai_data->colorInc = SEGMENT.custom2 >> 4;
if (!tetrisai_data->tetris || (tetrisai_data->tetris.nLookAhead != nLookAhead
|| tetrisai_data->showNext != SEGMENT.check1
|| tetrisai_data->showBorder != SEGMENT.check2
)
)
{
tetrisai_data->showNext = SEGMENT.check1;
tetrisai_data->showBorder = SEGMENT.check2;
//not more than 32 as this is the limit of this implementation
uint8_t gridWidth = cols < 32 ? cols : 32;
uint8_t gridHeight = rows;
// do we need space for the 'next' section?
if (tetrisai_data->showNext)
{
// make space for the piece and one pixel of space
gridWidth = gridWidth - 5;
// do we need space for a border?
if (tetrisai_data->showBorder)
{
gridWidth = gridWidth - 1;
}
}
tetrisai_data->tetris = TetrisAIGame(gridWidth, gridHeight, nLookAhead, piecesData, numPieces);
SEGMENT.fill(SEGCOLOR(1));
}
if (tetrisai_data->intelligence != SEGMENT.custom1)
{
tetrisai_data->intelligence = SEGMENT.custom1;
double dui = 0.2 - (0.2 * (tetrisai_data->intelligence / 255.0));
tetrisai_data->tetris.ai.aHeight = -0.510066 + dui;
tetrisai_data->tetris.ai.fullLines = 0.760666 - dui;
tetrisai_data->tetris.ai.holes = -0.35663 + dui;
tetrisai_data->tetris.ai.bumpiness = -0.184483 + dui;
}
if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_MOVE)
{
if (millis() - tetrisai_data->lastTime > msDelayMove)
{
drawGrid(&tetrisai_data->tetris, tetrisai_data);
tetrisai_data->lastTime = millis();
tetrisai_data->tetris.poll();
}
}
else if (tetrisai_data->tetris.state == TetrisAIGame::ANIMATE_GAME_OVER)
{
if (millis() - tetrisai_data->lastTime > msDelayGameOver)
{
drawGrid(&tetrisai_data->tetris, tetrisai_data);
tetrisai_data->lastTime = millis();
tetrisai_data->tetris.poll();
}
}
else if (tetrisai_data->tetris.state == TetrisAIGame::FIND_BEST_MOVE)
{
if (SEGMENT.check3)
{
if(tetrisai_data->mistaceCountdown == 0)
{
tetrisai_data->tetris.ai.findWorstMove = true;
tetrisai_data->tetris.poll();
tetrisai_data->tetris.ai.findWorstMove = false;
tetrisai_data->mistaceCountdown = SEGMENT.custom3;
}
tetrisai_data->mistaceCountdown--;
}
tetrisai_data->tetris.poll();
}
else
{
tetrisai_data->tetris.poll();
}
return FRAMETIME;
} // mode_2DTetrisAI()
static const char _data_FX_MODE_2DTETRISAI[] PROGMEM = "Tetris AI@!,Look ahead,Intelligence,Rotate color,Mistake free,Show next,Border,Mistakes;Game Over,!,Border;!;2;sx=127,ix=64,c1=255,c2=0,c3=31,o1=1,o2=1,o3=0,pal=11";
class TetrisAIUsermod : public Usermod
{
private:
public:
void setup()
{
strip.addEffect(255, &mode_2DTetrisAI, _data_FX_MODE_2DTETRISAI);
}
void loop()
{
}
uint16_t getId()
{
return USERMOD_ID_TETRISAI;
}
};

Wyświetl plik

@ -5029,21 +5029,25 @@ uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmateli
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
const uint16_t colsCenter = (cols>>1) + (cols%2);
const uint16_t rowsCenter = (rows>>1) + (rows%2);
SEGMENT.fadeToBlackBy(128);
const uint16_t maxDim = MAX(cols, rows)/2;
unsigned long t = strip.now / (32 - (SEGMENT.speed>>3));
unsigned long t_20 = t/20; // softhack007: pre-calculating this gives about 10% speedup
for (float i = 1; i < maxDim; i += 0.25) {
for (float i = 1.0f; i < maxDim; i += 0.25f) {
float angle = radians(t * (maxDim - i));
uint16_t myX = (cols>>1) + (uint16_t)(sin_t(angle) * i) + (cols%2);
uint16_t myY = (rows>>1) + (uint16_t)(cos_t(angle) * i) + (rows%2);
SEGMENT.setPixelColorXY(myX, myY, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND));
int16_t mySin = sin_t(angle) * i;
int16_t myCos = cos_t(angle) * i;
SEGMENT.setPixelColorXY(colsCenter + mySin, rowsCenter + myCos, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND));
if (SEGMENT.check1) SEGMENT.setPixelColorXY(colsCenter + myCos, rowsCenter + mySin, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND));
}
SEGMENT.blur(SEGMENT.intensity>>3);
return FRAMETIME;
} // mode_2DDrift()
static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur amount;;!;2";
static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur amount,,,,Twin;;!;2";
//////////////////////////
@ -6216,8 +6220,9 @@ uint16_t mode_2Ddriftrose(void) {
SEGMENT.fadeToBlackBy(32+(SEGMENT.speed>>3));
for (size_t i = 1; i < 37; i++) {
uint32_t x = (CX + (sin_t(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f;
uint32_t y = (CY + (cos_t(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f;
float angle = radians(i * 10);
uint32_t x = (CX + (sin_t(angle) * (beatsin8(i, 0, L*2)-L))) * 255.f;
uint32_t y = (CY + (cos_t(angle) * (beatsin8(i, 0, L*2)-L))) * 255.f;
SEGMENT.wu_pixel(x, y, CHSV(i * 10, 255, 255));
}
SEGMENT.blur((SEGMENT.intensity>>4)+1);

Wyświetl plik

@ -21,7 +21,6 @@ void shortPressAction(uint8_t b)
case 1: ++effectCurrent %= strip.getModeCount(); stateChanged = true; colorUpdated(CALL_MODE_BUTTON); break;
}
} else {
unloadPlaylist(); // applying a preset unloads the playlist
applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET);
}
@ -43,7 +42,6 @@ void longPressAction(uint8_t b)
case 1: bri += 8; stateUpdated(CALL_MODE_BUTTON); buttonPressedTime[b] = millis(); break; // repeatable action
}
} else {
unloadPlaylist(); // applying a preset unloads the playlist
applyPreset(macroLongPress[b], CALL_MODE_BUTTON_PRESET);
}
@ -65,7 +63,6 @@ void doublePressAction(uint8_t b)
case 1: ++effectPalette %= strip.getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break;
}
} else {
unloadPlaylist(); // applying a preset unloads the playlist
applyPreset(macroDoublePress[b], CALL_MODE_BUTTON_PRESET);
}

Wyświetl plik

@ -180,6 +180,7 @@
#define USERMOD_ID_STAIRWAY_WIPE 44 //Usermod "stairway-wipe-usermod-v2.h"
#define USERMOD_ID_ANIMARTRIX 45 //Usermod "usermod_v2_animartrix.h"
#define USERMOD_ID_HTTP_PULL_LIGHT_CONTROL 46 //usermod "usermod_v2_HttpPullLightControl.h"
#define USERMOD_ID_TETRISAI 47 //Usermod "usermod_v2_tetris.h"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot

Wyświetl plik

@ -1290,6 +1290,7 @@ TD .checkmark, TD .radiomark {
margin: 0 auto 12px;
min-height: 40px;
border: 1px solid var(--c-2);
width: 100%;
}
/* Simplify segments */

Wyświetl plik

@ -309,7 +309,7 @@
<button class="btn infobtn" id="resetbtn" onclick="cnfReset()">Reboot WLED</button>
</div>
<br>
<span class="h">Made with <span id="heart">&#10084;&#xFE0E;</span> by <a href="https://github.com/Aircoookie/" target="_blank">Aircoookie</a> and the <a href="https://wled.discourse.group/" target="_blank">WLED community</a></span>
<span class="h">Made with&#32;<span id="heart">&#10084;&#xFE0E;</span>&#32;by&#32;<a href="https://github.com/Aircoookie/" target="_blank">Aircoookie</a>&#32;and the&#32;<a href="https://wled.discourse.group/" target="_blank">WLED community</a></span>
</div>
<div id="nodes" class="modal">

Wyświetl plik

@ -618,7 +618,7 @@ function populatePresets(fromls)
cn += `<div class="pres lstI" id="p${i}o">`;
if (cfg.comp.pid) cn += `<div class="pid">${i}</div>`;
cn += `<div class="pname lstIname" onclick="setPreset(${i})">${isPlaylist(i)?"<i class='icons btn-icon'>&#xe139;</i>":""}${pName(i)}
cn += `<div class="pname lstIname" onclick="setPreset(${i})">${i==lastinfo.leds.bootps?"<i class='icons btn-icon'>&#xe410;</i>":""}${isPlaylist(i)?"<i class='icons btn-icon'>&#xe139;</i>":""}${pName(i)}
<i class="icons edit-icon flr" id="p${i}nedit" onclick="tglSegn(${i+100})">&#xe2c6;</i></div>
<i class="icons e-icon flr" id="sege${i+100}" onclick="expand(${i+100})">&#xe395;</i>
<div class="presin lstIcontent" id="seg${i+100}"></div>
@ -1959,6 +1959,7 @@ function plR(p)
function makeP(i,pl)
{
var content = "";
const bps = lastinfo.leds.bootps;
if (pl) {
if (i===0) plJson[0] = {
ps: [1],
@ -2024,6 +2025,11 @@ ${makePlSel(plJson[i].end?plJson[i].end:0, true)}
</div>
<div class="po2" id="p${i}o2">API command<br><textarea class="apitxt" id="p${i}api"></textarea></div>
<div class="po1" id="p${i}o1">${content}</div>
<label class="check revchkl">
<span class="lstIname">Apply at boot</span>
<input type="checkbox" id="p${i}bps" ${i==bps?"checked":""}>
<span class="checkmark"></span>
</label>
<div class="c m6">Save to ID <input id="p${i}id" type="number" oninput="checkUsed(${i})" max=250 min=1 value=${(i>0)?i:getLowestUnusedP()}></div>
<div class="c">
<button class="btn btn-p" onclick="saveP(${i},${pl})"><i class="icons btn-icon">&#xe390;</i>Save</button>
@ -2445,8 +2451,9 @@ function saveP(i,pl)
if (gId(`p${i}lmp`) && gId(`p${i}lmp`).value!=="") obj.ledmap = parseInt(gId(`p${i}lmp`).value);
}
}
obj.psave = pI; obj.n = pN;
if (gId(`p${i}bps`).checked) obj.bootps = pI;
obj.psave = pI;
obj.n = pN;
var pQN = gId(`p${i}ql`).value;
if (pQN.length > 0) obj.ql = pQN;

Wyświetl plik

@ -133,11 +133,11 @@
<div>Restore configuration<br><input type="file" name="data2" accept=".json"> <button type="button" onclick="uploadFile(d.Sf.data2,'/cfg.json');">Upload</button><br></div>
<hr>
<h3>About</h3>
<a href="https://github.com/Aircoookie/WLED/" target="_blank">WLED</a> version ##VERSION##<!-- Autoreplaced from package.json --><br><br>
<a href="https://github.com/Aircoookie/WLED/" target="_blank">WLED</a>&#32;version ##VERSION##<!-- Autoreplaced from package.json --><br><br>
<a href="https://github.com/Aircoookie/WLED/wiki/Contributors-and-credits" target="_blank">Contributors, dependencies and special thanks</a><br>
A huge thank you to everyone who helped me create WLED!<br><br>
(c) 2016-2023 Christian Schwinne <br>
<i>Licensed under the <a href="https://github.com/Aircoookie/WLED/blob/master/LICENSE" target="_blank">MIT license</a></i><br><br>
(c) 2016-2024 Christian Schwinne <br>
<i>Licensed under the&#32;<a href="https://github.com/Aircoookie/WLED/blob/master/LICENSE" target="_blank">MIT license</a></i><br><br>
Server message: <span class="sip"> Response error! </span><hr>
<div id="toast"></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>

Wyświetl plik

@ -11,6 +11,7 @@
//DDP protocol support, called by handleE131Packet
//handles RGB data only
void handleDDPPacket(e131_packet_t* p) {
static bool ddpSeenPush = false; // have we seen a push yet?
int lastPushSeq = e131LastSequenceNumber[0];
//reject late packets belonging to previous frame (assuming 4 packets max. before push)
@ -34,6 +35,7 @@ void handleDDPPacket(e131_packet_t* p) {
uint16_t 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
if (realtimeMode != REALTIME_MODE_DDP) ddpSeenPush = false; // just starting, no push yet
realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP);
if (!realtimeOverride || (realtimeMode && useMainSegmentOnly)) {
@ -44,7 +46,8 @@ void handleDDPPacket(e131_packet_t* p) {
}
bool push = p->flags & DDP_PUSH_FLAG;
if (push) {
ddpSeenPush |= push;
if (!ddpSeenPush || push) { // if we've never seen a push, or this is one, render display
e131NewData = true;
byte sn = p->sequenceNum & 0xF;
if (sn) e131LastSequenceNumber[0] = sn;
@ -184,7 +187,6 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
// only apply preset if not in playlist, or playlist changed
(currentPlaylist < 0 || dmxValPreset != currentPlaylist)) {
presetCycCurr = dmxValPreset;
unloadPlaylist(); // applying a preset unloads the playlist
applyPreset(dmxValPreset, CALL_MODE_NOTIFICATION);
}

Wyświetl plik

@ -5,7 +5,6 @@
* All globally accessible functions are declared here
*/
//alexa.cpp
#ifndef WLED_DISABLE_ALEXA
void onAlexaChange(EspalexaDevice* dev);

Wyświetl plik

@ -1,7 +1,9 @@
#include "wled.h"
#ifndef WLED_DISABLE_GIF
#include "GifDecoder.h"
#include "wled.h"
/*
* Functions to render images from filesystem to segments, used by the "Image" effect

Wyświetl plik

@ -442,13 +442,11 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
currentPreset = root[F("pd")] | currentPreset;
if (root["win"].isNull()) presetCycCurr = currentPreset; // otherwise it was set in handleSet() [set.cpp]
presetToRestore = currentPreset; // stateUpdated() will clear the preset, so we need to restore it after
//unloadPlaylist(); // applying a preset unloads the playlist, may be needed here too?
} else if (!root["ps"].isNull()) {
ps = presetCycCurr;
if (root["win"].isNull() && getVal(root["ps"], &ps, 0, 0) && ps > 0 && ps < 251 && ps != currentPreset) {
// b) preset ID only or preset that does not change state (use embedded cycling limits if they exist in getVal())
presetCycCurr = ps;
unloadPlaylist(); // applying a preset unloads the playlist
applyPreset(ps, callMode); // async load from file system (only preset ID was specified)
return stateResponse;
}
@ -628,6 +626,7 @@ void serializeInfo(JsonObject root)
leds[F("maxseg")] = strip.getMaxSegments();
//leds[F("actseg")] = strip.getActiveSegmentsNum();
//leds[F("seglock")] = false; //might be used in the future to prevent modifications to segment config
leds[F("bootps")] = bootPreset;
#ifndef WLED_DISABLE_2D
if (strip.isMatrix) {

Wyświetl plik

@ -172,7 +172,9 @@ void updateInterfaces(uint8_t callMode)
espalexaDevice->setColor(col[0], col[1], col[2]);
}
#endif
doPublishMqtt = true;
#ifndef WLED_DISABLE_MQTT
publishMqtt();
#endif
}
@ -180,9 +182,6 @@ void handleTransitions()
{
//handle still pending interface update
updateInterfaces(interfaceUpdateCallMode);
#ifndef WLED_DISABLE_MQTT
if (doPublishMqtt) publishMqtt();
#endif
if (transitionActive && strip.getTransition() > 0) {
float tper = (millis() - transitionStartTime)/(float)strip.getTransition();

Wyświetl plik

@ -47,8 +47,8 @@ void onMqttConnect(bool sessionPresent)
usermods.onMqttConnect(sessionPresent);
doPublishMqtt = true;
DEBUG_PRINTLN(F("MQTT ready"));
publishMqtt();
}
@ -76,7 +76,7 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties
if (index + len >= total) { // at end
payloadStr[total] = '\0'; // terminate c style string
} else {
DEBUG_PRINTLN(F("Partial packet received."));
DEBUG_PRINTLN(F("MQTT partial packet received."));
return; // process next packet
}
DEBUG_PRINTLN(payloadStr);
@ -131,13 +131,12 @@ void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProperties
void publishMqtt()
{
doPublishMqtt = false;
if (!WLED_MQTT_CONNECTED) return;
DEBUG_PRINTLN(F("Publish MQTT"));
#ifndef USERMOD_SMARTNEST
char s[10];
char subuf[38];
char subuf[48];
sprintf_P(s, PSTR("%u"), bri);
strlcpy(subuf, mqttDeviceTopic, 33);

Wyświetl plik

@ -399,7 +399,6 @@ void checkTimers()
&& isTodayInDateRange(((timerMonth[i] >> 4) & 0x0F), timerDay[i], timerMonth[i] & 0x0F, timerDayEnd[i])
)
{
unloadPlaylist();
applyPreset(timerMacro[i]);
}
}
@ -413,7 +412,6 @@ void checkTimers()
&& (timerWeekday[8] & 0x01) //timer is enabled
&& ((timerWeekday[8] >> weekdayMondayFirst()) & 0x01)) //timer should activate at current day of week
{
unloadPlaylist();
applyPreset(timerMacro[8]);
DEBUG_PRINTF_P(PSTR("Sunrise macro %d triggered."),timerMacro[8]);
}
@ -428,7 +426,6 @@ void checkTimers()
&& (timerWeekday[9] & 0x01) //timer is enabled
&& ((timerWeekday[9] >> weekdayMondayFirst()) & 0x01)) //timer should activate at current day of week
{
unloadPlaylist();
applyPreset(timerMacro[9]);
DEBUG_PRINTF_P(PSTR("Sunset macro %d triggered."),timerMacro[9]);
}

Wyświetl plik

@ -119,6 +119,7 @@ void initPresetsFile()
bool applyPreset(byte index, byte callMode)
{
unloadPlaylist(); // applying a preset unloads the playlist (#3827)
DEBUG_PRINT(F("Request to apply preset: "));
DEBUG_PRINTLN(index);
presetToApply = index;
@ -224,6 +225,13 @@ void savePreset(byte index, const char* pname, JsonObject sObj)
if (sObj[F("ql")].is<const char*>()) strlcpy(quickLoad, sObj[F("ql")].as<const char*>(), 9); // client limits QL to 2 chars, buffer for 8 bytes to allow unicode
else quickLoad[0] = 0;
const char *bootPS = PSTR("bootps");
if (!sObj[FPSTR(bootPS)].isNull()) {
bootPreset = sObj[FPSTR(bootPS)] | bootPreset;
sObj.remove(FPSTR(bootPS));
doSerializeConfig = true;
}
if (sObj.size()==0 || sObj["o"].isNull()) { // no "o" means not a playlist or custom API call, saving of state is async (not immediately)
includeBri = sObj["ib"].as<bool>() || sObj.size()==0 || index==255; // temporary preset needs brightness
segBounds = sObj["sb"].as<bool>() || sObj.size()==0 || index==255; // temporary preset needs bounds

Wyświetl plik

@ -108,7 +108,6 @@ static void setOff() {
void presetWithFallback(uint8_t presetID, uint8_t effectID, uint8_t paletteID) {
resetNightMode();
unloadPlaylist();
applyPresetWithFallback(presetID, CALL_MODE_BUTTON_PRESET, effectID, paletteID);
}

Wyświetl plik

@ -883,7 +883,6 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//apply preset
if (updateVal(req.c_str(), "PL=", &presetCycCurr, presetCycMin, presetCycMax)) {
unloadPlaylist();
applyPreset(presetCycCurr);
}

Wyświetl plik

@ -209,6 +209,10 @@
#include "../usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h"
#endif
#ifdef USERMOD_TETRISAI
#include "../usermods/TetrisAI_v2/usermod_v2_tetrisai.h"
#endif
void registerUsermods()
{
/*
@ -405,4 +409,8 @@ void registerUsermods()
#ifdef USERMOD_STAIRCASE_WIPE
usermods.add(new StairwayWipeUsermod());
#endif
#ifdef USERMOD_TETRISAI
usermods.add(new TetrisAIUsermod());
#endif
}

Wyświetl plik

@ -269,6 +269,7 @@ void WLED::loop()
maxLoopMillis = 0;
maxUsermodMillis = 0;
maxStripMillis = 0;
avgLoopMillis = 0;
avgUsermodMillis = 0;
avgStripMillis = 0;
debugTime = millis();

Wyświetl plik

@ -8,7 +8,7 @@
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2403070
#define VERSION 2403191
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG
@ -707,7 +707,6 @@ WLED_GLOBAL byte optionType;
WLED_GLOBAL bool doSerializeConfig _INIT(false); // flag to initiate saving of config
WLED_GLOBAL bool doReboot _INIT(false); // flag to initiate reboot from async handlers
WLED_GLOBAL bool doPublishMqtt _INIT(false);
// status led
#if defined(STATUSLED)

Wyświetl plik

@ -471,8 +471,10 @@ void getSettingsJS(byte subPage, char* dest)
}
sappend('c',SET_F("IP"),disablePullUp);
sappend('v',SET_F("TT"),touchThreshold);
#ifndef WLED_DISABLE_INFRARED
sappend('v',SET_F("IR"),irPin);
sappend('v',SET_F("IT"),irEnabled);
#endif
sappend('c',SET_F("MSO"),!irApplyToAllSelected);
}