mirror of
https://github.com/wled/WLED.git
synced 2026-06-21 04:11:34 +00:00
Compare commits
14 Commits
V5-C6
...
backport_5435
| Author | SHA1 | Date | |
|---|---|---|---|
| c02c5ca775 | |||
| 3a46f44495 | |||
| 360853bc28 | |||
| 99c3ea0ba9 | |||
| 56b3345f6f | |||
| 779cfa1904 | |||
| 01e81c286a | |||
| 098908d994 | |||
| 16d94e7b87 | |||
| adbd8e8ee3 | |||
| 0be9f2302d | |||
| 71ca244b46 | |||
| 37de1ce2bd | |||
| b4bb5fe4bd |
+1
-1
@@ -490,7 +490,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
|
||||
-D WLED_DISABLE_PARTICLESYSTEM1D
|
||||
-D WLED_DISABLE_PARTICLESYSTEM2D
|
||||
-D WLED_DISABLE_PIXELFORGE
|
||||
custom_usermods = audioreactive
|
||||
;; custom_usermods = audioreactive ;; pushed program flash size over the limits
|
||||
|
||||
[env:esp32dev]
|
||||
extends = esp32
|
||||
|
||||
+10
-1
@@ -810,6 +810,11 @@ class Segment {
|
||||
friend class ParticleSystem1D;
|
||||
};
|
||||
|
||||
// max wait times when waiting until !strip.isUpdating() - use with waitForLEDs(...)
|
||||
constexpr unsigned STRIP_WAIT_VERYSHORT = 25; // 25 ms - when risk of flickering is low but delays should be avoided
|
||||
constexpr unsigned STRIP_WAIT_SHORT = 50; // 50 ms - for cases where fluent animations are most important, and risk of flickering is low
|
||||
constexpr unsigned STRIP_WAIT_MEDIUM = 150; // 150 ms - good balance to avoid flickering on -C3 (good up to 4000 ws2812b LEDs per pin)
|
||||
|
||||
// main "strip" class (108 bytes)
|
||||
class WS2812FX {
|
||||
typedef void (*mode_ptr)(); // pointer to mode function
|
||||
@@ -910,6 +915,11 @@ class WS2812FX {
|
||||
{ if (_segments.size() < getMaxSegments()) _segments.emplace_back(sStart,sStop,sStartY,sStopY); }
|
||||
inline void suspend() { _suspend = true; } // will suspend (and canacel) strip.service() execution
|
||||
inline void resume() { _suspend = false; } // will resume strip.service() execution
|
||||
inline bool isSuspended() const { return _suspend; } // true if strip.service() execution is suspended
|
||||
// be nice, but not too nice - wait until LEDs are idle, or maxWaitMS have passed
|
||||
// on 8266 this call will _not_ wait outside of the main loop context
|
||||
// returns isUpdating() status after waiting
|
||||
bool waitForLEDs(unsigned maxWaitMS, bool always = false) const;
|
||||
|
||||
void restartRuntime();
|
||||
void setTransitionMode(bool t);
|
||||
@@ -923,7 +933,6 @@ class WS2812FX {
|
||||
inline bool isServicing() const { return _isServicing; } // returns true if strip.service() is executing
|
||||
inline bool hasWhiteChannel() const { return _hasWhiteChannel; } // returns true if strip contains separate white chanel
|
||||
inline bool isOffRefreshRequired() const { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset)
|
||||
inline bool isSuspended() const { return _suspend; } // returns true if strip.service() execution is suspended
|
||||
inline bool needsUpdate() const { return _triggered; } // returns true if strip received a trigger() request
|
||||
|
||||
// uint8_t paletteBlend; // obsolete - use global paletteBlend instead of strip.paletteBlend
|
||||
|
||||
@@ -1837,6 +1837,33 @@ void WS2812FX::waitForIt() {
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Be nice, but not too nice - wait until LEDs are idle, or maxWaitMS milliseconds have passed.
|
||||
* always=true enforces waiting even when using the RMTHI driver.
|
||||
* On 8266 this call will _not_ wait outside the main loop context.
|
||||
* Function returns isUpdating() status after waiting.
|
||||
**/
|
||||
bool WS2812FX::waitForLEDs(unsigned maxWaitMS, bool always) const {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv)
|
||||
// shortcut: don't wait if we have the RMTHI driver, unless requested with "always = true"
|
||||
if (!always) return isUpdating();
|
||||
#endif
|
||||
|
||||
unsigned long waitStart = millis();
|
||||
while (isUpdating() && (millis() - waitStart < maxWaitMS)) delay(1);
|
||||
#else
|
||||
if (can_yield()) {
|
||||
// If we are in a yieldable context (main loop), wait until the LEDs output finishes
|
||||
yield();
|
||||
unsigned long waitStart = millis();
|
||||
while (isUpdating() && (millis() - waitStart < maxWaitMS)) delay(1);
|
||||
yield();
|
||||
}
|
||||
#endif
|
||||
return isUpdating();
|
||||
}
|
||||
|
||||
void WS2812FX::setTargetFps(unsigned fps) {
|
||||
if (fps <= 250) _targetFps = fps;
|
||||
if (_targetFps > 0) _frametime = 1000 / _targetFps;
|
||||
|
||||
@@ -472,6 +472,8 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
|
||||
#define ERR_OVERTEMP 30 // An attached temperature sensor has measured above threshold temperature (not implemented)
|
||||
#define ERR_OVERCURRENT 31 // An attached current sensor has measured a current above the threshold (not implemented)
|
||||
#define ERR_UNDERVOLT 32 // An attached voltmeter has measured a voltage below the threshold (not implemented)
|
||||
#define ERR_REBOOT_NEEDED 98 // reboot needed after changing hardware setting
|
||||
#define ERR_POWEROFF_NEEDED 99 // power-cycle needed after changing hardware setting
|
||||
|
||||
// JSON buffer lock owners
|
||||
#define JSON_LOCK_UNKNOWN 255
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
d.rsvd = [];
|
||||
d.ro_gpio = [];
|
||||
d.extra = [];
|
||||
d.a_pins = []; // analog pins array
|
||||
}, ()=>{
|
||||
if (d.um_p[0]==-1) d.um_p.shift(); // remove filler
|
||||
d.Sf.SDA.max = d.Sf.SCL.max = d.Sf.MOSI.max = d.Sf.SCLK.max = d.Sf.MISO.max = d.max_gpio;
|
||||
@@ -194,7 +195,11 @@
|
||||
um += ":"+fld;
|
||||
} else if (typeof(fld) === "number") sel.classList.add("pin"); // a hack to add a class
|
||||
let arr = d.getElementsByName(um);
|
||||
let idx = arr[0].type==="hidden"?1:0; // ignore hidden field
|
||||
if (!arr || arr.length === 0) {
|
||||
console.log("addDD: No elements found for name:", um);
|
||||
return null; // no elements found
|
||||
}
|
||||
let idx = (arr[0] && arr[0].type==="hidden")?1:0; // ignore hidden field
|
||||
if (arr.length > 1+idx) {
|
||||
// we have array of values (usually pins)
|
||||
for (let i of arr) {
|
||||
@@ -256,6 +261,98 @@
|
||||
e.preventDefault();
|
||||
if (d.Sf.checkValidity()) d.Sf.submit(); //https://stackoverflow.com/q/37323914
|
||||
}
|
||||
|
||||
// TODO: rename this function, needs to be in sync with the now out of tree mod
|
||||
function aOpt(name,el) {
|
||||
let obj = d.getElementsByName(name);
|
||||
if (!obj || obj.length === 0) return; // No elements found
|
||||
|
||||
var select = obj;
|
||||
if (obj[el]) select = obj[el];
|
||||
|
||||
// Check if it's actually a select element with options
|
||||
if (!select.options || !select.options.length) return;
|
||||
|
||||
for (let i=0; i<select.options.length; i++) {
|
||||
let c = select.options[i];
|
||||
let found = false;
|
||||
for (let jj=0; jj<d.a_pins.length; jj++) if (d.a_pins[jj] == c.value) found = true; //value -1 or analog pins
|
||||
if (c.value != -1 && !found) {
|
||||
select.removeChild(c);
|
||||
i--; //decrease i by one because the index has been adjusted
|
||||
}
|
||||
//https://www.javascripttutorial.net/javascript-dom/javascript-add-remove-options/
|
||||
//https://www.javascripttutorial.net/javascript-dom/javascript-remove-items-from-a-select-conditionally/
|
||||
}
|
||||
}
|
||||
function rOpt(name,el,txt,val) {
|
||||
let obj = d.getElementsByName(name);
|
||||
if (!obj || obj.length === 0) return; // No elements found
|
||||
var select = obj;
|
||||
if (obj[el]) select = obj[el];
|
||||
|
||||
// Check if element has childNodes
|
||||
if (!select.childNodes || !select.childNodes.length) return;
|
||||
|
||||
for (let i=0; i<select.childNodes.length; i++) {
|
||||
let c = select.childNodes[i];
|
||||
if (c.value == val) c.text = txt;
|
||||
}
|
||||
}
|
||||
function xOpt(name,el,txt,val) {
|
||||
let obj = d.getElementsByName(name);
|
||||
if (!obj || obj.length === 0) return; // No elements found
|
||||
var select = obj;
|
||||
if (obj[el]) select = obj[el];
|
||||
|
||||
// Check if element has childNodes
|
||||
if (!select.childNodes || !select.childNodes.length) return;
|
||||
|
||||
for (let i=0; i<select.childNodes.length; i++) {
|
||||
let c = select.childNodes[i];
|
||||
if (c.value == val) c.text += txt;
|
||||
}
|
||||
}
|
||||
function dOpt(name,el,valFrom,valTo) {
|
||||
let obj = d.getElementsByName(name);
|
||||
if (!obj || obj.length === 0) return; // No elements found
|
||||
var select = obj;
|
||||
if (obj[el]) select = obj[el];
|
||||
|
||||
// Check if it's actually a select element with options
|
||||
if (!select.options || !select.options.length) return;
|
||||
|
||||
for (let i=0; i<select.options.length; i++) {
|
||||
let c = select.options[i];
|
||||
if (c.value >= valFrom && c.value <= valTo) {
|
||||
select.removeChild(c);
|
||||
i--; //decrease i by one because the index has been adjusted
|
||||
}
|
||||
//https://www.javascripttutorial.net/javascript-dom/javascript-add-remove-options/
|
||||
//https://www.javascripttutorial.net/javascript-dom/javascript-remove-items-from-a-select-conditionally/
|
||||
}
|
||||
}
|
||||
function dRO(name,el) {
|
||||
// Initialize d.ro_gpio if not already set
|
||||
if (!d.ro_gpio) d.ro_gpio = [];
|
||||
|
||||
let obj = d.getElementsByName(name);
|
||||
if (!obj || obj.length === 0) return; // No elements found
|
||||
|
||||
var select = obj;
|
||||
if (obj[el]) select = obj[el];
|
||||
|
||||
// Check if it's actually a select element with options
|
||||
if (!select.options || !select.options.length) return;
|
||||
|
||||
// console.log("dRO", name, el, obj, "s", select, d.ro_gpio);
|
||||
for (let i=0; i<select.options.length; i++) {
|
||||
let c = select.options[i];
|
||||
// console.log("dRO option", c, c.value, d.ro_gpio.includes(c.value));
|
||||
for (let j=0; j<d.ro_gpio.length; j++) if (d.ro_gpio[j] == c.value) c.disabled=true; //if (d.ro_gpio.includes(c.value))
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
||||
|
||||
+16
-1
@@ -34,13 +34,24 @@ static File f; // don't export to other cpp files
|
||||
|
||||
//wrapper to find out how long closing takes
|
||||
void closeFile() {
|
||||
if (!doCloseFile || !f) { doCloseFile = false; return; } // file not open, or no request to close -> nothing to do, nothing to wait
|
||||
#ifdef WLED_DEBUG_FS
|
||||
DEBUGFS_PRINT(F("Close -> "));
|
||||
uint32_t s = millis();
|
||||
#endif
|
||||
doCloseFile = false; // consume flag early, to reduce the time window for concurrent closing attempts from several tasks.
|
||||
|
||||
// f.close() may enter flash critical sections (interrupts/cache paused), so we wait for LED transmission to finish first to avoid WS281x glitches
|
||||
// This is most relevant on ESP32-C3/C5/C6, where the RMT driver is very sensitive to interrupt timing.
|
||||
bool haveSuspended = false;
|
||||
#if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32)
|
||||
if (!strip.isSuspended()) { strip.suspend(); haveSuspended = true; } // prevent that a new strip.show() starts after waiting
|
||||
strip.waitForLEDs(STRIP_WAIT_MEDIUM); // be nice, but not too nice. Waits up to 150ms
|
||||
#endif
|
||||
|
||||
f.close(); // "if (f)" check is aleady done inside f.close(), and f cannot be nullptr -> no need for double checking before closing the file handle.
|
||||
if (haveSuspended) strip.resume(); // end of critical section - new LEDs updates are allowed again
|
||||
DEBUGFS_PRINTF("took %lu ms\n", millis() - s);
|
||||
doCloseFile = false;
|
||||
}
|
||||
|
||||
//find() that reads and buffers data from file stream in 256-byte blocks.
|
||||
@@ -437,6 +448,7 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){
|
||||
}
|
||||
}
|
||||
#endif
|
||||
strip.waitForLEDs(STRIP_WAIT_SHORT); // wait for LEDs before file access (not using strip.suspend(), to avoid effect stuttering)
|
||||
if(WLED_FS.exists(path) || WLED_FS.exists(path + ".gz")) {
|
||||
request->send(request->beginResponse(WLED_FS, path, {}, request->hasArg(F("download")), {}));
|
||||
return true;
|
||||
@@ -447,6 +459,7 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){
|
||||
// copy a file, delete destination file if incomplete to prevent corrupted files
|
||||
bool copyFile(const char* src_path, const char* dst_path) {
|
||||
DEBUG_PRINTF("copyFile from %s to %s\n", src_path, dst_path);
|
||||
strip.waitForLEDs(STRIP_WAIT_MEDIUM, true); // wait for LEDs before file access (not using strip.suspend(), to avoid effect stuttering)
|
||||
if(!WLED_FS.exists(src_path)) {
|
||||
DEBUG_PRINTLN(F("file not found"));
|
||||
return false;
|
||||
@@ -485,6 +498,7 @@ bool copyFile(const char* src_path, const char* dst_path) {
|
||||
// compare two files, return true if identical
|
||||
bool compareFiles(const char* path1, const char* path2) {
|
||||
DEBUG_PRINTF("compareFile %s and %s\n", path1, path2);
|
||||
strip.waitForLEDs(STRIP_WAIT_SHORT); // wait for LEDs before file access (not using strip.suspend(), to avoid effect stuttering)
|
||||
if (!WLED_FS.exists(path1) || !WLED_FS.exists(path2)) {
|
||||
DEBUG_PRINTLN(F("file not found"));
|
||||
return false;
|
||||
@@ -543,6 +557,7 @@ bool restoreFile(const char* filename) {
|
||||
char backupname[32];
|
||||
snprintf_P(backupname, sizeof(backupname), s_backup_fmt, filename + 1); // skip leading '/' in filename
|
||||
|
||||
strip.waitForLEDs(STRIP_WAIT_MEDIUM); // wait for LEDs before file existence checking
|
||||
if (!WLED_FS.exists(backupname)) {
|
||||
DEBUG_PRINTLN(F("no backup found"));
|
||||
return false;
|
||||
|
||||
@@ -28,10 +28,7 @@ int fileReadCallback(void) {
|
||||
}
|
||||
|
||||
int fileReadBlockCallback(void * buffer, int numberOfBytes) {
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
unsigned t0 = millis();
|
||||
while (strip.isUpdating() && (millis() - t0 < 150)) yield(); // be nice, but not too nice. Waits up to 150ms to avoid glitches
|
||||
#endif
|
||||
strip.waitForLEDs(STRIP_WAIT_MEDIUM);
|
||||
return file.read((uint8_t*)buffer, numberOfBytes);
|
||||
}
|
||||
|
||||
@@ -137,6 +134,7 @@ byte renderImageToSegment(Segment &seg) {
|
||||
DEBUG_PRINTF_P(PSTR("GIF decoder unsupported file: %s\n"), lastFilename);
|
||||
return IMAGE_ERROR_UNSUPPORTED_FORMAT;
|
||||
}
|
||||
strip.waitForLEDs(STRIP_WAIT_MEDIUM);
|
||||
if (file) file.close();
|
||||
if (!openGif(lastFilename)) {
|
||||
gifDecodeFailed = true;
|
||||
|
||||
@@ -849,6 +849,8 @@ void serializeInfo(JsonObject root)
|
||||
root[F("lwip")] = LWIP_VERSION_MAJOR;
|
||||
#endif
|
||||
|
||||
// calling ESP.getFreeHeap() during led update causes glitches on C3 and possibly on 8266, too
|
||||
strip.waitForLEDs(STRIP_WAIT_SHORT); // be nice, but not too nice. Waits up to 50ms. No need to suspend effects - ESP.getFreeHeap() will not need much time
|
||||
root[F("freeheap")] = getFreeHeapSize();
|
||||
#if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM)
|
||||
// Report PSRAM information
|
||||
|
||||
@@ -239,6 +239,7 @@ static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context)
|
||||
UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
|
||||
|
||||
strip.suspend();
|
||||
strip.waitForLEDs(STRIP_WAIT_MEDIUM, true); // always wait for LED transmissions to finish
|
||||
backupConfig(); // backup current config in case the update ends badly
|
||||
strip.resetSegments(); // free as much memory as you can
|
||||
context->needsRestart = true;
|
||||
@@ -759,6 +760,7 @@ bool initBootloaderOTA(AsyncWebServerRequest *request) {
|
||||
#endif
|
||||
lastEditTime = millis(); // make sure PIN does not lock during update
|
||||
strip.suspend();
|
||||
strip.waitForLEDs(STRIP_WAIT_SHORT, true); // be sure that LED transmissions have finished
|
||||
strip.resetSegments();
|
||||
|
||||
// Check available heap before attempting allocation
|
||||
|
||||
+3
-5
@@ -174,12 +174,9 @@ void WLED::loop()
|
||||
#ifdef ESP8266
|
||||
uint32_t heap = getFreeHeapSize(); // ESP8266 needs ~8k of free heap for UI to work properly
|
||||
#else
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
// calling getContiguousFreeHeap() during led update causes glitches on C3
|
||||
// this can (probably) be removed once RMT driver for C3 is fixed
|
||||
unsigned t0 = millis();
|
||||
while (strip.isUpdating() && (millis() - t0 < 150)) delay(1); // be nice, but not too nice. Waits up to 150ms
|
||||
#endif
|
||||
strip.waitForLEDs(STRIP_WAIT_MEDIUM); // be nice, but not too nice. Waits up to 150ms - we are in the main loop, so a new strip.show() cannot start while waiting
|
||||
uint32_t heap = getContiguousFreeHeap(); // ESP32 family needs ~10k of contiguous free heap for UI to work properly
|
||||
#endif
|
||||
if (heap < MIN_HEAP_SIZE - 1024) heapDanger+=5; // allow 1k of "wiggle room" for things that do not respect min heap limits
|
||||
@@ -494,6 +491,7 @@ void WLED::setup()
|
||||
|
||||
DEBUG_PRINTLN(F("Initializing strip"));
|
||||
beginStrip();
|
||||
strip.waitForLEDs(STRIP_WAIT_MEDIUM); // prevent flickering - beginStrip() calls strip.show()
|
||||
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
|
||||
|
||||
DEBUG_PRINTLN(F("Usermods setup"));
|
||||
@@ -598,7 +596,7 @@ void WLED::setup()
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET)
|
||||
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); //enable brownout detector
|
||||
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); //re-enable brownout detector
|
||||
#endif
|
||||
markOTAvalid();
|
||||
}
|
||||
|
||||
@@ -212,7 +212,14 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename,
|
||||
request->_tempFile.write(data,len);
|
||||
}
|
||||
if (isFinal) {
|
||||
bool haveSuspended = false;
|
||||
#if defined(WLED_USE_SHARED_RMT) || defined(__riscv) || !defined(ARDUINO_ARCH_ESP32)
|
||||
if (!strip.isSuspended()) { strip.suspend(); haveSuspended = true; } // prevent that a new strip.show() starts after waiting
|
||||
strip.waitForLEDs(STRIP_WAIT_SHORT, true); // calling file.close() during LEDs sendout can cause glitches on C3 and on 8266
|
||||
#endif
|
||||
request->_tempFile.close();
|
||||
if (haveSuspended) strip.resume();
|
||||
|
||||
if (filename.indexOf(F("cfg.json")) >= 0) { // check for filename with or without slash
|
||||
doReboot = true;
|
||||
request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("Config restore ok.\nRebooting..."));
|
||||
|
||||
@@ -153,6 +153,7 @@ void sendDataWs(AsyncWebSocketClient * client)
|
||||
DEBUG_PRINTF_P(PSTR("JSON buffer size: %u for WS request (%u).\n"), pDoc->memoryUsage(), len);
|
||||
|
||||
// the following may no longer be necessary as heap management has been fixed by @willmmiles in AWS
|
||||
strip.waitForLEDs(STRIP_WAIT_VERYSHORT); // wait for LEDs ouptut to finish - prevents glitches on -C3
|
||||
size_t heap1 = getFreeHeapSize();
|
||||
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
|
||||
AsyncWebSocketBuffer buffer(len);
|
||||
|
||||
Reference in New Issue
Block a user