theelims / ESP32-sveltekit

A simple and extensible framework for ESP32 based IoT projects with a feature-rich, beautiful, and responsive front-end build with Sveltekit, Tailwind CSS and DaisyUI. This is a project template to get you started in no time with a fully integrated build chain.
https://theelims.github.io/ESP32-sveltekit/
Other
90 stars 15 forks source link

Read state from FS for custom external rest call #13

Closed besanur closed 6 months ago

besanur commented 7 months ago

Hello, I would like to save a Json Object in the FileSystem via Http. I then want to read the values from the FS in my CustomStateService.

What is the best way to do this in the backend? I have already done the frontend part, the id is set and a rest endpoint should be called. But I would like to save this ID in the file system in the backend and then read it somewhere else. For an Http rest call to an external service. Which in turn gives me a Json response and I save it back into the file system.

I'm currently also using my own code to save the result of the external service call in the FS. Can I also use the FSPersisntence for this somehow?

Here is the code I use...

#ifndef CustomTimesService_h
#define CustomTimesService_h

#include <Arduino.h>
#include <ArduinoJson.h>
#include <LittleFS.h>
#include <FSPersistence.h>
#include <HttpEndpoint.h>
#include <WiFiClient.h>
#include <HTTPClient.h>
#include <DisplayService.h>

class CustomTimeState {
 public:
  String selectedCountry;
  String selectedCity;
  String selectedDistrict;

  static void read(CustomTimeState& settings, JsonObject& root) {
    root["selectedCountry"] = settings.selectedCountry;
    root["selectedCity"] = settings.selectedCity;
    root["selectedDistrict"] = settings.selectedDistrict;
  }

  static StateUpdateResult update(JsonObject& root, CustomTimeState& customTimeState) {
    customTimeState.selectedCountry = root["selectedCountry"];
    customTimeState.selectedCity = root["selectedCity"];
    customTimeState.selectedDistrict = root["selectedDistrict"];
    return StateUpdateResult::CHANGED;
  }
};

class CustomTimesService : public StatefulService<CustomTimeState>  {
 public:
  CustomTimesService(DisplayService* displayService, AsyncWebServer* server, FS* fs, SecurityManager* securityManager);

  void begin();

 private:
  DisplayService _displayService;
  HttpEndpoint<CustomTimeState> _httpEndpoint;
  FSPersistence<CustomTimeState> _fsPersistence;

  boolean readFromFS();
  void makeHttpCall();
  void writeFile(const char* path, const char* content);
};

#endif
#include <CustomTimesService.h>

#define CUSTOM_TIMES_SETTINGS_PATH "/rest/customTimes"

CustomTimesService::CustomTimesService(DisplayService* displayService, AsyncWebServer* server, FS* fs, SecurityManager* securityManager)
: _displayService(),
 _httpEndpoint(CustomTimeState::read,
                  CustomTimeState::update,
                  this,
                  server,
                  CUSTOM_TIMES_SETTINGS_PATH,
                  securityManager,
                  AuthenticationPredicates::IS_AUTHENTICATED),
 _fsPersistence(CustomTimeState::read, CustomTimeState::update, this, fs, "/config/selectedCustomTime.json")
{}

unsigned long lastRequest = millis();

String endpointUrl = "http://localhost:1234/api/";
Adafruit_ST7789 tft(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);

void CustomTimesService::begin() {
  auto successfull = readFromFS();

  if (!successfull) {
    makeHttpCall();
  }
}

boolean CustomTimesService::readFromFS() {
  Serial.println("Start reading custom times from FS");
  File file = LittleFS.open("/custom_times.json");
  if (!file) {
    Serial.println("Failed to open custom_times.json");
    return false;
  }

  size_t size = file.size();
  std::unique_ptr<char[]> buf(new char[size]);
  file.readBytes(buf.get(), size);
  file.close();

  DynamicJsonDocument doc(25000);  // Passen Sie die Größe je nach Bedarf an
  DeserializationError error = deserializeJson(doc, buf.get());

  if (error) {
    Serial.println("Failed to parse JSON");
    return false;
  }

  const char* data = doc[0]["Data"];
  _displayService.displayPrayerTime("Data", data, ST77XX_WHITE, 2, DISPLAY_HEIGHT-145);

  Serial.println("Sucesfull read custom times from FS");
  return true;
}

void CustomTimesService::makeHttpCall() {
  WiFiClient wifiClient;
  HTTPClient httpClient;

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  tft.fillScreen(ST77XX_BLACK);

  // Read Id here from FS and append to endpointUrl
 // url = endpointUrl + id;

  // Führe HTTP GET-Request aus
  httpClient.addHeader("content-type", "application/json");
  if (httpClient.begin(wifiClient, url)) {
    int httpResponseCode = httpClient.GET();

    if (httpResponseCode == HTTP_CODE_OK) {
      String payload = httpClient.getString();
      Serial.println("Times fetched");
      Serial.println("HTTP-Code: ");
      Serial.print(httpResponseCode);

      DynamicJsonDocument doc(25000);
      DeserializationError error = deserializeJson(doc, payload);
      Serial.print("Size of json: ");
      Serial.println(doc.size());

      if (error) {
        Serial.println("Failed to parse JSON");
      }

      writeFile("/custom_times.json", payload.c_str());
    } else {
      Serial.print("Failure during HTTP-GET. HTTP-Statuscode: ");
      Serial.println(httpResponseCode);
    }

    httpClient.end();
    lastRequest = millis();
  }
}

void CustomTimesService::writeFile(const char* path, const char* content) {
  // Datei zum Schreiben öffnen (wird erstellt, wenn sie nicht existiert)
  File file = LittleFS.open(path, "w");
  if (!file) {
    Serial.println("Fehler beim Öffnen der Datei zum Schreiben");
    return;
  }
  file.print(content);
  file.close();
}
theelims commented 7 months ago

Hi,

I think it is best to see the JSON from your external API and the JSON created by the serialization for StatefulService completely separate. Instead of overwriting the file on the FS you can just parse it and write it into the variables directly. CustomTimeState is accessible as _state in the CustomTimesService class. After you've stored your external API results you can trigger an update by calling

_state.callUpdateHandlers("externalAPI");

The same logic can be used to update the display by simply reading from _state.selectedCity.

besanur commented 6 months ago

Hi,

I think it is best to see the JSON from your external API and the JSON created by the serialization for StatefulService completely separate. Instead of overwriting the file on the FS you can just parse it and write it into the variables directly. CustomTimeState is accessible as _state in the CustomTimesService class. After you've stored your external API results you can trigger an update by calling

_state.callUpdateHandlers("externalAPI");

The same logic can be used to update the display by simply reading from _state.selectedCity.

Ok thank you i will try this

theelims commented 6 months ago

@besanur Did this work for you? Can I close this issue as solved?

besanur commented 6 months ago

Yes, thank you, you can close it.