Skip to content

Commit 945896d

Browse files
authored
Merge pull request #5462 from willmmiles/16_x-unlimited-fxdata-size
Serialize fxdata without ArduinoJSON
2 parents 188beec + a3f7302 commit 945896d

2 files changed

Lines changed: 75 additions & 21 deletions

File tree

wled00/fcn_declare.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,6 @@ void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool
176176
void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false);
177177
void serializeInfo(JsonObject root);
178178
void serializeModeNames(JsonArray arr);
179-
void serializeModeData(JsonArray fxdata);
180179
void serializePins(JsonObject root);
181180
void serveJson(AsyncWebServerRequest* request);
182181
#ifdef WLED_ENABLE_JSONLIVE

wled00/json.cpp

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,21 +1160,6 @@ void serializePins(JsonObject root)
11601160
}
11611161
}
11621162

1163-
// deserializes mode data string into JsonArray
1164-
void serializeModeData(JsonArray fxdata)
1165-
{
1166-
char lineBuffer[256];
1167-
for (size_t i = 0; i < strip.getModeCount(); i++) {
1168-
strncpy_P(lineBuffer, strip.getModeData(i), sizeof(lineBuffer)/sizeof(char)-1);
1169-
lineBuffer[sizeof(lineBuffer)/sizeof(char)-1] = '\0'; // terminate string
1170-
if (lineBuffer[0] != 0) {
1171-
char* dataPtr = strchr(lineBuffer,'@');
1172-
if (dataPtr) fxdata.add(dataPtr+1);
1173-
else fxdata.add("");
1174-
}
1175-
}
1176-
}
1177-
11781163
// deserializes mode names string into JsonArray
11791164
// also removes effect data extensions (@...) from deserialised names
11801165
void serializeModeNames(JsonArray arr)
@@ -1191,6 +1176,78 @@ void serializeModeNames(JsonArray arr)
11911176
}
11921177
}
11931178

1179+
// Writes a JSON-escaped string (with surrounding quotes) into dest[0..maxLen-1].
1180+
// Returns bytes written, or 0 if the buffer was too small.
1181+
static size_t writeJSONString(uint8_t* dest, size_t maxLen, const char* src) {
1182+
size_t pos = 0;
1183+
1184+
auto emit = [&](char c) -> bool {
1185+
if (pos >= maxLen) return false;
1186+
dest[pos++] = (uint8_t)c;
1187+
return true;
1188+
};
1189+
1190+
if (!emit('"')) return 0;
1191+
1192+
for (const char* p = src; *p; ++p) {
1193+
char esc = ARDUINOJSON_NAMESPACE::EscapeSequence::escapeChar(*p);
1194+
if (esc) {
1195+
if (!emit('\\') || !emit(esc)) return 0;
1196+
} else {
1197+
if (!emit(*p)) return 0;
1198+
}
1199+
}
1200+
1201+
if (!emit('"')) return 0;
1202+
return pos;
1203+
}
1204+
1205+
// Writes ,"<escaped_src>" into dest[0..maxLen-1] (no null terminator).
1206+
// Returns bytes written, or 0 if the buffer was too small.
1207+
static size_t writeJSONStringElement(uint8_t* dest, size_t maxLen, const char* src) {
1208+
if (maxLen == 0) return 0;
1209+
dest[0] = ',';
1210+
size_t n = writeJSONString(dest + 1, maxLen - 1, src);
1211+
if (n == 0) return 0;
1212+
return 1 + n;
1213+
}
1214+
1215+
// Generate a streamed JSON response for the mode data
1216+
// This uses sendChunked to send the reply in blocks based on how much fit in the outbound
1217+
// packet buffer, minimizing the required state (ie. just the next index to send). This
1218+
// allows us to send an arbitrarily large response without using any significant amount of
1219+
// memory (so no worries about buffer limits).
1220+
void respondModeData(AsyncWebServerRequest* request) {
1221+
size_t fx_index = 0;
1222+
request->sendChunked(FPSTR(CONTENT_TYPE_JSON),
1223+
[fx_index](uint8_t* data, size_t len, size_t) mutable {
1224+
size_t bytes_written = 0;
1225+
char lineBuffer[256];
1226+
while (fx_index < strip.getModeCount()) {
1227+
strncpy_P(lineBuffer, strip.getModeData(fx_index), sizeof(lineBuffer)-1); // Copy to stack buffer for strchr
1228+
if (lineBuffer[0] != 0) {
1229+
lineBuffer[sizeof(lineBuffer)-1] = '\0'; // terminate string (only needed if strncpy filled the buffer)
1230+
const char* dataPtr = strchr(lineBuffer,'@'); // Find '@', if there is one
1231+
size_t mode_bytes = writeJSONStringElement(data, len, dataPtr ? dataPtr + 1 : "");
1232+
if (mode_bytes == 0) break; // didn't fit; break loop and try again next packet
1233+
if (fx_index == 0) *data = '[';
1234+
data += mode_bytes;
1235+
len -= mode_bytes;
1236+
bytes_written += mode_bytes;
1237+
}
1238+
++fx_index;
1239+
}
1240+
1241+
if ((fx_index == strip.getModeCount()) && (len >= 1)) {
1242+
*data = ']';
1243+
++bytes_written;
1244+
++fx_index; // we're really done
1245+
}
1246+
1247+
return bytes_written;
1248+
});
1249+
}
1250+
11941251
// Global buffer locking response helper class (to make sure lock is released when AsyncJsonResponse is destroyed)
11951252
class LockedJsonResponse: public AsyncJsonResponse {
11961253
bool _holding_lock;
@@ -1218,7 +1275,7 @@ class LockedJsonResponse: public AsyncJsonResponse {
12181275
void serveJson(AsyncWebServerRequest* request)
12191276
{
12201277
enum class json_target {
1221-
all, state, info, state_info, nodes, effects, palettes, fxdata, networks, config, pins
1278+
all, state, info, state_info, nodes, effects, palettes, networks, config, pins
12221279
};
12231280
json_target subJson = json_target::all;
12241281

@@ -1229,7 +1286,7 @@ void serveJson(AsyncWebServerRequest* request)
12291286
else if (url.indexOf(F("nodes")) > 0) subJson = json_target::nodes;
12301287
else if (url.indexOf(F("eff")) > 0) subJson = json_target::effects;
12311288
else if (url.indexOf(F("palx")) > 0) subJson = json_target::palettes;
1232-
else if (url.indexOf(F("fxda")) > 0) subJson = json_target::fxdata;
1289+
else if (url.indexOf(F("fxda")) > 0) { respondModeData(request); return; }
12331290
else if (url.indexOf(F("net")) > 0) subJson = json_target::networks;
12341291
else if (url.indexOf(F("cfg")) > 0) subJson = json_target::config;
12351292
else if (url.indexOf(F("pins")) > 0) subJson = json_target::pins;
@@ -1254,7 +1311,7 @@ void serveJson(AsyncWebServerRequest* request)
12541311
}
12551312
// releaseJSONBufferLock() will be called when "response" is destroyed (from AsyncWebServer)
12561313
// make sure you delete "response" if no "request->send(response);" is made
1257-
LockedJsonResponse *response = new LockedJsonResponse(pDoc, subJson==json_target::fxdata || subJson==json_target::effects); // will clear and convert JsonDocument into JsonArray if necessary
1314+
LockedJsonResponse *response = new LockedJsonResponse(pDoc, subJson==json_target::effects); // will clear and convert JsonDocument into JsonArray if necessary
12581315

12591316
JsonVariant lDoc = response->getRoot();
12601317

@@ -1270,8 +1327,6 @@ void serveJson(AsyncWebServerRequest* request)
12701327
serializePalettes(lDoc, request->hasParam(F("page")) ? request->getParam(F("page"))->value().toInt() : 0); break;
12711328
case json_target::effects:
12721329
serializeModeNames(lDoc); break;
1273-
case json_target::fxdata:
1274-
serializeModeData(lDoc); break;
12751330
case json_target::networks:
12761331
serializeNetworks(lDoc); break;
12771332
case json_target::config:

0 commit comments

Comments
 (0)