mirror of
https://github.com/wled/WLED.git
synced 2026-04-22 15:12:45 +00:00
Compare commits
8 Commits
ar-network
...
copilot/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8087e0fbb | ||
|
|
028902e596 | ||
|
|
ca7f8d463a | ||
|
|
b7c863a3c0 | ||
|
|
5497f7e7e9 | ||
|
|
3af2ae5f2f | ||
|
|
259bf3c0f8 | ||
|
|
5e49a1cffb |
@@ -375,7 +375,6 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
|
||||
-D WLED_DISABLE_PARTICLESYSTEM2D
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
monitor_filters = esp8266_exception_decoder
|
||||
custom_usermods = audioreactive
|
||||
|
||||
[env:nodemcuv2_compat]
|
||||
extends = env:nodemcuv2
|
||||
@@ -385,7 +384,6 @@ platform_packages = ${esp8266.platform_packages_compat}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP8266_compat\" #-DWLED_DISABLE_2D
|
||||
-D WLED_DISABLE_PARTICLESYSTEM2D
|
||||
;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9
|
||||
custom_usermods = audioreactive
|
||||
|
||||
[env:nodemcuv2_160]
|
||||
extends = env:nodemcuv2
|
||||
@@ -404,7 +402,6 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=
|
||||
-D WLED_DISABLE_PARTICLESYSTEM2D
|
||||
-D WLED_DISABLE_PARTICLESYSTEM1D
|
||||
lib_deps = ${esp8266.lib_deps}
|
||||
custom_usermods = audioreactive
|
||||
|
||||
[env:esp8266_2m_compat]
|
||||
extends = env:esp8266_2m
|
||||
@@ -414,7 +411,6 @@ platform_packages = ${esp8266.platform_packages_compat}
|
||||
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP02_compat\" #-DWLED_DISABLE_2D
|
||||
-D WLED_DISABLE_PARTICLESYSTEM1D
|
||||
-D WLED_DISABLE_PARTICLESYSTEM2D
|
||||
custom_usermods = audioreactive
|
||||
|
||||
[env:esp8266_2m_160]
|
||||
extends = env:esp8266_2m
|
||||
|
||||
@@ -140,7 +140,7 @@ static uint8_t binNum = 8; // Used to select the bin for FFT based bea
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#define UM_AUDIOREACTIVE_USE_INTEGER_FFT // always use integer FFT on ESP32-S2 and ESP32-C3
|
||||
#endif
|
||||
#endif // UM_AUDIOREACTIVE_USE_ARDUINO_FFT
|
||||
#endif
|
||||
|
||||
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
|
||||
using FFTsampleType = float;
|
||||
@@ -758,8 +758,6 @@ class AudioReactive : public Usermod {
|
||||
private:
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
static constexpr uint8_t SR_DMTYPE_NETWORK_ONLY = 254;
|
||||
|
||||
#ifndef AUDIOPIN
|
||||
int8_t audioPin = -1;
|
||||
#else
|
||||
@@ -1441,7 +1439,7 @@ class AudioReactive : public Usermod {
|
||||
break;
|
||||
#endif
|
||||
|
||||
case SR_DMTYPE_NETWORK_ONLY: // dummy "network receive only" mode
|
||||
case 254: // dummy "network receive only" mode
|
||||
if (audioSource) delete audioSource; audioSource = nullptr;
|
||||
disableSoundProcessing = true;
|
||||
audioSyncEnabled = 2; // force udp sound receive mode
|
||||
@@ -1458,25 +1456,19 @@ class AudioReactive : public Usermod {
|
||||
}
|
||||
delay(250); // give microphone enough time to initialise
|
||||
|
||||
if (!audioSource && (dmType != SR_DMTYPE_NETWORK_ONLY)) enabled = false;// audio failed to initialise
|
||||
if (!audioSource && (dmType != 254)) enabled = false;// audio failed to initialise
|
||||
#endif
|
||||
if (enabled) onUpdateBegin(false); // create FFT task, and initialize network
|
||||
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (audioSource && FFT_Task == nullptr) enabled = false; // FFT task creation failed
|
||||
if (FFT_Task == nullptr) enabled = false; // FFT task creation failed
|
||||
if((!audioSource) || (!audioSource->isInitialized())) { // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync
|
||||
#ifdef WLED_DEBUG
|
||||
#define AR_INIT_DEBUG_PRINT DEBUG_PRINTLN
|
||||
#else
|
||||
#define AR_INIT_DEBUG_PRINT DEBUGSR_PRINTLN
|
||||
#endif
|
||||
if (dmType == SR_DMTYPE_NETWORK_ONLY) {
|
||||
AR_INIT_DEBUG_PRINT(F("AR: No sound input driver configured - network receive only."));
|
||||
} else {
|
||||
AR_INIT_DEBUG_PRINT(F("AR: Failed to initialize sound input driver. Please check input PIN settings."));
|
||||
}
|
||||
#undef AR_INIT_DEBUG_PRINT
|
||||
#ifdef WLED_DEBUG
|
||||
DEBUG_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings."));
|
||||
#else
|
||||
DEBUGSR_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings."));
|
||||
#endif
|
||||
disableSoundProcessing = true;
|
||||
}
|
||||
#endif
|
||||
@@ -2131,9 +2123,6 @@ class AudioReactive : public Usermod {
|
||||
uiScript.print(F("addOption(dd,'Generic PDM',5);"));
|
||||
#endif
|
||||
uiScript.print(F("addOption(dd,'ES8388',6);"));
|
||||
uiScript.print(F("addOption(dd,'None - network receive only',"));
|
||||
uiScript.print(SR_DMTYPE_NETWORK_ONLY);
|
||||
uiScript.print(F(");"));
|
||||
|
||||
uiScript.print(F("dd=addDropdown(ux,'config:AGC');"));
|
||||
uiScript.print(F("addOption(dd,'Off',0);"));
|
||||
|
||||
@@ -8730,7 +8730,7 @@ void mode_particleperlin(void) {
|
||||
|
||||
PartSys->update(); // update and render
|
||||
}
|
||||
static const char _data_FX_MODE_PARTICLEPERLIN[] PROGMEM = "PS Fuzzy Noise@Speed,Particles,Bounce,Friction,Scale,Cylinder,Smear,Collide;;!;2;pal=64,sx=50,ix=200,c1=130,c2=30,c3=5,o3=1";
|
||||
static const char _data_FX_MODE_PARTICLEPERLIN[] PROGMEM = "PS Fuzzy Noise@Speed,Particles,Bounce,Friction,Scale,Cylinder,Smear,Collide;;!;2;pal=64,sx=50,ix=200,c1=130,c2=30,c3=5";
|
||||
|
||||
/*
|
||||
Particle smashing down like meteors and exploding as they hit the ground, has many parameters to play with
|
||||
@@ -9836,6 +9836,7 @@ void mode_particleFireworks1D(void) {
|
||||
PartSys->setColorByPosition(false); // disable
|
||||
for (uint32_t e = 0; e < explosionsize; e++) { // emit explosion particles
|
||||
int idx = PartSys->sprayEmit(PartSys->sources[0]); // emit a particle
|
||||
if (idx < 0) break; // no more particles available
|
||||
if(SEGMENT.custom3 > 23) {
|
||||
if(SEGMENT.custom3 == 31) { // highest slider value
|
||||
PartSys->setColorByAge(SEGMENT.check1); // color by age if colorful mode is enabled
|
||||
@@ -9860,7 +9861,7 @@ void mode_particleFireworks1D(void) {
|
||||
PartSys->applyFriction(1); // apply friction to all particles
|
||||
|
||||
PartSys->update(); // update and render
|
||||
|
||||
|
||||
for (uint32_t i = 0; i < PartSys->usedParticles; i++) {
|
||||
if (PartSys->particles[i].ttl > 20) PartSys->particles[i].ttl -= 20; //ttl is linked to brightness, this allows to use higher brightness but still a short spark lifespan
|
||||
else PartSys->particles[i].ttl = 0;
|
||||
@@ -9910,7 +9911,7 @@ void mode_particleSparkler(void) {
|
||||
PartSys->sources[i].source.ttl = 400; // replenish its life (setting it perpetual uses more code)
|
||||
PartSys->sources[i].sat = SEGMENT.custom1; // color saturation
|
||||
if (SEGMENT.speed == 255) // random position at highest speed setting
|
||||
PartSys->sources[i].source.x = hw_random16(PartSys->maxX);
|
||||
PartSys->sources[i].source.x = hw_random(PartSys->maxX);
|
||||
else
|
||||
PartSys->particleMoveUpdate(PartSys->sources[i].source, PartSys->sources[i].sourceFlags, &sparklersettings); //move sparkler
|
||||
}
|
||||
|
||||
@@ -71,29 +71,50 @@ void WS2812FX::setUpMatrix() {
|
||||
// allowed values are: -1 (missing pixel/no LED attached), 0 (inactive/unused pixel), 1 (active/used pixel)
|
||||
char fileName[32]; strcpy_P(fileName, PSTR("/2d-gaps.json"));
|
||||
bool isFile = WLED_FS.exists(fileName);
|
||||
size_t gapSize = 0;
|
||||
int8_t *gapTable = nullptr;
|
||||
|
||||
if (isFile && requestJSONBufferLock(JSON_LOCK_LEDGAP)) {
|
||||
if (isFile) {
|
||||
DEBUG_PRINT(F("Reading LED gap from "));
|
||||
DEBUG_PRINTLN(fileName);
|
||||
// read the array into global JSON buffer
|
||||
if (readObjectFromFile(fileName, nullptr, pDoc)) {
|
||||
gapTable = static_cast<int8_t*>(p_malloc(matrixSize));
|
||||
if (gapTable) {
|
||||
// the array is similar to ledmap, except it has only 3 values:
|
||||
// -1 ... missing pixel (do not increase pixel count)
|
||||
// 0 ... inactive pixel (it does count, but should be mapped out (-1))
|
||||
// 1 ... active pixel (it will count and will be mapped)
|
||||
JsonArray map = pDoc->as<JsonArray>();
|
||||
gapSize = map.size();
|
||||
if (!map.isNull() && gapSize >= matrixSize) { // not an empty map
|
||||
gapTable = static_cast<int8_t*>(p_malloc(gapSize));
|
||||
if (gapTable) for (size_t i = 0; i < gapSize; i++) {
|
||||
gapTable[i] = constrain(map[i], -1, 1);
|
||||
// read entries directly from the file, one number at a time
|
||||
// (no JSON buffer / pDoc needed — the file is a plain JSON array)
|
||||
// follows the same parsing pattern used by deserializeMap() for ledmap.json
|
||||
size_t gapIdx = 0;
|
||||
File f = WLED_FS.open(fileName, "r");
|
||||
if (f) {
|
||||
f.find('['); // skip to start of array
|
||||
while (f.available() && gapIdx < matrixSize) {
|
||||
char number[8];
|
||||
size_t numRead = f.readBytesUntil(',', number, sizeof(number) - 1); // last entry reads up to ']' (no trailing comma)
|
||||
number[numRead] = 0;
|
||||
if (numRead > 0) {
|
||||
char *end = strchr(number, ']'); // check for end-of-array marker
|
||||
bool foundDigit = (end == nullptr); // no ']' means the whole token is a number
|
||||
if (end != nullptr) {
|
||||
// ']' present — only accept if a digit (or '-') appears before it
|
||||
for (int k = 0; &number[k] != end; k++) {
|
||||
if (number[k] >= '0' && number[k] <= '9') { foundDigit = true; break; }
|
||||
if (number[k] == '-') { foundDigit = true; break; }
|
||||
}
|
||||
}
|
||||
if (!foundDigit) break; // ']' with no number — array ended
|
||||
gapTable[gapIdx++] = constrain(atoi(number), -1, 1);
|
||||
} else break;
|
||||
}
|
||||
f.close();
|
||||
}
|
||||
if (gapIdx < matrixSize) { // file was too short or could not be read — discard
|
||||
p_free(gapTable);
|
||||
gapTable = nullptr;
|
||||
}
|
||||
}
|
||||
DEBUG_PRINTLN(F("Gaps loaded."));
|
||||
releaseJSONBufferLock();
|
||||
}
|
||||
|
||||
unsigned x, y, pix=0; //pixel
|
||||
|
||||
@@ -478,7 +478,7 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
|
||||
#define JSON_LOCK_SERVEJSON 17
|
||||
#define JSON_LOCK_NOTIFY 18
|
||||
#define JSON_LOCK_PRESET_NAME 19
|
||||
#define JSON_LOCK_LEDGAP 20
|
||||
// JSON_LOCK 20 formerly used for LEDGAP (now parsed without JSON buffer)
|
||||
#define JSON_LOCK_LEDMAP_ENUM 21
|
||||
#define JSON_LOCK_REMOTE 22
|
||||
|
||||
|
||||
@@ -761,6 +761,7 @@ void serializeInfo(JsonObject root)
|
||||
case REALTIME_MODE_ARTNET: root["lm"] = F("Art-Net"); break;
|
||||
case REALTIME_MODE_TPM2NET: root["lm"] = F("tpm2.net"); break;
|
||||
case REALTIME_MODE_DDP: root["lm"] = F("DDP"); break;
|
||||
case REALTIME_MODE_DMX: root["lm"] = F("DMX"); break;
|
||||
}
|
||||
|
||||
root[F("lip")] = realtimeIP[0] == 0 ? "" : realtimeIP.toString();
|
||||
@@ -1212,23 +1213,32 @@ static size_t writeJSONStringElement(uint8_t* dest, size_t maxLen, const char* s
|
||||
return 1 + n;
|
||||
}
|
||||
|
||||
// Generate a streamed JSON response for the mode data
|
||||
// This uses sendChunked to send the reply in blocks based on how much fit in the outbound
|
||||
// packet buffer, minimizing the required state (ie. just the next index to send). This
|
||||
// allows us to send an arbitrarily large response without using any significant amount of
|
||||
// memory (so no worries about buffer limits).
|
||||
void respondModeData(AsyncWebServerRequest* request) {
|
||||
// Generate a streamed JSON response for the mode data (namesOnly=false) or mode names
|
||||
// (namesOnly=true). This uses sendChunked to send the reply in blocks based on how much
|
||||
// fit in the outbound packet buffer, minimizing the required state (ie. just the next index
|
||||
// to send). This allows us to send an arbitrarily large response without using any
|
||||
// significant amount of memory (so no worries about buffer limits).
|
||||
void respondModeData(AsyncWebServerRequest* request, bool namesOnly = false) {
|
||||
size_t fx_index = 0;
|
||||
request->sendChunked(FPSTR(CONTENT_TYPE_JSON),
|
||||
[fx_index](uint8_t* data, size_t len, size_t) mutable {
|
||||
[fx_index, namesOnly](uint8_t* data, size_t len, size_t) mutable {
|
||||
size_t bytes_written = 0;
|
||||
char lineBuffer[256];
|
||||
while (fx_index < strip.getModeCount()) {
|
||||
strncpy_P(lineBuffer, strip.getModeData(fx_index), sizeof(lineBuffer)-1); // Copy to stack buffer for strchr
|
||||
if (lineBuffer[0] != 0) {
|
||||
lineBuffer[sizeof(lineBuffer)-1] = '\0'; // terminate string (only needed if strncpy filled the buffer)
|
||||
const char* dataPtr = strchr(lineBuffer,'@'); // Find '@', if there is one
|
||||
size_t mode_bytes = writeJSONStringElement(data, len, dataPtr ? dataPtr + 1 : "");
|
||||
char* dataPtr = strchr(lineBuffer,'@'); // Find '@', if there is one; non-const so namesOnly mode can truncate lineBuffer here
|
||||
const char* value;
|
||||
// namesOnly=true → emit the display name (everything before '@')
|
||||
// namesOnly=false → emit the fx-data string (everything after '@')
|
||||
if (namesOnly) {
|
||||
if (dataPtr) *dataPtr = '\0'; // truncate at '@' to get name only
|
||||
value = lineBuffer;
|
||||
} else {
|
||||
value = dataPtr ? dataPtr + 1 : ""; // everything after '@' is the fx data
|
||||
}
|
||||
size_t mode_bytes = writeJSONStringElement(data, len, value);
|
||||
if (mode_bytes == 0) break; // didn't fit; break loop and try again next packet
|
||||
if (fx_index == 0) *data = '[';
|
||||
data += mode_bytes;
|
||||
@@ -1275,7 +1285,7 @@ class LockedJsonResponse: public AsyncJsonResponse {
|
||||
void serveJson(AsyncWebServerRequest* request)
|
||||
{
|
||||
enum class json_target {
|
||||
all, state, info, state_info, nodes, effects, palettes, networks, config, pins
|
||||
all, state, info, state_info, nodes, palettes, networks, config, pins
|
||||
};
|
||||
json_target subJson = json_target::all;
|
||||
|
||||
@@ -1284,7 +1294,7 @@ void serveJson(AsyncWebServerRequest* request)
|
||||
else if (url.indexOf("info") > 0) subJson = json_target::info;
|
||||
else if (url.indexOf("si") > 0) subJson = json_target::state_info;
|
||||
else if (url.indexOf(F("nodes")) > 0) subJson = json_target::nodes;
|
||||
else if (url.indexOf(F("eff")) > 0) subJson = json_target::effects;
|
||||
else if (url.indexOf(F("eff")) > 0) { respondModeData(request, true); return; }
|
||||
else if (url.indexOf(F("palx")) > 0) subJson = json_target::palettes;
|
||||
else if (url.indexOf(F("fxda")) > 0) { respondModeData(request); return; }
|
||||
else if (url.indexOf(F("net")) > 0) subJson = json_target::networks;
|
||||
@@ -1311,7 +1321,7 @@ void serveJson(AsyncWebServerRequest* request)
|
||||
}
|
||||
// releaseJSONBufferLock() will be called when "response" is destroyed (from AsyncWebServer)
|
||||
// make sure you delete "response" if no "request->send(response);" is made
|
||||
LockedJsonResponse *response = new LockedJsonResponse(pDoc, subJson==json_target::effects); // will clear and convert JsonDocument into JsonArray if necessary
|
||||
LockedJsonResponse *response = new LockedJsonResponse(pDoc, false); // will clear JsonDocument
|
||||
|
||||
JsonVariant lDoc = response->getRoot();
|
||||
|
||||
@@ -1325,8 +1335,6 @@ void serveJson(AsyncWebServerRequest* request)
|
||||
serializeNodes(lDoc); break;
|
||||
case json_target::palettes:
|
||||
serializePalettes(lDoc, request->hasParam(F("page")) ? request->getParam(F("page"))->value().toInt() : 0); break;
|
||||
case json_target::effects:
|
||||
serializeModeNames(lDoc); break;
|
||||
case json_target::networks:
|
||||
serializeNetworks(lDoc); break;
|
||||
case json_target::config:
|
||||
|
||||
Reference in New Issue
Block a user