@@ -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
11801165void serializeModeNames (JsonArray arr)
@@ -1191,6 +1176,48 @@ void serializeModeNames(JsonArray arr)
11911176 }
11921177}
11931178
1179+ // Generate a streamed JSON response for the mode data
1180+ // This uses sendChunked to send the reply in blocks based on how much fit in the outbound
1181+ // packet buffer, minimizing the required state (ie. just the next index to send). This
1182+ // allows us to send an arbitrarily large response without using any significant amount of
1183+ // memory (so no worries about buffer limits).
1184+ void respondModeData (AsyncWebServerRequest* request) {
1185+ size_t fx_index = 0 ;
1186+ request->sendChunked (FPSTR (CONTENT_TYPE_JSON),
1187+ [fx_index](uint8_t * data, size_t len, size_t ) mutable {
1188+ size_t bytes_written = 0 ;
1189+ char lineBuffer[256 ];
1190+ while (fx_index < strip.getModeCount () && (len > 5 )) {
1191+ strncpy_P (lineBuffer, strip.getModeData (fx_index), sizeof (lineBuffer)/sizeof (char )-1 ); // Copy to stack buffer for strchr
1192+ if (lineBuffer[0 ] != 0 ) {
1193+ lineBuffer[sizeof (lineBuffer)/sizeof (char )-1 ] = ' \0 ' ; // terminate string
1194+ char * dataPtr = strchr (lineBuffer,' @' ); // Find '@', if there is one
1195+ size_t mode_bytes;
1196+ if (dataPtr) {
1197+ mode_bytes = snprintf_P ((char *) data, len, PSTR (" ,\" %s\" " ), dataPtr + 1 );
1198+ if (mode_bytes > len) break ; // didn't fit; break loop and try again next packet
1199+ } else {
1200+ strncpy_P ((char *)data, PSTR (" ,\"\" " ), len);
1201+ mode_bytes = 3 ;
1202+ }
1203+ if (fx_index == 0 ) *data = ' [' ;
1204+ data += mode_bytes;
1205+ len -= mode_bytes;
1206+ bytes_written += mode_bytes;
1207+ }
1208+ ++fx_index;
1209+ }
1210+
1211+ if ((fx_index == strip.getModeCount ()) && (len >= 1 )) {
1212+ *data = ' ]' ;
1213+ ++bytes_written;
1214+ ++fx_index; // we're really done
1215+ }
1216+
1217+ return bytes_written;
1218+ });
1219+ }
1220+
11941221// Global buffer locking response helper class (to make sure lock is released when AsyncJsonResponse is destroyed)
11951222class LockedJsonResponse : public AsyncJsonResponse {
11961223 bool _holding_lock;
@@ -1218,7 +1245,7 @@ class LockedJsonResponse: public AsyncJsonResponse {
12181245void serveJson (AsyncWebServerRequest* request)
12191246{
12201247 enum class json_target {
1221- all, state, info, state_info, nodes, effects, palettes, fxdata, networks, config, pins
1248+ all, state, info, state_info, nodes, effects, palettes, networks, config, pins
12221249 };
12231250 json_target subJson = json_target::all;
12241251
@@ -1229,7 +1256,7 @@ void serveJson(AsyncWebServerRequest* request)
12291256 else if (url.indexOf (F (" nodes" )) > 0 ) subJson = json_target::nodes;
12301257 else if (url.indexOf (F (" eff" )) > 0 ) subJson = json_target::effects;
12311258 else if (url.indexOf (F (" palx" )) > 0 ) subJson = json_target::palettes;
1232- else if (url.indexOf (F (" fxda" )) > 0 ) subJson = json_target::fxdata;
1259+ else if (url.indexOf (F (" fxda" )) > 0 ) { respondModeData (request); return ; }
12331260 else if (url.indexOf (F (" net" )) > 0 ) subJson = json_target::networks;
12341261 else if (url.indexOf (F (" cfg" )) > 0 ) subJson = json_target::config;
12351262 else if (url.indexOf (F (" pins" )) > 0 ) subJson = json_target::pins;
@@ -1254,7 +1281,7 @@ void serveJson(AsyncWebServerRequest* request)
12541281 }
12551282 // releaseJSONBufferLock() will be called when "response" is destroyed (from AsyncWebServer)
12561283 // 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
1284+ LockedJsonResponse *response = new LockedJsonResponse (pDoc, subJson==json_target::effects); // will clear and convert JsonDocument into JsonArray if necessary
12581285
12591286 JsonVariant lDoc = response->getRoot ();
12601287
@@ -1270,8 +1297,6 @@ void serveJson(AsyncWebServerRequest* request)
12701297 serializePalettes (lDoc, request->hasParam (F (" page" )) ? request->getParam (F (" page" ))->value ().toInt () : 0 ); break ;
12711298 case json_target::effects:
12721299 serializeModeNames (lDoc); break ;
1273- case json_target::fxdata:
1274- serializeModeData (lDoc); break ;
12751300 case json_target::networks:
12761301 serializeNetworks (lDoc); break ;
12771302 case json_target::config:
0 commit comments