ESP32 Memory Leak?

#include <WiFi.h>
#include <LITTLEFS.h>
#include <ESPAsyncWebServer.h>
#include <WebSocketsServer.h>
#include <queue>
#include <vector>
#include <ArduinoJson.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <map>
#include <set>
#include <sstream>

#define FORMAT_LITTLEFS_IF_FAILED false
#define RXD2 16
#define TXD2 17

// Constants
const int http_port = 80;
const int led_pin = 2;

// Globals
AsyncWebServer server(http_port);
WebSocketsServer webSocket = WebSocketsServer(81);

// Gcode Queue
std::queue<char *> gcodeQueue;
bool waitingForOk = false;

/***********************************************************
   Functions
*/

// Callback: receiving any WebSocket message
void onWebSocketEvent(uint8_t client_num,
                      WStype_t type,
                      uint8_t *payload,
                      size_t length)
{

  // Figure out the type of WebSocket event
  switch (type)
  {

  // Client has disconnected
  case WStype_DISCONNECTED:
    Serial.printf("[%u] Disconnected!\n", client_num);
    if (client_num == 0)
    {
      digitalWrite(led_pin, LOW);
    }

    break;

  // New client has connected
  case WStype_CONNECTED:
  {
    IPAddress ip = webSocket.remoteIP(client_num);
    Serial.printf("[%u] Connection from ", client_num);
    Serial.println(ip.toString());
    if (client_num == 0)
    {
      // main client connected
      // Get the config.json file
      File configjson = LITTLEFS.open("/config/config.json", "r");
      if (!configjson)
      {
        Serial.println("Failed to open config.json");
        return;
      }

      // Parse the JSON file
      StaticJsonDocument<512> doc;
      DeserializationError error = deserializeJson(doc, configjson);

      // Close the file as soon as you're done with it
      configjson.close();

      if (error)
      {
        Serial.println("Failed to parse config.json");
        return;
      }

      // Get the "turnoffboardled" boolean from the config.json file
      bool turnoffboardled = doc["turnoffboardled"];

      if (!turnoffboardled)
      {
        // main client connected and "turnoffboardled" is false
        digitalWrite(led_pin, HIGH);
      }
    }
  }
  break;

  // For everything else: do nothing
  case WStype_TEXT:
    Serial.printf("[%u] Received text: %s\n", client_num, payload);
    if (strcmp((char *)payload, "LEDON") == 0)
    {
      digitalWrite(led_pin, HIGH);
    }
    else if (strcmp((char *)payload, "LEDOFF") == 0)
    {
      digitalWrite(led_pin, LOW);
    }
    break;
  case WStype_BIN:
  case WStype_ERROR:
  case WStype_FRAGMENT_TEXT_START:
  case WStype_FRAGMENT_BIN_START:
  case WStype_FRAGMENT:
  case WStype_FRAGMENT_FIN:
  default:
    break;
  }
}

// Callback: send 404 if requested file does not exist
void onPageNotFound(AsyncWebServerRequest *request)
{
  IPAddress remote_ip = request->client()->remoteIP();
  Serial.println("[" + remote_ip.toString() +
                 "] HTTP GET request of " + request->url());
  request->send(404, "text/plain", "Not found");
}

/***********************************************************
   Main
*/

void setup()
{

  // Set LED pin as output
  pinMode(led_pin, OUTPUT);

  // Start Serial port
  Serial.begin(115200);
  Serial2.begin(115200, SERIAL_8N1, RXD2, TXD2);

  // Make sure we can read the file system
  if (!LITTLEFS.begin(FORMAT_LITTLEFS_IF_FAILED))
  {
    Serial.println("Error mounting SPIFFS");
    return;
  }

  int tBytes = LITTLEFS.totalBytes();
  int uBytes = LITTLEFS.usedBytes();
  Serial.println("File system info");
  Serial.print("Total bytes: ");
  Serial.println(tBytes);
  Serial.print("Used bytes: ");
  Serial.println(uBytes);

  // Get the Network name and password
  File networkjson = LITTLEFS.open("/config/config.json", "r");
  if (!networkjson || networkjson.isDirectory())
  {
    Serial.println("Failed to open config.json");
    return;
  }

  // Parse the JSON file
  StaticJsonDocument<512> doc;
  DeserializationError error = deserializeJson(doc, networkjson);

  // Close the file as soon as you're done with it
  networkjson.close();

  if (error)
  {
    Serial.println("Failed to parse config.json");
    return;
  }

  // Get network name and password
  const char *networkname = doc["networkname"];
  const char *networkpassword = doc["networkpassword"];

  Serial.println("Network name: " + String(networkname));
  Serial.println("Network password: " + String(networkpassword));

  // Start access point
  WiFi.softAP(networkname, networkpassword);

  // Print our IP address
  Serial.println();
  Serial.println("AP running");
  Serial.print("My IP address: ");
  Serial.println(WiFi.softAPIP());

  server.serveStatic("/", LITTLEFS, "/");

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
            {
        AsyncWebServerResponse* response = request->beginResponse(LITTLEFS, "/index.html", "text/html");
        response->addHeader("Content-Encoding", "gzip");
        request->send(response); });

  server.on(
      "/upload", HTTP_POST, [](AsyncWebServerRequest *request)
      { request->send(200); },
      handleUpload);

  server.on(
      "/gcode", HTTP_POST, [](AsyncWebServerRequest *request)
      { request->send(200); },
      handleGcode);

  // Handle file delete
  server.on("/delete", HTTP_DELETE, [](AsyncWebServerRequest *request)
            {
    if (request->hasParam("filename")) { // if the request has a parameter named "filename"
        String filename = request->getParam("filename")->value(); // get the value of the "filename" parameter
        if (LITTLEFS.remove("/" + filename)) { // attempt to delete the file
            request->send(200, "text/plain", "Successfull deleted file: " + filename); // if successful, send a 200 OK response
            Serial.println("Deleted file: " + filename);
        } else {
            request->send(500, "text/plain", "Failed to delete file"); // if unsuccessful, send a 500 Internal Server Error response
        }
    } else {
        request->send(400, "text/plain", "Bad request"); // if the request does not have a "filename" parameter, send a 400 Bad Request response
    } });

  // Handle GET request for file list
  server.on("/files", HTTP_GET, [](AsyncWebServerRequest *request)
            {
    if (request->hasParam("directory")) {
        String directory = request->getParam("directory")->value();
        String fileList = "";
        File root = LITTLEFS.open(directory);

        if (root && root.isDirectory()) {
            File file = root.openNextFile();
            while (file) {
                String fileNameWithPath = file.name();
                String fileName = fileNameWithPath.substring(directory.length());
                fileList += fileName;
                fileList += "\n";
                file = root.openNextFile();
            }
        }

        Serial.println(fileList);
        request->send(200, "text/plain", fileList);
    } else {
        request->send(400, "text/plain", "Bad request");
    } });

  // Handle bytes check
  server.on("/space", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(200, "text/plain", String(LITTLEFS.totalBytes()) + "," + String(LITTLEFS.usedBytes())); });

  server.on("/writeinput", HTTP_POST, [](AsyncWebServerRequest *request)
            {
    if (request->hasParam("text", true)) {
        String text = request->getParam("text", true)->value();
        Serial.println("Text: " + text);
        TextToGCODE(text);
        request->send(200);
    } else {
        request->send(400);
    } });

  // Handle requests for pages that do not exist
  server.onNotFound(onPageNotFound);

  // Start web server
  server.begin();

  // Start WebSocket server and assign callback
  webSocket.begin();

  // enable heartbeat for websocket
  webSocket.enableHeartbeat(15000, 3000, 2);

  webSocket.onEvent(onWebSocketEvent);
}

#define RX_BUFFER_SIZE 128 // adjust this to your needs

int g_count = 0;
int l_count = 0;
int error_count = 0;
bool verbose = true;

int len = 0;

void loop()
{
  webSocket.loop();

  char *currentBlock = nullptr; // Declare currentBlock outside the if block

  if (!gcodeQueue.empty())
  {
    bool okReceived = false;
    currentBlock = gcodeQueue.front();
    gcodeQueue.pop();
    len = strlen(currentBlock) + 1;
    Serial.println("  LEN: " + String(len));

    while (len >= RX_BUFFER_SIZE - 1 || Serial2.available())
    {
      char out_temp[RX_BUFFER_SIZE];
      Serial2.readBytesUntil('\n', out_temp, RX_BUFFER_SIZE);
      if (strstr(out_temp, "ok") == NULL && strstr(out_temp, "error") == NULL && strstr(out_temp, "[Pgm End]") == NULL)
      {
        Serial.println("    MSG: \"" + String(out_temp) + "\""); // Debug response
      }
      else
      {
        if (!okReceived && (strstr(out_temp, "ok") != NULL || strstr(out_temp, "[Pgm End]") != NULL))
        {
          okReceived = true;
          g_count += 1;
          len -= strlen(currentBlock) + 1;
          if (verbose)
            Serial.println("  REC<" + String(g_count) + ": \"" + String(out_temp) + "\"");
          // If [Pgm End] is received, send "PE" over the websocket
          if (strstr(out_temp, "[Pgm End]") != NULL)
          {
            Serial.println("Program End");
            webSocket.broadcastTXT("PE");
          }
        }
        if (strstr(out_temp, "error") != NULL)
          error_count += 1;
      }
    }
    // Append newline character to currentBlock
    char currentBlockWithNewline[strlen(currentBlock) + 2];
    strcpy(currentBlockWithNewline, currentBlock);
    strcat(currentBlockWithNewline, "\n");

    // Convert currentBlockWithNewline to byte array and send it
    Serial2.write((const uint8_t *)currentBlockWithNewline, strlen(currentBlockWithNewline));
    if (verbose)
      Serial.println("SND>" + String(l_count) + ": \"" + String(currentBlock) + "\"");
    l_count += 1;

    // Yield to allow the ESP32 to handle the WebSocket events
    yield();
  }

  // Wait until all responses have been received.
  while (l_count > g_count)
  {
    bool okReceived = false;
    char out_temp[RX_BUFFER_SIZE];
    Serial2.readBytesUntil('\n', out_temp, RX_BUFFER_SIZE);
    if (strstr(out_temp, "ok") == NULL && strstr(out_temp, "error") == NULL && strstr(out_temp, "[Pgm End]") == NULL)
    {
      Serial.println("    MSG: \"" + String(out_temp) + "\""); // Debug response
    }
    else
    {
      if (!okReceived && (strstr(out_temp, "ok") != NULL || strstr(out_temp, "[Pgm End]") != NULL))
      {
        okReceived = true;
        g_count += 1;
        len -= strlen(currentBlock) + 1;
        if (verbose)
          Serial.println("  REC<" + String(g_count) + ": \"" + String(out_temp) + "\"");

        // If [Pgm End] is received, send "PE" over the websocket
        if (strstr(out_temp, "[Pgm End]") != NULL)
        {
          Serial.println("Program End");
          webSocket.broadcastTXT("PE");
        }
      }
      if (strstr(out_temp, "error") != NULL)
        error_count += 1;
    }
    // Yield to allow the ESP32 to handle the WebSocket events
    yield();
  }
}

void ReceivedGCodeData(uint8_t *payload, size_t length)
{
  // Tokenize the payload into lines
  char *token;
  char *saveptr;
  token = strtok_r((char *)payload, ",", &saveptr);
  while (token != NULL)
  {
    byte mode = 0;
    if (strcmp(token, "M7") == 0)
    {
      mode = 1;
    }
    if (strcmp(token, "M8") == 0)
    {
      mode = 1;
    }
    if (strcmp(token, "M2") == 0)
    {
      mode = 1;
    }
    if (strncmp(token, "G1", 2) == 0)
    {
      mode = 1;
    }
    if (strncmp(token, "G0", 2) == 0)
    {
      mode = 1;
    }
    if (strcmp(token, "G21") == 0)
    {
      mode = 1;
    }
    if (strcmp(token, "G90") == 0)
    {
      mode = 1;
    }
    if (strncmp(token, "G92", 3) == 0)
    {
      mode = 1;
    }
    if (strcmp(token, "F1250") == 0)
    {
      mode = 1;
    }
    if (mode == 1)
    {
      char *gcodeLine = new char[20];
      strcpy(gcodeLine, token);
      gcodeQueue.push(gcodeLine);
    }
    token = strtok_r(NULL, ",", &saveptr);
  }
  // Flush the Serial2 buffer
  if (gcodeQueue.empty())
  {
    serialFlush();
  }
}

void serialFlush()
{
  while (Serial2.available() > 0)
  {
    char t = Serial2.read();
  }
}

// handles uploads
void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final)
{
  String logmessage = "Client:" + request->client()->remoteIP().toString() + " " + request->url();
  Serial.println(logmessage);

  if (!index)
  {
    logmessage = "Upload Start: " + String(filename);
    // open the file on first call and store the file handle in the request object
    request->_tempFile = LITTLEFS.open("/" + filename, "w");
    Serial.println(logmessage);
  }

  if (len)
  {
    // stream the incoming chunk to the opened file
    request->_tempFile.write(data, len);
    logmessage = "Writing file: " + String(filename) + " index=" + String(index) + " len=" + String(len);
    Serial.println(logmessage);
  }

  if (final)
  {
    logmessage = "Upload Complete: " + String(filename) + ",size: " + String(index + len);

    // close the file handle as the upload is now done
    request->_tempFile.close();
    Serial.println(logmessage);
  }
}

// handles gcode file uploads
void handleGcode(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final)
{
  static std::vector<uint8_t> buffer;

  if (!index)
  {
    Serial.println("Gcode received");

    // First check if gcodequeue is empty and if yes wake up grbl
    if (gcodeQueue.empty())
    {
      Serial.println("Queue is empty");
      const char *message = "\r\n\r\n";
      Serial2.write((const uint8_t *)message, strlen(message));
    }
    // Clear the buffer
    buffer.clear();
  }

  if (len)
  {
    // Append the incoming data to the buffer
    buffer.insert(buffer.end(), data, data + len);
  }

  if (final)
  {
    Serial.println("Gcode upload complete");

    // Send the buffer to ReceivedGCodeData
    ReceivedGCodeData(buffer.data(), buffer.size());

    // Clear the buffer
    buffer.clear();
  }
}

void TextToGCODE(String text)
{

  // Get the config variables from the config.json
  File configjson = LITTLEFS.open("/config/config.json", "r");
  if (!configjson || configjson.isDirectory())
  {
    Serial.println("Failed to open config.json");
    return;
  }

  // Parse the JSON file
  StaticJsonDocument<512> doc;
  DeserializationError error = deserializeJson(doc, configjson);

  // Close the file as soon as you're done with it
  configjson.close();

  if (error)
  {
    Serial.println("Failed to parse config.json");
    return;
  }

  // Declare the variables

  float lineSpacing;
  float lineLength;

  bool restatreservoir = doc["restatreservoir"];
  int linesselected = doc["linesselected"];
  bool usecustomchars = doc["usecustomchars"];
  bool useverticalwriting = doc["useverticalwriting"];
  bool fulldrawingarea = doc["fulldrawingarea"];
  String quality = doc["quality"];
  float qualityfactor = 0;
  if (quality == "low")
  {
    qualityfactor = 0.6;
  }
  else if (quality == "medium")
  {
    qualityfactor = 0.4;
  }
  else if (quality == "high")
  {
    qualityfactor = 0.2;
  }
  if (!useverticalwriting)
  {
    lineLength = 40;
  }
  else if (useverticalwriting && !fulldrawingarea)
  {
    lineLength = 29.5;
  }
  else if (useverticalwriting && fulldrawingarea)
  {
    lineLength = 39.5;
  }

  if (!useverticalwriting && !fulldrawingarea)
  {
    switch (linesselected)
    {
    case 1:
      lineSpacing = 0;
      break;
    case 2:
      lineSpacing = 15;
      break;
    case 3:
      lineSpacing = 10;
      break;
    case 4:
      lineSpacing = 7.5;
      break;
    default:
      lineSpacing = 0;
      break;
    }
  }
  else if (useverticalwriting && !fulldrawingarea)
  {
    switch (linesselected)
    {
    case 1:
      lineSpacing = 0;
      break;
    case 2:
      lineSpacing = 17;
      break;
    case 3:
      lineSpacing = 12;
      break;
    case 4:
      lineSpacing = 9;
      break;
    default:
      lineSpacing = 0;
      break;
    }
  }
  else if (!useverticalwriting && fulldrawingarea)
  {
    switch (linesselected)
    {
    case 1:
      lineSpacing = 0;
      break;
    case 2:
      lineSpacing = 18;
      break;
    case 3:
      lineSpacing = 12;
      break;
    case 4:
      lineSpacing = 9;
      break;
    default:
      lineSpacing = 0;
      break;
    }
  }
  else if (useverticalwriting && fulldrawingarea)
  {
    switch (linesselected)
    {
    case 1:
      lineSpacing = 0;
      break;
    case 2:
      lineSpacing = 18;
      break;
    case 3:
      lineSpacing = 12;
      break;
    case 4:
      lineSpacing = 9.33;
      break;
    default:
      lineSpacing = 0;
      break;
    }
  }
  Serial.println("GCode Dir: chars/default");
  Serial.println("Line Length: " + String(lineLength));
  Serial.println("Line Spacing: " + String(lineSpacing));

  // Pass this to the mainwrite function
  mainwrite(text, "chars/default", lineLength, lineSpacing, linesselected, useverticalwriting, fulldrawingarea, usecustomchars, qualityfactor, restatreservoir);
}
class Instr
{
public:
  std::string command;
  float x, y;

  Instr(std::string command, float x = NAN, float y = NAN) : command(command), x(x), y(y) {}

  Instr translated(float dx, float dy, bool useverticalwriting)
  {
    if (!isnan(x) && !isnan(y))
    {
      float newX = x + dx;
      float newY = y + dy;
      if (useverticalwriting)
      {
        // Rotate 90 degrees to the left
        float temp = newX;
        newX = newY;
        newY = -temp;
      }
      return Instr(command, newX, newY);
    }
    return Instr(command);
  }
};

class Letter
{
public:
  std::vector<Instr> instructions; // Declare instructions field
  float width;                     // Declare width field

  Letter() : width(0) {} // Default constructor
  Letter(std::vector<Instr> instructions, float width) : instructions(instructions), width(width) {}

  Letter translated(float dx, float dy, bool useverticalwriting)
  {
    std::vector<Instr> newInstructions;
    for (auto &instr : instructions)
    {
      newInstructions.push_back(instr.translated(dx, dy, useverticalwriting));
    }
    return Letter(newInstructions, width);
  }

  Letter scaled(float scalingFactor)
  {
    // Maximum scaling factor (only downscale, not upscale)
    const float maxScalingFactor = 1.0;

    // If the scaling factor is greater than the maximum, set it to the maximum
    if (scalingFactor > maxScalingFactor)
      scalingFactor = maxScalingFactor;

    std::vector<Instr> newInstructions;
    for (auto &instr : instructions)
    {
      newInstructions.push_back(Instr(instr.command, instr.x * scalingFactor, instr.y));
    }
    return Letter(newInstructions, width * scalingFactor);
  }
};

float getScaleFactor(int linesselected, bool useverticalwriting, bool fulldrawingarea)
{
  float scaleFactor;

  if (linesselected == 1)
  {
    if (fulldrawingarea)
    {
      scaleFactor = useverticalwriting ? 4.0 : 3.9;
    }
    else
    {
      scaleFactor = useverticalwriting ? 4.0 : 3.2;
    }
  }
  else if (linesselected == 2)
  {
    if (fulldrawingarea)
    {
      scaleFactor = useverticalwriting ? 2.2 : 2.1;
    }
    else
    {
      scaleFactor = useverticalwriting ? 2.3 : 1.75;
    }
  }
  else if (linesselected == 3)
  {
    if (fulldrawingarea)
    {
      scaleFactor = useverticalwriting ? 1.6 : 1.5;
    }
    else
    {
      scaleFactor = useverticalwriting ? 1.5 : 1.25;
    }
  }
  else if (linesselected == 4)
  {
    if (fulldrawingarea)
    {
      scaleFactor = useverticalwriting ? 1.2 : 1.2;
    }
    else
    {
      scaleFactor = useverticalwriting ? 1.2 : 0.9;
    }
  }

  return scaleFactor;
}

String getRandomVariant(String character, std::vector<String> allchars)
{
  std::vector<String> variants;
  for (const auto &item : allchars)
  {
    if (item.indexOf("/Variante") != -1 &&
        item.indexOf("=" + character + ".gcode") != -1)
    {
      variants.push_back(item);
    }
  }
  if (!variants.empty())
  {
    return variants[rand() % variants.size()];
  }
  return "";
}

std::map<String, Letter> readLetters(String directory, String text, int linesselected, bool useverticalwriting, bool fulldrawingarea, bool usecustomchars)
{
  std::map<String, Letter> letters;
  std::set<String> processedChars; // Keep track of processed characters
  Serial.println("Reading Letters for: " + text);

  // Get all chars
  std::vector<String> allchars;
  if (usecustomchars)
  {
    File root = LITTLEFS.open("/chars/custom");

    if (root && root.isDirectory())
    {
      File file = root.openNextFile();
      while (file)
      {
        String fileNameWithPath = file.name();
        String fileName = fileNameWithPath.substring(13);
        if (fileName.endsWith(".gcode"))
        {
          allchars.push_back(fileName);
        }
        file = root.openNextFile();
      }
    }

    Serial.println("All chars:");
    for (auto &c : allchars)
    {
      Serial.println(c);
    }
  }

  for (unsigned int i = 0; i < text.length(); i += 8)
  {
    String c = text.substring(i, i + 8);

    // Skip if character has already been processed
    if (processedChars.find(c) != processedChars.end())
    {
      continue;
    }
    // Add character to processed set
    processedChars.insert(c);

    Serial.printf("Character: %s", c.c_str());
    Serial.println();

    // Handle new line character
    if (c == "0a000000")
    {
      continue;
    }

    // Handle white space
    if (c == "20000000")
    {
      // Create a new Letter with a width of 5 and add it to the letters map
      letters[c] = Letter(std::vector<Instr>(), 5);
      continue;
    }

    String path = "/" + directory + "/" + c + ".gcode";

    // Handle custom characters
    if (usecustomchars)
    {
      // Get a random variant
      String variant = getRandomVariant(c, allchars);
      if (variant.length() == 0)
      {
        path = "/" + directory + "/" + c + ".gcode";
      }
      else
      {
        path = "/chars/custom" + variant;
      }
    }

    Serial.println(path);

    if (!LITTLEFS.exists(path))
      continue;

    File file = LITTLEFS.open(path, "r");
    if (!file)
      continue;

    std::vector<Instr> instructions;
    float scaleFactor = getScaleFactor(linesselected, useverticalwriting, fulldrawingarea);
    while (file.available())
    {
      const char *line = file.readStringUntil('\n').c_str();
      std::istringstream iss(line);
      std::string segment;
      while (std::getline(iss, segment, ','))
      {
        std::istringstream segmentStream(segment);
        std::string command;
        float x = 0.0, y = 0.0;

        // Extract command
        segmentStream >> command;

        if (command == "M7" || command == "M8")
        {
          instructions.push_back(Instr(command));
          continue;
        }

        // Extract X and Y values
        std::string temp;
        while (segmentStream >> temp)
        {
          if (temp[0] == 'X')
            x = atof(temp.substr(1).c_str()); // Convert string to float
          else if (temp[0] == 'Y')
            y = atof(temp.substr(1).c_str()); // Convert string to float
        }

        instructions.push_back(Instr(command, x * scaleFactor, y * scaleFactor));
      }
    }

    file.close();

    float width = 0;
    for (auto &instr : instructions)
    {
      if (instr.x > width)
        width = instr.x;
    }

    letters[c] = Letter(instructions, width);
  }

  return letters;
}

int currentline = 1;

std::vector<Instr> textToGcode(String text, std::map<String, Letter> &letters, float lineLength, float lineSpacing, bool useverticalwriting)
{
  // Calculate total width of characters
  float totalWidth = 0;
  for (unsigned int i = 0; i < text.length(); i += 8)
  {
    String s = text.substring(i, i + 8);
    if (letters.find(s) != letters.end())
    {
      totalWidth += letters[s].width;
    }
  }
  Serial.println("Total Width: " + String(totalWidth));

  // Calculate starting x-coordinate for center alignment
  float startX = 0;
  if (totalWidth < lineLength)
  {
    startX = (lineLength - totalWidth) / 2;
  }

  std::vector<Instr> gcode;
  float x = startX; // Start from startX
  float y = 0;      // Apply padding on top

  // Calculate scaling factor
  float scalingFactor = lineLength / totalWidth;

  currentline = 1;

  for (unsigned int i = 0; i < text.length(); i += 8)
  {
    String s = text.substring(i, i + 8);
    if (s == "0a000000" || x > lineLength)
    {
      currentline++;
      x = startX;       // Reset x-coordinate for new line
      y += lineSpacing; // Apply padding on bottom
    }
    if (letters.find(s) != letters.end())
    {
      Letter letter = letters[s].scaled(scalingFactor).translated(x, y, useverticalwriting);

      gcode.insert(gcode.end(), letter.instructions.begin(), letter.instructions.end());
      x += letter.width; // Adjust x-coordinate by scaled width
    }
  }
  return gcode;
}

std::vector<Instr> compressGcode(std::vector<Instr> &gcode, float scalingquality = 0)
{
  std::vector<Instr> compressedGcode;
  float lastX = NAN, lastY = NAN;

  for (const auto &instr : gcode)
  {
    if (instr.command == "G1" && !isnan(instr.x) && !isnan(instr.y))
    {
      if (!isnan(lastX) && !isnan(lastY))
      {
        if (abs(instr.x - lastX) < scalingquality && abs(instr.y - lastY) < scalingquality)
        {
          // Skip this command
          continue;
        }
      }

      lastX = instr.x;
      lastY = instr.y;
    }

    compressedGcode.push_back(instr);
  }

  return compressedGcode;
}

void mainwrite(String text, String gcodedir, float lineLength, float lineSpacing, int linesselected, bool useverticalwriting, bool fulldrawingarea, bool usecustomchars, float qualityfactor, bool restatreservoir)
{

  // Wakeup grbl
  if (gcodeQueue.empty())
  {
    const char *message = "\r\n\r\n";
    Serial2.write((const uint8_t *)message, strlen(message));
  }

  // Get the letters
  std::map<String, Letter> letters = readLetters(gcodedir, text, linesselected, useverticalwriting, fulldrawingarea, usecustomchars);
  // Convert text to gcode
  std::vector<Instr> gcode = textToGcode(text, letters, lineLength, lineSpacing, useverticalwriting);

  if (gcode.empty())
  {
    Serial.println("No gcode generated");
    return;
  }

  // Compress the gcode with the quality factor
  gcode = compressGcode(gcode, qualityfactor);

  // Create a new Instr for each command
  Instr instrG21 = {"G21", NAN, NAN};
  Instr instrG90 = {"G90", NAN, NAN};
  Instr instrF1250 = {"F1250", NAN, NAN};
  Instr instrM8 = {"M8", NAN, NAN};
  Instr instrG92 = {"G92", NAN, NAN};

  // Insert the new instructions at the beginning of the gcode vector
  if (useverticalwriting && !restatreservoir && fulldrawingarea)
  {
    instrG92.x = 0;
    instrG92.y = -39.5;
    gcode.insert(gcode.begin(), {instrG21, instrG90, instrF1250, instrM8, instrG92});
  }
  else if (useverticalwriting && !fulldrawingarea && !restatreservoir)
  {
    instrG92.x = 0;
    instrG92.y = -29.5;
    gcode.insert(gcode.begin(), {instrG21, instrG90, instrF1250, instrM8, instrG92});
  }
  else if (!useverticalwriting && !restatreservoir)
  {
    instrG92.x = 0;
    instrG92.y = 0;
    gcode.insert(gcode.begin(), {instrG21, instrG90, instrF1250, instrM8, instrG92});
  }
  else if (useverticalwriting && !fulldrawingarea && restatreservoir)
  {
    instrG92.x = 0;
    instrG92.y = 10;
    gcode.insert(gcode.begin(), {instrG21, instrG90, instrF1250, instrM8, instrG92});
  }
  else if (!useverticalwriting && restatreservoir)
  {
    instrG92.x = 0;
    instrG92.y = 39.5;
    gcode.insert(gcode.begin(), {instrG21, instrG90, instrF1250, instrM8, instrG92});
  }

  Instr instrM2 = {"M2", NAN, NAN};
  Instr instrG0 = {"G0", NAN, NAN};
  Instr instrM7 = {"M7", NAN, NAN};

  // Add M2 to the end of the gcode vector
  if (currentline == linesselected)
  {
    gcode.push_back(instrM2);
  }

  // Add the appropriate G0 and M7 instructions based on the conditions
  if (useverticalwriting && !restatreservoir && fulldrawingarea)
  {
    instrG0.x = 0;
    instrG0.y = -39.5;
    gcode.push_back(instrG0);
  }
  else if (useverticalwriting && !fulldrawingarea && !restatreservoir)
  {
    instrG0.x = 0;
    instrG0.y = -29.5;
    gcode.push_back(instrG0);
  }
  else if (!useverticalwriting && !restatreservoir)
  {
    instrG0.x = 0;
    instrG0.y = 0;
    gcode.push_back(instrG0);
  }
  else if (useverticalwriting && !fulldrawingarea && restatreservoir)
  {
    instrG0.x = 0;
    instrG0.y = -29.5;
    gcode.push_back(instrG0);
    instrG0.y = 10;
    gcode.push_back(instrG0);
    gcode.push_back(instrM7);
  }
  else if (!useverticalwriting && restatreservoir)
  {
    instrG0.x = 0;
    instrG0.y = 0;
    gcode.push_back(instrG0);
    instrG0.y = 39.5;
    gcode.push_back(instrG0);
    gcode.push_back(instrM7);
  }

  // Flush the Serial2 buffer
  if (gcodeQueue.empty())
  {
    serialFlush();
  }

  // Print the gcode
  /*for (const auto &instr : gcode)
  {
    Serial.printf("%s X%.2f Y%.2f\n", instr.command.c_str(), instr.x, instr.y);
  }*/

  // Send the gcode to the queue
  for (const auto &instr : gcode)
  {
    char *gcodeLine;
    if (instr.command == "M7" || instr.command == "M8" || instr.command == "M2" || instr.command == "G21" || instr.command == "G90" || instr.command == "F1250")
    {
      gcodeLine = new char[instr.command.length() + 1];
      sprintf(gcodeLine, "%s", instr.command.c_str());
    }
    else
    {
      gcodeLine = new char[20];
      sprintf(gcodeLine, "%s X%.2f Y%.2f", instr.command.c_str(), instr.x, instr.y);
    }
    gcodeQueue.push(gcodeLine);
  }
}

Hallo das ist mein ESP32 Code oftmals Crasht der ESP32 beim Schreiben ich weiß nicht warum, also in Writeinput kommt ein Text an nähmlich in einem UTF32 Format bedeutet jede 8 Charakter ist quasi ein Buchstabe danach lese ich jeden Buchstaben aus dem Spiffs und Skaliere den GCode optimal hier sollte aber das Problem nicht liegen jetzt zum senden also ich habe eine Globale Variable gcodeQueue da pushe ich immer meine Gcodes wenn ich das nicht mache passiert auch nie was mit dem ESP32 also das Problem liegt beim Senden ist das ein Memory Leak oder was könnte das sein?

Dann solltest du die Bootmeldungen untersuchen.

Fakten zählen.

abort() was called at PC 0x40158c63 on core 1

ELF file SHA256: 0000000000000000

Backtrace: 0x4008870c:0x3ffd2270 0x40088989:0x3ffd2290 0x40158c63:0x3ffd22b0 0x40158caa:0x3ffd22d0 0x40158383:0x3ffd22f0 0x401586ba:0x3ffd2310 0x400d163b:0x3ffd2330 0x400d3795:0x3ffd2350 0x400d3ab9:0x3ffd2380 0x400d6b05:0x3ffd2450 0x400d78f6:0x3ffd29f0 0x400d7afa:0x3ffd2b10 0x400e4ca9:0x3ffd2b70 0x400e2879:0x3ffd2bc0 0x400e2905:0x3ffd2c10 0x400e5d55:0x3ffd2c30 0x400e5de9:0x3ffd2c70 0x400e63fa:0x3ffd2c90 0x4008999a:0x3ffd2cc0

Rebooting...
ets Jul 29 2019 12:21:46

rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1216
ho 0 tail 12 room 4
load:0x40078000,len:10944
load:0x40080400,len:6388
entry 0x400806b4 so sieht der boot aus

Du arbeitest viel mit a) String und auch mit b) new, zu dem ich kein delete finde.
Beides sorgt mit Sicherheit a) für Fragmentierung des RAM und b) für Memory Leaks.

Also ja, Du zerlegst Deinen RAM.

Gruß Tommy

Immerhin ein kontrolliertes Ende

Der Exception Decoder könnte dir die Stelle verraten, wo das passiert.

Ja! (schön wachsam gewesen)

Man nehme locale Variablen, wo es geht oder verwende RAII fähige Instanzen, wie z.B. String. Und wenn schon Pointer, dann doch besser "smart Pointer"

Danke für eure schnelle Antworten denkt ihr es gibt leute die das Optimieren? Weil bin in diesem Thema nicht so wirklich der Profi :->

// Send the gcode to the queue
  for (const auto &instr : gcode)
  {
    char *gcodeLine;
    if (instr.command == "M7" || instr.command == "M8" || instr.command == "M2" || instr.command == "G21" || instr.command == "G90" || instr.command == "F1250")
    {
      gcodeLine = new char[instr.command.length() + 1];
      sprintf(gcodeLine, "%s", instr.command.c_str());
    }
    else
    {
      gcodeLine = new char[20];
      sprintf(gcodeLine, "%s X%.2f Y%.2f", instr.command.c_str(), instr.x, instr.y);
    }
    gcodeQueue.push(gcodeLine);

also mit new meint ihr gcodeLine = new char wie könnte ich den da eine besserung vornehmen?

Das kann man lernen. Mein Vorschlag:
String raus, mit char-Arrays arbeiten, an vielen Stellen machst Du das ja bereits.
Kein new oder definiert ein delete dazu gleich mit programmieren. Den Aufbau halt darauf anpassen.
Das habe ich früher in Delphi auch so gehandhabt. Wenn ich ein new geschrieben habe, habe ich gleich auch das dazu gehörende delete programmiert. Das hilft ungemein, weil man seine Struktur vorher überdenken muss.

Dann sollte es keine Memory Leaks mehr geben, die ihre Ursache in Deinem Code haben.

Gruß Tommy

void loop()
{
  webSocket.loop();

  char *currentBlock = nullptr; // Declare currentBlock outside the if block

  if (!gcodeQueue.empty())
  {
    bool okReceived = false;
    currentBlock = gcodeQueue.front();
    gcodeQueue.pop();
    len = strlen(currentBlock) + 1;
    Serial.println("  LEN: " + String(len));

    while (len >= RX_BUFFER_SIZE - 1 || Serial2.available())
    {
      char out_temp[RX_BUFFER_SIZE];
      Serial2.readBytesUntil('\n', out_temp, RX_BUFFER_SIZE);
      if (strstr(out_temp, "ok") == NULL && strstr(out_temp, "error") == NULL && strstr(out_temp, "[Pgm End]") == NULL)
      {
        Serial.println("    MSG: \"" + String(out_temp) + "\""); // Debug response
      }
      else
      {
        if (!okReceived && (strstr(out_temp, "ok") != NULL || strstr(out_temp, "[Pgm End]") != NULL))
        {
          okReceived = true;
          g_count += 1;
          len -= strlen(currentBlock) + 1;
          if (verbose)
            Serial.println("  REC<" + String(g_count) + ": \"" + String(out_temp) + "\"");
          // If [Pgm End] is received, send "PE" over the websocket
          if (strstr(out_temp, "[Pgm End]") != NULL)
          {
            Serial.println("Program End");
            webSocket.broadcastTXT("PE");
          }
        }
        if (strstr(out_temp, "error") != NULL)
          error_count += 1;
      }
    }
    // Append newline character to currentBlock
    char currentBlockWithNewline[strlen(currentBlock) + 2];
    strcpy(currentBlockWithNewline, currentBlock);
    strcat(currentBlockWithNewline, "\n");

    // Convert currentBlockWithNewline to byte array and send it
    Serial2.write((const uint8_t *)currentBlockWithNewline, strlen(currentBlockWithNewline));
    if (verbose)
      Serial.println("SND>" + String(l_count) + ": \"" + String(currentBlock) + "\"");
    l_count += 1;

    delete[] currentBlock; // Delete currentBlock

    // Yield to allow the ESP32 to handle the WebSocket events
    yield();
  }

  // Wait until all responses have been received.
  while (l_count > g_count)
  {
    bool okReceived = false;
    char out_temp[RX_BUFFER_SIZE];
    Serial2.readBytesUntil('\n', out_temp, RX_BUFFER_SIZE);
    if (strstr(out_temp, "ok") == NULL && strstr(out_temp, "error") == NULL && strstr(out_temp, "[Pgm End]") == NULL)
    {
      Serial.println("    MSG: \"" + String(out_temp) + "\""); // Debug response
    }
    else
    {
      if (!okReceived && (strstr(out_temp, "ok") != NULL || strstr(out_temp, "[Pgm End]") != NULL))
      {
        okReceived = true;
        g_count += 1;
        len -= strlen(currentBlock) + 1;
        if (verbose)
          Serial.println("  REC<" + String(g_count) + ": \"" + String(out_temp) + "\"");

        // If [Pgm End] is received, send "PE" over the websocket
        if (strstr(out_temp, "[Pgm End]") != NULL)
        {
          Serial.println("Program End");
          webSocket.broadcastTXT("PE");
        }
      }
      if (strstr(out_temp, "error") != NULL)
        error_count += 1;
    }
    // Yield to allow the ESP32 to handle the WebSocket events
    yield();
  }
}

Also so zum Beispiel?

Nö, da ist doch schon wieder ein unsinniger String drin. Gerade solche Sachen fragmentieren Deinen RAM.

Gruß Tommy

ich meine jetzt mit delete[] currentBlock; // Delete currentBlock

Da sehe ich das passende new nicht. Solche Fragmente bringen nichts.
Da sieht man den Zusammenhang von new und delete nicht.

Den +String(....)+ meine ich aber auch ernst

Gruß Tommy

// Send the gcode to the queue
  for (const auto &instr : gcode)
  {
    char *gcodeLine;
    if (instr.command == "M7" || instr.command == "M8" || instr.command == "M2" || instr.command == "G21" || instr.command == "G90" || instr.command == "F1250")
    {
      gcodeLine = new char[instr.command.length() + 1];
      sprintf(gcodeLine, "%s", instr.command.c_str());
    }
    else
    {
      gcodeLine = new char[20];
      sprintf(gcodeLine, "%s X%.2f Y%.2f", instr.command.c_str(), instr.x, instr.y);
    }
    gcodeQueue.push(gcodeLine);
  }

aber wenn ich doch nachdem ich den gcode da rein pushe delete mache dann lösche ich den gcode doch direkt wieder?

Ich wiederhole: RAII
Der ESP32 hat soviel Speicher, dass man sich um Fragmentierung eher weniger Sorgen machen muss.

Ja, dann ist der Weg wohl nicht so einfach.

Evtl.

  for (const auto &instr : gcode)
  {
    char *gcodeLine = nullptr;
    if (instr.command == "M7" || instr.command == "M8" || instr.command == "M2" || instr.command == "G21" || instr.command == "G90" || instr.command == "F1250")
    {
      gcodeLine = new char[instr.command.length() + 1];
      sprintf(gcodeLine, "%s", instr.command.c_str());
    }
    else
    {
      gcodeLine = new char[20];
      sprintf(gcodeLine, "%s X%.2f Y%.2f", instr.command.c_str(), instr.x, instr.y);
    }
    if (gcodeLine != nullptr) {  // weil Du ja auch keine leere Line pushen willst oder?
      gcodeQueue.push(gcodeLine);
      delete(gcodeLine);
   }
  }

Gruß Tommy

Also wenn ich es so mache kommt bei der GCodeQueue nichts an, ich kann das doch erst nur deleten wenn der abgearbeitet wurde?

Wenn push keine Meldung, wenn fertig gibt, wird es schwierig.
Gib mir mal einen Link zur Lib.

Gruß Tommy

https://en.cppreference.com/w/cpp/container/queue

Das hat keinen Returncode. Evtl. vorm delete ein kurzes delay einfügen oder Du musst das Ganze komplett anders aufbauen.
Ist Dir die maximale Länge der Zeile bekannt oder abzuschätzen?

Gruß Tommy

Die ist mir bekannt nie über 20 zeichen