#include "webui.h"

AsyncWebServer webServer(80);

const char *PARAM_MESSAGE = "message";

String processor(const String &var);
void WebserverPOST_Callback(AsyncWebServerRequest *request);
void WebserverNotFound_Callback(AsyncWebServerRequest *request);
void Webserver_Callback(AsyncWebServerRequest *request);
void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final);
void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final);
void WebServerEEJSON_Callback(AsyncWebServerRequest *request);
void GetFlashVersion(char *buff, size_t buff_size);

#ifdef FEATURE_ENABLE_WEBSOCKETS

AsyncWebSocket webSocket("/ws");

void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
void Websocket_HandleMessage(void *arg, uint8_t *data, size_t len);

#endif

void initWebUI()
{
  if (!LittleFS.begin())
  {
    Debug_pushMessage("An Error has occurred while mounting LittleFS\n");
    MaintainDTC(DTC_FLASHFS_ERROR, DTC_CRITICAL, true);
    return;
  }

  GetFlashVersion(globals.FlashVersion, sizeof(globals.FlashVersion));

  char buffer[6];
  snprintf(buffer, sizeof(buffer), "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor);
  if (strcmp(globals.FlashVersion, buffer))
  {
    MaintainDTC(DTC_FLASHFS_VERSION_ERROR, DTC_WARN, true);
  }

  MDNS.begin(globals.DeviceName);
  MDNS.addService("http", "tcp", 80);

#ifdef FEATURE_ENABLE_WEBSOCKETS
  webSocket.onEvent(WebsocketEvent_Callback);
  webServer.addHandler(&webSocket);
#endif

  webServer.serveStatic("/static/", LittleFS, "/static/").setCacheControl("max-age=360000");
  webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
               { request->redirect("/index.htm"); });
  webServer.onNotFound(WebserverNotFound_Callback);
  webServer.on("/index.htm", HTTP_GET, Webserver_Callback);
  webServer.on("/post.htm", HTTP_POST, WebserverPOST_Callback);
  webServer.on("/eejson", HTTP_GET, WebServerEEJSON_Callback);
  webServer.on(
      "/doUpdate", HTTP_POST, [](AsyncWebServerRequest *request) {}, WebserverFirmwareUpdate_Callback);
  webServer.on(
      "/eeRestore", HTTP_POST, [](AsyncWebServerRequest *request) {}, WebserverEERestore_Callback);

  webServer.begin();
}

void Webserver_Process()
{
#ifdef FEATURE_ENABLE_WEBSOCKETS
  webSocket.cleanupClients();
#endif
}

String processor(const String &var)
{
  if (var == "TANK_REMAIN_CAPACITY")
    return String((PersistenceData.tankRemain_microL / 10) / LubeConfig.tankCapacity_ml);
  if (var == "LUBE_DISTANCE_NORMAL")
    return String(LubeConfig.DistancePerLube_Default);
  if (var == "LUBE_DISTANCE_RAIN")
    return String(LubeConfig.DistancePerLube_Rain);
  if (var == "TANK_CAPACITY")
    return String(LubeConfig.tankCapacity_ml);
  if (var == "AMOUNT_PER_DOSE")
    return String(LubeConfig.amountPerDose_microL);
  if (var == "TANK_REMIND")
    return String(LubeConfig.TankRemindAtPercentage);
  if (var == "PULSE_PER_REV")
    return String(LubeConfig.PulsePerRevolution);
  if (var == "TIRE_WIDTH_MM")
    return String(LubeConfig.TireWidth_mm);
  if (var == "TIRE_RATIO")
    return String(LubeConfig.TireWidthHeight_Ratio);
  if (var == "RIM_DIAMETER")
    return String(LubeConfig.RimDiameter_Inch);
  if (var == "DISTANCE_PER_REV")
    return String(LubeConfig.DistancePerRevolution_mm);
  if (var == "BLEEDING_PULSES")
    return String(LubeConfig.BleedingPulses);
  if (var == "SPEED_SOURCE")
    return String(SpeedSourceString[LubeConfig.SpeedSource]);
  if (var == "GPS_BAUD")
#ifdef FEATURE_ENABLE_GPS
    return String(GPSBaudRateString[LubeConfig.GPSBaudRate]);
#else
    return "Feature N/A";
#endif
  if (var == "CAN_SOURCE")
#ifdef FEATURE_ENABLE_CAN
    return String(CANSourceString[LubeConfig.CANSource]);
#else
    return "Feature N/A";
#endif
  if (var == "LED_MODE_FLASH")
    return String(LubeConfig.LED_Mode_Flash);
  if (var == "LEDFLASHCHECKED")
    return String(LubeConfig.LED_Mode_Flash == true ? "checked" : "");
  if (var == "LED_MAX_BRIGHTNESS")
    return String(LubeConfig.LED_Max_Brightness);
  if (var == "LED_MIN_BRIGHTNESS")
    return String(LubeConfig.LED_Min_Brightness);
  if (var == "EEPROM_VERSION")
    return String(LubeConfig.EEPROM_Version);
  if (var == "CONFIG_CHECKSUM")
  {
    char buffer[7];
    sprintf(buffer, "0x%04X", LubeConfig.checksum);
    return String(buffer);
  }
  if (var == "WRITE_CYCLE_COUNT")
    return String(PersistenceData.writeCycleCounter);
  if (var == "PERSISTENCE_MARKER")
    return String(globals.eePersistanceAdress);
  if (var == "TANK_REMAIN_UL")
    return String(PersistenceData.tankRemain_microL);
  if (var == "TRAVEL_DISTANCE_HIGHRES")
    return String(PersistenceData.TravelDistance_highRes_mm);
  if (var == "ODOMETER")
    return String(PersistenceData.odometer);
  if (var == "ODOMETER_M")
    return String(PersistenceData.odometer_mm / 1000);
  if (var == "PERSISTANCE_CHECKSUM")
  {
    char buffer[7];
    sprintf(buffer, "0x%04X", PersistenceData.checksum);
    return String(buffer);
  }
  if (var == "SHOW_IMPULSE_SETTINGS")
    return LubeConfig.SpeedSource == SOURCE_IMPULSE ? "" : "hidden";
  if (var == "SHOW_CAN_SETTINGS")
#ifdef FEATURE_ENABLE_CAN
    return LubeConfig.SpeedSource == SOURCE_CAN ? "" : "hidden";
#else
    return "hidden";
#endif
  if (var == "SHOW_GPS_SETTINGS")
#ifdef FEATURE_ENABLE_GPS
    return LubeConfig.SpeedSource == SOURCE_GPS ? "" : "hidden";
#else
    return "hidden";
#endif
  if (var == "SHOW_DTC_TABLE")
    return globals.hasDTC ? "" : "hidden";

  if (var == "DTC_TABLE")
  {
    String temp = "";
    char buff_timestamp[16]; // Format: DD-hh:mm:ss:xxx

    for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++)
    {
      if (DTCStorage[i].Number < DTC_LAST_DTC)
      {
        sprintf(buff_timestamp, "%02d-%02d:%02d:%02d:%03d",
                DTCStorage[i].timestamp / 86400000,    // Days
                DTCStorage[i].timestamp / 360000 % 24, // Hours
                DTCStorage[i].timestamp / 60000 % 60,  // Minutes
                DTCStorage[i].timestamp / 1000 % 60,   // Seconds
                DTCStorage[i].timestamp % 1000);       // milliseconds

        temp = temp + "<tr data-dtc=" + String(DTCStorage[i].Number);
        temp = temp + " data-debugval=" + String(DTCStorage[i].debugVal) + "><td>" + String(buff_timestamp);
        temp = temp + "</td><td>" + String(DTCStorage[i].Number) + "</td><td>";
        temp = temp + "<img src=static/img/";
        switch (DTCStorage[i].severity)
        {
        case DTC_CRITICAL:
          temp = temp + "critical";
          break;
        case DTC_WARN:
          temp = temp + "warn";
          break;
        case DTC_INFO:
          temp = temp + "info";
          break;
        }
        temp = temp + ".png></td><td>";

        if (DTCStorage[i].active == DTC_ACTIVE)
          temp = temp + "active";
        else if (DTCStorage[i].active == DTC_PREVIOUS)
          temp = temp + "previous";
        else
          temp = temp + "none";

        temp = temp + "</td></tr>";
      }
    }
    return temp;
  }

  if (var == "SOURCE_SELECT_OPTIONS")
  {
    String temp;
    for (uint32_t i = 0; i < SpeedSourceString_Elements; i++)
    {
      String selected = LubeConfig.SpeedSource == i ? " selected " : "";
      temp = temp + "<option value=\"" + i + "\"" + selected + ">" + SpeedSourceString[i] + "</option>";
    }
    return temp;
  }

#ifdef FEATURE_ENABLE_CAN
  if (var == "CANSOURCE_SELECT_OPTIONS")
  {
    String temp;
    for (uint32_t i = 0; i < CANSourceString_Elements; i++)
    {
      String selected = LubeConfig.CANSource == i ? " selected " : "";
      temp = temp + "<option value=\"" + i + "\"" + selected + ">" + CANSourceString[i] + "</option>";
    }
    return temp;
  }
#endif
#ifdef FEATURE_EABLE_GPS
  if (var == "GPSBAUD_SELECT_OPTIONS")
  {
    String temp;
    for (uint32_t i = 0; i < GPSBaudRateString_Elements; i++)
    {
      String selected = LubeConfig.GPSBaudRate == i ? " selected " : "";
      temp = temp + "<option value=\"" + i + "\"" + selected + ">" + GPSBaudRateString[i] + "</option>";
    }
    return temp;
  }
#endif

  if (var == "SYSTEM_STATUS")
    return String(globals.systemStatustxt);

  if (var == "SW_VERSION")
  {
    char buffer[6];
    snprintf(buffer, sizeof(buffer), "%d.%02d", constants.FW_Version_major, constants.FW_Version_minor);
    return String(buffer);
  }

  if (var == "FS_VERSION")
    return String(globals.FlashVersion);

  if (var == "GIT_REV")
    return String(constants.GitHash);

  if (var == "MEASURED_PULSES")
    return String(globals.measuredPulses);

  if (var == "MEASURE_BTN")
    return String(globals.measurementActive == true ? "Stop" : "Start");

  if (var == "PLACEHOLDER")
    return "placeholder";

  return String();
}

void Webserver_Callback(AsyncWebServerRequest *request)
{
  request->send(LittleFS, "/index.htm", "text/html", false, processor);
}

void WebserverPOST_Callback(AsyncWebServerRequest *request)
{
  request->send(LittleFS, "/post.htm", "text/html", false, processor);

  Debug_pushMessage("POST:\n");
  int paramsNr = request->params();
  for (int i = 0; i < paramsNr; i++)
  {
    AsyncWebParameter *p = request->getParam(i);
    Debug_pushMessage("%s : %s\n", p->name().c_str(), p->value().c_str());

    // begin: POST Form Source Changed
    if (p->name() == "sourceselect")
    {
      SpeedSource_t temp = (SpeedSource_t)p->value().toInt();
      Debug_pushMessage("temp: %d", temp);
      Debug_pushMessage("SpeedSource: %d", LubeConfig.SpeedSource);
      if (LubeConfig.SpeedSource != temp)
      {
        LubeConfig.SpeedSource = temp;
        globals.systemStatus = sysStat_Shutdown;
      }
    }
    // end: POST Form Source Changed
    // begin: POST Form Source Pulse Settings
    if (p->name() == "tirewidth")
      LubeConfig.TireWidth_mm = p->value().toInt();
    if (p->name() == "tireratio")
      LubeConfig.TireWidthHeight_Ratio = p->value().toInt();
    if (p->name() == "tiredia")
      LubeConfig.RimDiameter_Inch = p->value().toInt();
    if (p->name() == "pulserev")
      LubeConfig.PulsePerRevolution = p->value().toInt();
    if (p->name() == "pulsesave")
      globals.requestEEAction = EE_CFG_SAVE;
      // end: POST Form Source Pulse Settings
#ifdef FEATURE_EABLE_GPS
    // begin: POST Form Source GPS Settings
    if (p->name() == "gpsbaud")
      LubeConfig.GPSBaudRate = (GPSBaudRate_t)p->value().toInt();
    if (p->name() == "gpssave")
      globals.requestEEAction = EE_CFG_SAVE;
// end: POST Form Source GPS Settings
#endif
#ifdef FEATURE_EABLE_CAN
    // begin: POST Form Source CAN Settings
    if (p->name() == "cansource")
      LubeConfig.CANSource = (CANSource_t)p->value().toInt();
    if (p->name() == "cansave")
      globals.requestEEAction = EE_CFG_SAVE;
// end: POST Form Source CAN Settings
#endif
    // begin: POST Form Lubrication
    if (p->name() == "lubedistancenormal")
      LubeConfig.DistancePerLube_Default = p->value().toInt();
    if (p->name() == "lubedistancerain")
      LubeConfig.DistancePerLube_Rain = p->value().toInt();
    if (p->name() == "lubesave")
      globals.requestEEAction = EE_CFG_SAVE;
    // end: POST Form Lubrication
    // begin: POST Form Oiltank
    if (p->name() == "tankcap")
      LubeConfig.tankCapacity_ml = p->value().toInt();
    if (p->name() == "tankwarn")
      LubeConfig.TankRemindAtPercentage = p->value().toInt();
    if (p->name() == "pumppulse")
      LubeConfig.amountPerDose_microL = p->value().toInt();
    if (p->name() == "oilsave")
      globals.requestEEAction = EE_CFG_SAVE;
    // end: POST Form Oiltank
    // begin: POST Form Maintenance
    if (p->name() == "purgepulse")
      LubeConfig.BleedingPulses = p->value().toInt();
    if (p->name() == "maintsave")
      globals.requestEEAction = EE_CFG_SAVE;
    if (p->name() == "resettank")
    {
      PersistenceData.tankRemain_microL = LubeConfig.tankCapacity_ml * 1000;
      globals.requestEEAction = EE_PDS_SAVE;
    }
    if (p->name() == "reset_ee_btn")
    {
      if (request->hasParam("reset_ee_pds", true))
      {
        AsyncWebParameter *param = request->getParam("reset_ee_pds", true);
        if (param->value() == "on")
          globals.requestEEAction = globals.requestEEAction == EE_CFG_FORMAT ? EE_FORMAT_ALL : EE_PDS_FORMAT;
      }
      if (request->hasParam("reset_ee_cfg", true))
      {
        AsyncWebParameter *param = request->getParam("reset_ee_cfg", true);
        if (param->value() == "on")
          globals.requestEEAction = globals.requestEEAction == EE_PDS_FORMAT ? EE_FORMAT_ALL : EE_CFG_FORMAT;
      }
    }
    if (p->name() == "purgenow")
    {
      globals.systemStatus = sysStat_Purge;
      globals.purgePulses = LubeConfig.BleedingPulses;
    }
    if (p->name() == "reboot")
    {
      globals.systemStatus = sysStat_Shutdown;
    }
    // end: POST Form Maintenance
    // begin: POST Form LED Settings
    if (p->name() == "ledmaxbrightness")
      LubeConfig.LED_Max_Brightness = p->value().toInt();
    if (p->name() == "ledminbrightness")
      LubeConfig.LED_Min_Brightness = p->value().toInt();
    if (p->name() == "ledsave")
    {
      if (request->hasParam("ledmodeflash", true))
      {
        AsyncWebParameter *param = request->getParam("ledmodeflash", true);
        if (param->value() == "on")
          LubeConfig.LED_Mode_Flash = true;
      }
      else
      {
        LubeConfig.LED_Mode_Flash = false;
      }
      globals.requestEEAction = EE_CFG_SAVE;
    }
    // end: POST Form LED SEttings
    // begin: POST Form Measure Pulses
    if (p->name() == "measurereset")
      globals.measuredPulses = 0;
    if (p->name() == "measurestartstop")
      globals.measurementActive = !globals.measurementActive;
    // end: POST Form Measure Pulses
  }
}

void WebserverNotFound_Callback(AsyncWebServerRequest *request)
{
  request->send(404, "text/html", "Not found");
}

void GetFlashVersion(char *buff, size_t buff_size)
{
  File this_file = LittleFS.open("version", "r");
  if (!this_file)
  { // failed to open the file, retrn empty result
    buff[0] = '\0';
    return;
  }
  if (this_file.available())
  {
    int bytes_read;
    bytes_read = this_file.readBytesUntil('\r', buff, buff_size - 1);
    buff[bytes_read] = '\0';
  }
  this_file.close();
}

void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final)
{

  if (!index)
  {
    Debug_pushMessage("Update");
    size_t content_len = request->contentLength();
    int cmd = (filename.indexOf(".fs") > -1) ? U_FS : U_FLASH;
    Update.runAsync(true);
    if (!Update.begin(content_len, cmd))
    {
      Update.printError(Serial);
    }
  }

  if (Update.write(data, len) != len)
  {
    Update.printError(Serial);
  }
  else
  {
    Debug_pushMessage("Progress: %d%%\n", (Update.progress() * 100) / Update.size());
  }

  if (final)
  {
    AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "Please wait while the device reboots");
    response->addHeader("Refresh", "20");
    response->addHeader("Location", "/");
    request->send(response);
    if (!Update.end(true))
    {
      Update.printError(Serial);
    }
    else
    {
      Debug_pushMessage("Update complete\n");
      globals.systemStatus = sysStat_Shutdown;
    }
  }
}

void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final)
{
  bool ee_done = false;
  static bool validext = false;
  static char *buffer = NULL;
  static uint32_t read_ptr = 0;
  DeserializationError error;

  if (!index)
  {
    validext = (filename.indexOf(".ee.json") > -1);
    if (validext)
    {
      buffer = (char *)malloc(1536);
      read_ptr = 0;
      if (buffer == NULL)
        Debug_pushMessage("malloc() failed for EEPROM-Restore");
    }
  }

  if (buffer != NULL)
  {
    memcpy(buffer + read_ptr, data, len);
    read_ptr = read_ptr + len;
  }

  if (final)
  {
    if (buffer != NULL)
    {
      Serial.print(buffer);
      StaticJsonDocument<1536> doc;
      error = deserializeJson(doc, buffer);
      if (error)
      {
        Debug_pushMessage("deserializeJson() failed: %s\n", error.f_str());
      }
      else
      {

        LubeConfig.DistancePerLube_Default = doc["config"]["DistancePerLube_Default"].as<uint32_t>();
        LubeConfig.DistancePerLube_Rain = doc["config"]["DistancePerLube_Rain"].as<uint32_t>();
        LubeConfig.tankCapacity_ml = doc["config"]["tankCapacity_ml"].as<uint32_t>();
        LubeConfig.amountPerDose_microL = doc["config"]["amountPerDose_microL"].as<uint32_t>();
        LubeConfig.TankRemindAtPercentage = doc["config"]["TankRemindAtPercentage"].as<uint8_t>();
        LubeConfig.PulsePerRevolution = doc["config"]["PulsePerRevolution"].as<uint8_t>();
        LubeConfig.TireWidth_mm = doc["config"]["TireWidth_mm"].as<uint32_t>();
        LubeConfig.TireWidthHeight_Ratio = doc["config"]["TireWidthHeight_Ratio"].as<uint32_t>();
        LubeConfig.RimDiameter_Inch = doc["config"]["RimDiameter_Inch"].as<uint32_t>();
        LubeConfig.DistancePerRevolution_mm = doc["config"]["DistancePerRevolution_mm"].as<uint32_t>();
        LubeConfig.BleedingPulses = doc["config"]["BleedingPulses"].as<uint16_t>();
        LubeConfig.SpeedSource = (SpeedSource_t)doc["config"]["SpeedSource"].as<int>();
#ifdef FEATURE_ENABLE_GPS
        LubeConfig.GPSBaudRate = (GPSBaudRate_t)doc["config"]["GPSBaudRate"].as<int>();
#endif
#ifdef FEATURE_ENABLE_CAN
        LubeConfig.CANSource = (CANSource_t)doc["config"]["CANSource"].as<int>();
#endif
        LubeConfig.LED_Mode_Flash = doc["config"]["LED_Mode_Flash"].as<bool>();
        LubeConfig.LED_Max_Brightness = doc["config"]["LED_Max_Brightness"].as<uint8_t>();
        LubeConfig.LED_Min_Brightness = doc["config"]["LED_Min_Brightness"].as<uint8_t>();

        PersistenceData.writeCycleCounter = doc["persis"]["writeCycleCounter"].as<uint16_t>();
        PersistenceData.tankRemain_microL = doc["persis"]["tankRemain_microL"].as<uint32_t>();
        PersistenceData.TravelDistance_highRes_mm = doc["persis"]["TravelDistance_highRes_mm"].as<uint32_t>();
        PersistenceData.odometer_mm = doc["persis"]["odometer_mm"].as<uint32_t>();
        PersistenceData.odometer = doc["persis"]["odometer"].as<uint32_t>();
        PersistenceData.checksum = doc["persis"]["checksum"].as<uint32_t>();

        ee_done = true;
      }
    }

    free(buffer);

    AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "Please wait while the device reboots");
    response->addHeader("Refresh", "20");
    response->addHeader("Location", "/");
    request->send(response);

    if (ee_done)
    {
      Debug_pushMessage("Update complete");
      globals.systemStatus = sysStat_Shutdown;
    }
    else
    {
    }
  }
}

void WebServerEEJSON_Callback(AsyncWebServerRequest *request)
{
  AsyncResponseStream *response = request->beginResponseStream("application/json");
  DynamicJsonDocument json(1024);
  JsonObject fwinfo = json.createNestedObject("info");

  char buffer[16];

  fwinfo["DeviceName"] = globals.DeviceName;
  sprintf(buffer, "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor);
  fwinfo["FW-Version"] = buffer;
  fwinfo["FS-Version"] = globals.FlashVersion;
  snprintf_P(buffer, sizeof(buffer), "%s", constants.GitHash);
  fwinfo["Git-Hash"] = buffer;

  JsonObject config = json.createNestedObject("config");

  config["EEPROM_Version"] = LubeConfig.EEPROM_Version;
  config["DistancePerLube_Default"] = LubeConfig.DistancePerLube_Default;
  config["DistancePerLube_Rain"] = LubeConfig.DistancePerLube_Rain;
  config["tankCapacity_ml"] = LubeConfig.tankCapacity_ml;
  config["amountPerDose_microL"] = LubeConfig.amountPerDose_microL;
  config["TankRemindAtPercentage"] = LubeConfig.TankRemindAtPercentage;
  config["PulsePerRevolution"] = LubeConfig.PulsePerRevolution;
  config["TireWidth_mm"] = LubeConfig.TireWidth_mm;
  config["TireWidthHeight_Ratio"] = LubeConfig.TireWidthHeight_Ratio;
  config["RimDiameter_Inch"] = LubeConfig.RimDiameter_Inch;
  config["DistancePerRevolution_mm"] = LubeConfig.DistancePerRevolution_mm;
  config["BleedingPulses"] = LubeConfig.BleedingPulses;
  config["SpeedSource"] = LubeConfig.SpeedSource;
  config["SpeedSource_Str"] = SpeedSourceString[LubeConfig.SpeedSource];
#ifdef FEATURE_ENABLE_GPS
  config["GPSBaudRate"] = LubeConfig.GPSBaudRate;
  config["GPSBaudRate_Str"] = GPSBaudRateString[LubeConfig.GPSBaudRate];
#endif
#ifdef FEATURE_ENABLE_CAN
  config["CANSource"] = LubeConfig.CANSource;
  config["CANSource_Str"] = CANSourceString[LubeConfig.CANSource];
#endif
  config["LED_Mode_Flash"] = LubeConfig.LED_Mode_Flash;
  config["LED_Max_Brightness"] = LubeConfig.LED_Max_Brightness;
  config["LED_Min_Brightness"] = LubeConfig.LED_Min_Brightness;
  sprintf(buffer, "0x%08X", LubeConfig.checksum);
  config["checksum"] = buffer;

  JsonObject eepart = json.createNestedObject("eepart");

  sprintf(buffer, "0x%04X", globals.eePersistanceAdress);
  eepart["PersistanceAddress"] = buffer;

  JsonObject persis = json.createNestedObject("persis");

  persis["writeCycleCounter"] = PersistenceData.writeCycleCounter;
  persis["tankRemain_microL"] = PersistenceData.tankRemain_microL;
  persis["TravelDistance_highRes_mm"] = PersistenceData.TravelDistance_highRes_mm;
  persis["odometer_mm"] = PersistenceData.odometer_mm;
  persis["odometer"] = PersistenceData.odometer;
  sprintf(buffer, "0x%08X", PersistenceData.checksum);
  persis["checksum"] = buffer;

  serializeJsonPretty(json, *response);

  response->addHeader("Content-disposition", "attachment; filename=backup.ee.json");

  request->send(response);
}

#ifdef FEATURE_ENABLE_WEBSOCKETS
void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len)
{
  switch (type)
  {
  case WS_EVT_CONNECT:
    Debug_pushMessage("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
    break;
  case WS_EVT_DISCONNECT:
    Debug_pushMessage("WebSocket client #%u disconnected\n", client->id());
    break;
  case WS_EVT_DATA:
    Websocket_HandleMessage(arg, data, len);
    break;
  case WS_EVT_PONG:
  case WS_EVT_ERROR:
    break;
  }
}

void Websocket_HandleMessage(void *arg, uint8_t *data, size_t len)
{
  AwsFrameInfo *info = (AwsFrameInfo *)arg;
  if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT)
  {
    data[len] = 0;

    Debug_pushMessage("Got WebSocket Message: %s \n", (char *)data);

    if (strcmp((char *)data, "start") == 0)
    {
      SetDebugportStatus(dbg_Webui, enabled);
    }
    else if (strcmp((char *)data, "stop") == 0)
    {
      SetDebugportStatus(dbg_Webui, disabled);
    }
    else if (strcmp((char *)data, "foo") == 0)
    {
      Debug_pushMessage("Got WebSocket Message 'foo' from client\n");
    }
  }
}

void Websocket_PushLiveDebug(String Message)
{
  webSocket.textAll(Message + "\n");
}
#endif