@@ -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,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)
11951252class LockedJsonResponse : public AsyncJsonResponse {
11961253 bool _holding_lock;
@@ -1218,7 +1275,7 @@ class LockedJsonResponse: public AsyncJsonResponse {
12181275void 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