arraysjsonarduinoesp32embeddedwebserver

ESPAsyncWebServer serve large array from RAM


I am trying to serve a large float array with 8192 values from the ESP32 Heap with the ESPAsyncWebServer library for the ArduinoIDE. The µC is a ESP32 devkit c and I want to access the array with a browser. Here is the code for the array:

#include "AsyncJson.h"
#include "ArduinoJson.h"
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

#define SAMPLES 8192

static float * vReal;

void setup() {
  vReal = (float *) malloc(SAMPLES * sizeof(float));
  assert(vReal != NULL);
}

void loop() {
  //Put something into the array
  for (int i = 0; i < SAMPLES; i++)
  {
    vReal[i] = 1.1;
  }
}

At the moment I use the "ArduinoJson Basic Response" and send the large array in parts of 512 values. With 1024 values I get a stack overflow in task async_tcp, if I try to access the array with the browser, so I set it to 512 values. Here is the code for this:

server.on("/array1", HTTP_GET, [](AsyncWebServerRequest * request) {
AsyncResponseStream *response = request->beginResponseStream("application/json");
const size_t CAPACITY = JSON_ARRAY_SIZE(512); //Compute size of array
StaticJsonDocument<CAPACITY> vRealPart;
JsonArray array = vRealPart.to<JsonArray>();
for (int i = 0; i < 512; i++)
{
  vRealPart.add(vReal[i]);
}
serializeJson(vRealPart, *response); //Print to HTML
request->send(response);
});

I do this 16 times to serve the whole array. Later, I call the path's "/array1", "/array2", "/array3"... with JavaScript and parse the JSON. This is the output, if one of the the path's is called in a webbrowser:

[0.334593,0.427480,0.181299,0.066654,0.271184,0.356220,0.374454,0.235625,...]

This works so far for the most of the time, but I think it is very long-winded. It would be nice, if there is only one path with the whole array. It works with a static file from SPIFFS, for example:

server.serveStatic("/jsonArray1", SPIFFS, "/jsonArray1");

But it takes to much time to write the whole array to flash. Although the reading is realy fast.

I also tried the "ArduinoJson Advanced Response", but I could not get it to run with a JsonArray. Unfortunatly the examples on the GitHub page from ESPAsyncWebServer for ArduinoJson are deprecated, because they changed the syntax a bit in the new version (v6).

In a nutshell: What is the best way to serve such large arrays from the ESP32 Heap with the ESPAsyncWebServer library? The goal is to process the array later with JavaScript in a webbrowser.

Thanks for your help!

Bentiedem

PS: If it helps: I am doing a FFT with the library arduinoFFT from a motor current. Therefore I do a ADC and save the 16384 values from the analog to digital converter in an array. This array is passed to the FFT library. The output is an array with 8192 values. This result is in the heap and should be transferred to my webinterface to display the result. I want to keep the arrays in the RAM for speed. For every measurment you get a result array with 8192 values.


Solution

  • I finally found a solution now which uses the chunked response. Also ArduinoJson is not used any more. I had to shrink the buffer to get it stable (maxLen = maxLen >> 1;). The library gives me a wrong max. buffer length with maxLen. This could be bug.
    It is much more faster than my previous solution and runs without any crashes of the µC. An array with 16384 values gets transfered in 422 ms from the µC to the webbrowser in one piece. Here is the code for the server response:

    server.on("/array", HTTP_GET, [](AsyncWebServerRequest * request)
      {
        indexvReal = 0;
        AsyncWebServerResponse* response = request->beginChunkedResponse(contentType,
                                           [](uint8_t* buffer, size_t maxLen, size_t index)
        {
          maxLen = maxLen >> 1;
          size_t len = 0;
          if (indexvReal == 0)
          {
            len += sprintf(((char *)buffer), "[%g", vReal[indexvReal]);
            indexvReal++;
          }
          while (len < (maxLen - FLOATSIZE) && indexvReal < LEN(vReal))
          {
            len += sprintf(((char *)buffer + len), ",%g", vReal[indexvReal]);
            indexvReal++;
          }
          if (indexvReal == LEN(vReal))
          {
            len += sprintf(((char *)buffer + len), "]");
            indexvReal++;
          }
          return len;
        });
        request->send(response);
      });
    

    Here is a complete example for testing purposes:

    #include <WiFi.h>
    #include <AsyncTCP.h>
    #include <ESPAsyncWebServer.h>
    
    const char *ssid = "...";
    const char *password = "...";
    int counter_wifi = 0;
    AsyncWebServer server(80);
    
    #define LEN(arr) ((int) (sizeof (arr) / sizeof (arr)[0]))
    #define SAMPLES 16384
    static float vReal[SAMPLES]; //Stack
    uint16_t indexvReal = 0;
    
    #define FLOATSIZE 20
    const char *contentType = "application/json";
    //const char *contentType = "text/plain";
    
    void setup() {
      Serial.begin(115200);
      Serial.println();
      Serial.println();
      WiFi.begin(ssid, password);
      while (WiFi.status() != WL_CONNECTED)
      {
        delay(500);
        Serial.print(".");
        counter_wifi++;
        if (counter_wifi >= 10) { //after 5 seconds timeout - reset board (on unsucessful connection)
          ESP.restart();
        }
      }
      Serial.println("WiFi connected.");
      Serial.println("IP Address: ");
      Serial.println(WiFi.localIP());
    
      for (int i = 0; i < SAMPLES; i++)
      {
        vReal[i] = random(20) * 3.2357911;
      }
    
      server.on("/array", HTTP_GET, [](AsyncWebServerRequest * request)
      {
        indexvReal = 0;
        AsyncWebServerResponse* response = request->beginChunkedResponse(contentType,
                                           [](uint8_t* buffer, size_t maxLen, size_t index)
        {
          maxLen = maxLen >> 1;
          size_t len = 0;
          if (indexvReal == 0)
          {
            len += sprintf(((char *)buffer), "[%g", vReal[indexvReal]);
            indexvReal++;
          }
          while (len < (maxLen - FLOATSIZE) && indexvReal < LEN(vReal))
          {
            len += sprintf(((char *)buffer + len), ",%g", vReal[indexvReal]);
            indexvReal++;
          }
          if (indexvReal == LEN(vReal))
          {
            len += sprintf(((char *)buffer + len), "]");
            indexvReal++;
          }
          return len;
        });
        request->send(response);
      });
    
      DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
      server.begin();
      Serial.println("HTTP server started");
    }
    
    void loop() {
      // put your main code here, to run repeatedly:
    
    }