diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 167d5285..c4b1e0e9 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5062,8 +5062,8 @@ uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmateli unsigned long t_20 = t/20; // softhack007: pre-calculating this gives about 10% speedup for (float i = 1; i < maxDim; i += 0.25) { 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); + uint16_t myX = (cols>>1) + (uint16_t)(sinf(angle) * i) + (cols%2); + uint16_t myY = (rows>>1) + (uint16_t)(cosf(angle) * i) + (rows%2); SEGMENT.setPixelColorXY(myX, myY, ColorFromPalette(SEGPALETTE, (i * 20) + t_20, 255, LINEARBLEND)); } SEGMENT.blur(SEGMENT.intensity>>3); @@ -5338,8 +5338,8 @@ uint16_t mode_2DJulia(void) { // An animated Julia set reAl = -0.94299f; // PixelBlaze example imAg = 0.3162f; - reAl += sin_t((float)strip.now/305.f)/20.f; - imAg += sin_t((float)strip.now/405.f)/20.f; + reAl += sinf((float)strip.now/305.f)/20.f; + imAg += sinf((float)strip.now/405.f)/20.f; dx = (xmax - xmin) / (cols); // Scale the delta x and y values to our matrix size. dy = (ymax - ymin) / (rows); @@ -6067,8 +6067,8 @@ uint16_t mode_2Dghostrider(void) { CRGB color = CRGB::White; SEGMENT.wu_pixel(lighter->gPosX * 256 / 10, lighter->gPosY * 256 / 10, color); - lighter->gPosX += lighter->Vspeed * sin_t(radians(lighter->gAngle)); - lighter->gPosY += lighter->Vspeed * cos_t(radians(lighter->gAngle)); + lighter->gPosX += lighter->Vspeed * sinf(radians(lighter->gAngle)); + lighter->gPosY += lighter->Vspeed * cosf(radians(lighter->gAngle)); lighter->gAngle += lighter->angleSpeed; if (lighter->gPosX < 0) lighter->gPosX = (cols - 1) * 10; if (lighter->gPosX > (cols - 1) * 10) lighter->gPosX = 0; @@ -6090,8 +6090,8 @@ uint16_t mode_2Dghostrider(void) { lighter->time[i] = 0; lighter->reg[i] = false; } else { - lighter->lightersPosX[i] += -7 * sin_t(radians(lighter->Angle[i])); - lighter->lightersPosY[i] += -7 * cos_t(radians(lighter->Angle[i])); + lighter->lightersPosX[i] += -7 * sinf(radians(lighter->Angle[i])); + lighter->lightersPosY[i] += -7 * cosf(radians(lighter->Angle[i])); } SEGMENT.wu_pixel(lighter->lightersPosX[i] * 256 / 10, lighter->lightersPosY[i] * 256 / 10, ColorFromPalette(SEGPALETTE, (256 - lighter->time[i]))); } @@ -6303,8 +6303,8 @@ 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; + uint32_t x = (CX + (sinf(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f; + uint32_t y = (CY + (cosf(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f; SEGMENT.wu_pixel(x, y, CHSV(i * 10, 255, 255)); } SEGMENT.blur((SEGMENT.intensity>>4)+1); diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 1f7733f6..fa464320 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -776,6 +776,16 @@ void Segment::deletejMap() { } } + +// WLEDMM constants for mapping mode "Pinwheel" +constexpr int Pinwheel_Steps_Medium = 208; // no holes up to 32x32; 60fps +constexpr int Pinwheel_Size_Medium = 32; // larger than this -> use "Big" +constexpr int Pinwheel_Steps_Big = 360; // no holes expected up to 58x58; 40fps +constexpr float Int_to_Rad_Med = (DEG_TO_RAD * 360) / Pinwheel_Steps_Medium; // conversion: from 0...208 to Radians +constexpr float Int_to_Rad_Big = (DEG_TO_RAD * 360) / Pinwheel_Steps_Big; // conversion: from 0...360 to Radians +// WLEDMM end + + // 1D strip uint16_t Segment::virtualLength() const { #ifndef WLED_DISABLE_2D @@ -806,7 +816,11 @@ uint16_t Segment::virtualLength() const { vLen = max(vW,vH) * 0.5; // get the longest dimension break; case M12_sPinWheel: //WLEDMM - vLen = 360; // full circle + //vLen = full circle + if (max(vW,vH) <= Pinwheel_Size_Medium) + vLen = Pinwheel_Steps_Medium; + else + vLen = Pinwheel_Steps_Big; break; } return vLen; @@ -875,13 +889,26 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) //WLEDMM: IRAM_ATT else { //WLEDMM: drawArc(0, 0, i, col); could work as alternative - float step = HALF_PI / (2.85f*i); - for (float rad = 0.0f; rad <= HALF_PI+step/2; rad += step) { + //WLEDMM: some optimizations for the drawing loop + // pre-calculate loop limits, exploit symmetry at 45deg + float radius = float(i); + // float step = HALF_PI / (2.85f * radius); // upstream uses this + float step = HALF_PI / (M_PI * radius); // WLEDMM we use the correct circumference + bool useSymmetry = (max(vH, vW) > 20); // for segments wider than 20 pixels, we exploit symmetry + unsigned numSteps; + if (useSymmetry) numSteps = 1 + ((HALF_PI/2.0f + step/2.0f) / step); // with symmetry + else numSteps = 1 + ((HALF_PI + step/2.0f) / step); // without symmetry + + float rad = 0.0f; + for (unsigned count = 0; count < numSteps; count++) { // may want to try float version as well (with or without antialiasing) - int x = roundf(sin_t(rad) * i); - int y = roundf(cos_t(rad) * i); + int x = roundf(sinf(rad) * radius); + int y = roundf(cosf(rad) * radius); setPixelColorXY(x, y, col); + if(useSymmetry) setPixelColorXY(y, x, col);// WLEDMM + rad += step; } + // Bresenham’s Algorithm (may not fill every pixel) //int d = 3 - (2*i); //int y = i, x = 0; @@ -909,8 +936,8 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) //WLEDMM: IRAM_ATT case M12_sCircle: //WLEDMM if (vStrip > 0) { - int x = roundf(sin_t(360*i/SEGLEN*DEG_TO_RAD) * vW * (vStrip+1)/nrOfVStrips()); - int y = roundf(cos_t(360*i/SEGLEN*DEG_TO_RAD) * vW * (vStrip+1)/nrOfVStrips()); + int x = roundf(sinf(360*i/SEGLEN*DEG_TO_RAD) * vW * (vStrip+1)/nrOfVStrips()); + int y = roundf(cosf(360*i/SEGLEN*DEG_TO_RAD) * vW * (vStrip+1)/nrOfVStrips()); setPixelColorXY(x + vW/2, y + vH/2, col); } else // pArc -> circle @@ -935,24 +962,35 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) //WLEDMM: IRAM_ATT } } break; - case M12_sPinWheel: { - // i = 0 through 359 - float centerX = (vW-1) / 2; - float centerY = (vH-1) / 2; + case M12_sPinWheel: { // WLEDMM + // i = angle --> 0 through 359 (Big), OR 0 through 208 (Medium) + float centerX = roundf((vW-1) / 2.0f); + float centerY = roundf((vH-1) / 2.0f); // int maxDistance = sqrt(centerX * centerX + centerY * centerY) + 1; - - int distance = 0; - float cosVal = cos(i * DEG_TO_RAD); // i = current angle - float sinVal = sin(i * DEG_TO_RAD); - while (true) { - int x = round(centerX + distance * cosVal); - int y = round(centerY + distance * sinVal); - // Check bounds - if (x < 0 || x >= vW || y < 0 || y >= vH) { - break; - } + float angleRad = (max(vW,vH) > Pinwheel_Size_Medium) ? float(i) * Int_to_Rad_Big : float(i) * Int_to_Rad_Med; // angle in radians + float cosVal = cosf(angleRad); + float sinVal = sinf(angleRad); + + // draw line at angle, starting at center and ending at the segment edge + // we use fixed point math for better speed. Starting distance is 0.5 for better rounding + constexpr int_fast32_t Fixed_Scale = 512; // fixpoint scaling factor + int_fast32_t posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point + int_fast32_t posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point + int_fast16_t inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) + int_fast16_t inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) + + int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint + int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint + // draw until we hit any edge + while ((posx > 0) && (posy > 0) && (posx < maxX) && (posy < maxY)) { + // scale down to integer (compiler will replace division with appropriate bitshift) + int x = posx / Fixed_Scale; + int y = posy / Fixed_Scale; + // set pixel setPixelColorXY(x, y, col); - distance++; + // advance to next position + posx += inc_x; + posy += inc_y; } break; } @@ -1076,8 +1114,8 @@ uint32_t Segment::getPixelColor(int i) case M12_sCircle: //WLEDMM if (vStrip > 0) { - int x = roundf(sin_t(360*i/SEGLEN*DEG_TO_RAD) * vW * (vStrip+1)/nrOfVStrips()); - int y = roundf(cos_t(360*i/SEGLEN*DEG_TO_RAD) * vW * (vStrip+1)/nrOfVStrips()); + int x = roundf(sinf(360*i/SEGLEN*DEG_TO_RAD) * vW * (vStrip+1)/nrOfVStrips()); + int y = roundf(cosf(360*i/SEGLEN*DEG_TO_RAD) * vW * (vStrip+1)/nrOfVStrips()); return getPixelColorXY(x + vW/2, y + vH/2); } else @@ -1094,12 +1132,13 @@ uint32_t Segment::getPixelColor(int i) return getPixelColorXY(vW / 2, vH / 2 - i - 1); break; case M12_sPinWheel: //WLEDMM - // not 100% accurate, returns outer edge of circle - int distance = min(vH, vW) / 2; - float centerX = (vW - 1) / 2; - float centerY = (vH - 1) / 2; - int x = round(centerX + distance * cos(i * DEG_TO_RAD)); - int y = round(centerY + distance * sin(i * DEG_TO_RAD)); + // not 100% accurate, returns outer edge of circle + float distance = max(1.0f, min(vH-1, vW-1) / 2.0f); + float centerX = (vW - 1) / 2.0f; + float centerY = (vH - 1) / 2.0f; + float angleRad = (max(vW,vH) > Pinwheel_Size_Medium) ? float(i) * Int_to_Rad_Big : float(i) * Int_to_Rad_Med; // angle in radians + int x = roundf(centerX + distance * cosf(angleRad)); + int y = roundf(centerY + distance * sinf(angleRad)); return getPixelColorXY(x, y); } return 0; diff --git a/wled00/const.h b/wled00/const.h index 4492f631..ae97beef 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -344,6 +344,7 @@ #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_LOW_MEM 33 // low memory (RAM) // Timer mode types #define NL_MODE_SET 0 //After nightlight time elapsed, set to target brightness diff --git a/wled00/data/index.js b/wled00/data/index.js index 783424ff..c8159d9e 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -1967,6 +1967,9 @@ function readState(s,command=false) case 19: errstr = "A filesystem error has occured."; break; + case 33: + errstr = "Warning: Low Memory (RAM)."; + break; } showToast('Error ' + s.error + ": " + errstr, true); } diff --git a/wled00/json.cpp b/wled00/json.cpp index 55ed72d4..8bd9cd2d 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1405,14 +1405,26 @@ void serializeModeNames(JsonArray arr) { // Global buffer locking response helper class (to make sure lock is released when AsyncJsonResponse is destroyed) class LockedJsonResponse: public AsyncJsonResponse { + bool _holding_lock; public: // WARNING: constructor assumes requestJSONBufferLock() was successfully acquired externally/prior to constructing the instance // Not a good practice with C++. Unfortunately AsyncJsonResponse only has 2 constructors - for dynamic buffer or existing buffer, // with existing buffer it clears its content during construction // if the lock was not acquired (using JSONBufferGuard class) previous implementation still cleared existing buffer - inline LockedJsonResponse(JsonDocument *doc, bool isArray) : AsyncJsonResponse(doc, isArray) {}; + inline LockedJsonResponse(JsonDocument* doc, bool isArray) : AsyncJsonResponse(doc, isArray), _holding_lock(true) {}; + + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) { + size_t result = AsyncJsonResponse::_fillBuffer(buf, maxLen); + // Release lock as soon as we're done filling content + if (((result + _sentLength) >= (_contentLength)) && _holding_lock) { + releaseJSONBufferLock(); + _holding_lock = false; + } + return result; + } + // destructor will remove JSON buffer lock when response is destroyed in AsyncWebServer - virtual ~LockedJsonResponse() { releaseJSONBufferLock(); }; + virtual ~LockedJsonResponse() { if (_holding_lock) releaseJSONBufferLock(); }; }; void serveJson(AsyncWebServerRequest* request) diff --git a/wled00/presets.cpp b/wled00/presets.cpp index 748771a9..3beab1b8 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -95,7 +95,7 @@ static void doSaveState() { bool getPresetName(byte index, String& name) { - if (!requestJSONBufferLock(9)) return false; + if (!requestJSONBufferLock(19)) return false; bool presetExists = false; if (readObjectFromFileUsingId(getFileName(), index, &doc)) { diff --git a/wled00/util.cpp b/wled00/util.cpp index 37d1b2c2..31f9716c 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -204,12 +204,12 @@ bool requestJSONBufferLock(uint8_t module) { unsigned long now = millis(); - while (jsonBufferLock && millis()-now < 1000) delay(1); // wait for a second for buffer lock + while (jsonBufferLock && millis()-now < 1200) delay(1); // wait for fraction for buffer lock - if (millis()-now >= 1000) { - DEBUG_PRINT(F("ERROR: Locking JSON buffer failed! (")); - DEBUG_PRINT(jsonBufferLock); - DEBUG_PRINTLN(")"); + if (jsonBufferLock) { + USER_PRINT(F("ERROR: Locking JSON buffer failed! (still locked by ")); + USER_PRINT(jsonBufferLock); + USER_PRINTLN(")"); return false; // waiting time-outed } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index a5a93430..e8e9eecc 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -1205,6 +1205,7 @@ void WLED::handleConnection() return; } + static unsigned retryCount = 0; // WLEDMM #ifdef ARDUINO_ARCH_ESP32 // reconnect WiFi to clear stale allocations if heap gets too low if (now - heapTime > 5000) { // WLEDMM: updated with better logic for small heap available by block, not total. @@ -1214,10 +1215,16 @@ void WLED::handleConnection() uint32_t heap = heap_caps_get_largest_free_block(0x1800); // WLEDMM: This is a better metric for free heap. #endif if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) { - USER_PRINT(F("Heap too low! (step 2, force reconnect): ")); - USER_PRINTLN(heap); - forceReconnect = true; - strip.purgeSegments(true); // remove all but one segments from memory + if (retryCount < 5) { // WLEDMM avoid repeated disconnects + USER_PRINT(F("Heap too low! (step 2, force reconnect): ")); + USER_PRINTLN(heap); + forceReconnect = true; + strip.purgeSegments(true); // remove all but one segments from memory + // WLEDMM + errorFlag = ERR_LOW_MEM; + retryCount ++; + } + errorFlag = ERR_LOW_MEM; } else if (heap < MIN_HEAP_SIZE) { USER_PRINT(F("Heap too low! (step 1, flush unread UDP): ")); USER_PRINTLN(heap); @@ -1226,7 +1233,10 @@ void WLED::handleConnection() rgbUdp.flush(); notifier2Udp.flush(); ntpUdp.flush(); - } + // WLEDMM + errorFlag = ERR_LOW_MEM; + retryCount = 1; + } else retryCount = 0; // WLEDMM memory OK - reset counter lastHeap = heap; heapTime = now; } @@ -1235,15 +1245,23 @@ void WLED::handleConnection() if (now - heapTime > 5000) { uint32_t heap = ESP.getFreeHeap(); if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) { - USER_PRINT(F("Heap too low! (step 2, force reconnect): ")); - USER_PRINTLN(heap); - forceReconnect = true; - strip.purgeSegments(true); // remove all but one segments from memory + if (retryCount < 5) { // WLEDMM avoid repeated disconnects + USER_PRINT(F("Heap too low! (step 2, force reconnect): ")); + USER_PRINTLN(heap); + forceReconnect = true; + strip.purgeSegments(true); // remove all but one segments from memory + // WLEDMM + errorFlag = ERR_LOW_MEM; + retryCount ++; + } } else if (heap < MIN_HEAP_SIZE) { USER_PRINT(F("Heap too low! (step 1, purge segments): ")); USER_PRINTLN(heap); strip.purgeSegments(); - } + // WLEDMM + errorFlag = ERR_LOW_MEM; + retryCount = 1; + } else retryCount = 0; // WLEDMM memory OK - reset counter lastHeap = heap; heapTime = now; } diff --git a/wled00/wled.h b/wled00/wled.h index 3994a48f..5008c1af 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2402180 +#define VERSION 2402252 // WLEDMM - you can check for this define in usermods, to only enabled WLEDMM specific code in the "right" fork. Its not defined in AC WLED. #define _MoonModules_WLED_ @@ -822,11 +822,13 @@ WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0); #define DEBUGOUT(x) (netDebugEnabled || !canUseSerial())?NetDebug.print(x):Serial.print(x) #define DEBUGOUTLN(x) (netDebugEnabled || !canUseSerial())?NetDebug.println(x):Serial.println(x) #define DEBUGOUTF(x...) (netDebugEnabled || !canUseSerial())?NetDebug.printf(x):Serial.printf(x) + #define DEBUGOUTFP(x...) (netDebugEnabled || !canUseSerial())?NetDebug.printf_P(x):Serial.printf_P(x) #define DEBUGOUTFlush() (netDebugEnabled || !canUseSerial())?NetDebug.flush():Serial.flush() #else #define DEBUGOUT(x) {if (canUseSerial()) Serial.print(x);} #define DEBUGOUTLN(x) {if (canUseSerial()) Serial.println(x);} #define DEBUGOUTF(x...) {if (canUseSerial()) Serial.printf(x);} + #define DEBUGOUTFP(x...) {if (canUseSerial()) Serial.printf_P(x);} #define DEBUGOUTFlush() {if (canUseSerial()) Serial.flush();} #endif @@ -837,10 +839,12 @@ WLED_GLOBAL volatile uint8_t jsonBufferLock _INIT(0); #define DEBUG_PRINT(x) DEBUGOUT(x) #define DEBUG_PRINTLN(x) DEBUGOUTLN(x) #define DEBUG_PRINTF(x...) DEBUGOUTF(x) + #define DEBUG_PRINTF_P(x...) DEBUGOUTFP(x) #else #define DEBUG_PRINT(x) #define DEBUG_PRINTLN(x) #define DEBUG_PRINTF(x...) + #define DEBUG_PRINTF_P(x...) #endif #define USER_PRINT(x) DEBUGOUT(x) diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp index c34e29dc..596b5791 100644 --- a/wled00/wled_serial.cpp +++ b/wled00/wled_serial.cpp @@ -136,7 +136,10 @@ void handleSerial() } else if (next == '{') { //JSON API bool verboseResponse = false; - if (!requestJSONBufferLock(16)) return; + if (!requestJSONBufferLock(16)) { + if (Serial) Serial.println(F("{\"error\":3}")); // ERR_NOBUF + return; + } Serial.setTimeout(100); DeserializationError error = deserializeJson(doc, Serial); if (error) { diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 2ed23ce5..85f75406 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -41,7 +41,10 @@ void wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp } bool verboseResponse = false; - if (!requestJSONBufferLock(11)) return; + if (!requestJSONBufferLock(11)) { + client->text(F("{\"error\":3}")); // ERR_NOBUF + return; + } DeserializationError error = deserializeJson(doc, data, len); JsonObject root = doc.as(); @@ -106,7 +109,14 @@ void sendDataWs(AsyncWebSocketClient * client) if (!ws.count()) return; AsyncWebSocketMessageBuffer * buffer; - if (!requestJSONBufferLock(12)) return; + if (!requestJSONBufferLock(12)) { + if (client) { + client->text(F("{\"error\":3}")); // ERR_NOBUF + } else { + ws.textAll(F("{\"error\":3}")); // ERR_NOBUF + } + return; + } JsonObject state = doc.createNestedObject("state"); serializeState(state); diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 147f666e..9bbb5e99 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -265,16 +265,10 @@ void appendGPIOinfo() { // add info about max. # of pins oappend(SET_F("d.max_gpio=")); - #if defined(CONFIG_IDF_TARGET_ESP32S2) - oappendi(46); - #elif defined(CONFIG_IDF_TARGET_ESP32S3) - oappendi(48); - #elif defined(CONFIG_IDF_TARGET_ESP32C3) - oappendi(21); - #elif defined(ESP32) - oappendi(39); + #if defined(ESP32) + oappendi(NUM_DIGITAL_PINS - 1); #else //8266 - oappendi(NUM_DIGITAL_PINS); //WLEDMM include pin 17 for Analog + oappendi(NUM_DIGITAL_PINS); //WLEDMM include pin 17 for Analog #endif oappend(SET_F(";"));