Skip to content

Commit

Permalink
Add API endpoint to retrieve custom languages and complete language pack
Browse files Browse the repository at this point in the history
  • Loading branch information
tbnobody committed Oct 21, 2024
1 parent 8257eb7 commit e29b86e
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 1 deletion.
27 changes: 27 additions & 0 deletions include/I18n.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <TaskSchedulerDeclarations.h>
#include <WString.h>
#include <list>

struct LanguageInfo_t {
String code;
String name;
String filename;
};

class I18nClass {
public:
I18nClass();
void init(Scheduler& scheduler);
std::list<LanguageInfo_t> getAvailableLanguages();

private:
void readLangPacks();
void readConfig(String file);

std::list<LanguageInfo_t> _availLanguages;
};

extern I18nClass I18n;
1 change: 1 addition & 0 deletions include/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ class Utils {
static int getTimezoneOffset();
static bool checkJsonAlloc(const JsonDocument& doc, const char* function, const uint16_t line);
static void removeAllFiles();
static String generateMd5FromFile(String file);
};
2 changes: 2 additions & 0 deletions include/WebApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "WebApi_file.h"
#include "WebApi_firmware.h"
#include "WebApi_gridprofile.h"
#include "WebApi_i18n.h"
#include "WebApi_inverter.h"
#include "WebApi_limit.h"
#include "WebApi_maintenance.h"
Expand Down Expand Up @@ -53,6 +54,7 @@ class WebApiClass {
WebApiFileClass _webApiFile;
WebApiFirmwareClass _webApiFirmware;
WebApiGridProfileClass _webApiGridprofile;
WebApiI18nClass _webApiI18n;
WebApiInverterClass _webApiInverter;
WebApiLimitClass _webApiLimit;
WebApiMaintenanceClass _webApiMaintenance;
Expand Down
14 changes: 14 additions & 0 deletions include/WebApi_i18n.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>

class WebApiI18nClass {
public:
void init(AsyncWebServer& server, Scheduler& scheduler);

private:
void onI18nLanguages(AsyncWebServerRequest* request);
void onI18nLanguage(AsyncWebServerRequest* request);
};
2 changes: 2 additions & 0 deletions include/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,5 @@
#define LED_BRIGHTNESS 100U

#define MAX_INVERTER_LIMIT 2250

#define LANG_PACK_SUFFIX ".lang.json"
72 changes: 72 additions & 0 deletions src/I18n.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2024 Thomas Basler and others
*/
#include "I18n.h"
#include "MessageOutput.h"
#include "Utils.h"
#include "defaults.h"
#include <ArduinoJson.h>
#include <LittleFS.h>

I18nClass I18n;

I18nClass::I18nClass()
{
}

void I18nClass::init(Scheduler& scheduler)
{
readLangPacks();
}

std::list<LanguageInfo_t> I18nClass::getAvailableLanguages()
{
return _availLanguages;
}

void I18nClass::readLangPacks()
{
auto root = LittleFS.open("/");
auto file = root.getNextFileName();

while (file != "") {
if (file.endsWith(LANG_PACK_SUFFIX)) {
MessageOutput.printf("Read File %s\r\n", file.c_str());
readConfig(file);
}
file = root.getNextFileName();
}
root.close();
}

void I18nClass::readConfig(String file)
{
JsonDocument filter;
filter["meta"] = true;

File f = LittleFS.open(file, "r", false);

JsonDocument doc;

// Deserialize the JSON document
const DeserializationError error = deserializeJson(doc, f, DeserializationOption::Filter(filter));
if (error) {
MessageOutput.printf("Failed to read file %s\r\n", file.c_str());
f.close();
return;
}

if (!Utils::checkJsonAlloc(doc, __FUNCTION__, __LINE__)) {
return;
}

LanguageInfo_t lang;
lang.code = String(doc["meta"]["code"]);
lang.name = String(doc["meta"]["name"]);
lang.filename = file;

_availLanguages.push_back(lang);

f.close();
}
32 changes: 32 additions & 0 deletions src/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "MessageOutput.h"
#include "PinMapping.h"
#include <LittleFS.h>
#include <MD5Builder.h>

uint32_t Utils::getChipId()
{
Expand Down Expand Up @@ -80,3 +81,34 @@ void Utils::removeAllFiles()
file = root.getNextFileName();
}
}

String Utils::generateMd5FromFile(String file)
{
if (!LittleFS.exists(file)) {
return String();
}

File f = LittleFS.open(file, "r");
if (!file) {
return String();
}

MD5Builder md5;
md5.begin();

// Read the file in chunks to avoid using too much memory
const size_t bufferSize = 512;
uint8_t buffer[bufferSize];

while (f.available()) {
size_t bytesRead = f.read(buffer, bufferSize);
md5.add(buffer, bytesRead);
}

// Finalize and calculate the MD5 hash
md5.calculate();

f.close();

return md5.toString();
}
1 change: 1 addition & 0 deletions src/WebApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ void WebApiClass::init(Scheduler& scheduler)
_webApiFile.init(_server, scheduler);
_webApiFirmware.init(_server, scheduler);
_webApiGridprofile.init(_server, scheduler);
_webApiI18n.init(_server, scheduler);
_webApiInverter.init(_server, scheduler);
_webApiLimit.init(_server, scheduler);
_webApiMaintenance.init(_server, scheduler);
Expand Down
81 changes: 81 additions & 0 deletions src/WebApi_i18n.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2024 Thomas Basler and others
*/
#include "WebApi_i18n.h"
#include "I18n.h"
#include "Utils.h"
#include "WebApi.h"
#include <AsyncJson.h>
#include <LittleFS.h>

#include "MessageOutput.h"

void WebApiI18nClass::init(AsyncWebServer& server, Scheduler& scheduler)
{
using std::placeholders::_1;

server.on("/api/i18n/languages", HTTP_GET, std::bind(&WebApiI18nClass::onI18nLanguages, this, _1));
server.on("/api/i18n/language", HTTP_GET, std::bind(&WebApiI18nClass::onI18nLanguage, this, _1));
}

void WebApiI18nClass::onI18nLanguages(AsyncWebServerRequest* request)
{
AsyncJsonResponse* response = new AsyncJsonResponse(true);
auto& root = response->getRoot();
const auto& languages = I18n.getAvailableLanguages();

for (auto& language : languages) {
auto jsonLang = root.add<JsonObject>();

jsonLang["code"] = language.code;
jsonLang["name"] = language.name;
}

WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__);
}

void WebApiI18nClass::onI18nLanguage(AsyncWebServerRequest* request)
{
if (request->hasParam("code")) {
String code = request->getParam("code")->value();

const auto& languages = I18n.getAvailableLanguages();
auto it = std::find_if(languages.begin(), languages.end(), [code](const LanguageInfo_t& elem) {
return elem.code == code;
});

if (it != languages.end()) {
String md5 = Utils::generateMd5FromFile(it->filename);

String expectedEtag;
expectedEtag = "\"";
expectedEtag += md5;
expectedEtag += "\"";

bool eTagMatch = false;
if (request->hasHeader("If-None-Match")) {
const AsyncWebHeader* h = request->getHeader("If-None-Match");
eTagMatch = h->value().equals(expectedEtag);
}

// begin response 200 or 304
AsyncWebServerResponse* response;
if (eTagMatch) {
response = request->beginResponse(304);
} else {
response = request->beginResponse(LittleFS, it->filename, asyncsrv::T_application_json);
}

// HTTP requires cache headers in 200 and 304 to be identical
response->addHeader("Cache-Control", "public, must-revalidate");
response->addHeader("ETag", expectedEtag);

request->send(response);
return;
}
}

request->send(404);
return;
}
8 changes: 7 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "Configuration.h"
#include "Datastore.h"
#include "Display_Graphic.h"
#include "I18n.h"
#include "InverterSettings.h"
#include "Led_Single.h"
#include "MessageOutput.h"
Expand All @@ -24,9 +25,9 @@
#include "defaults.h"
#include <Arduino.h>
#include <LittleFS.h>
#include <SpiManager.h>
#include <TaskScheduler.h>
#include <esp_heap_caps.h>
#include <SpiManager.h>

#include <driver/uart.h>

Expand Down Expand Up @@ -83,6 +84,11 @@ void setup()
auto& config = Configuration.get();
MessageOutput.println("done");

// Read languate pack
MessageOutput.print("Reading language pack... ");
I18n.init(scheduler);
MessageOutput.println("done");

// Load PinMapping
MessageOutput.print("Reading PinMapping... ");
if (PinMapping.init(String(Configuration.get().Dev_PinMapping))) {
Expand Down

0 comments on commit e29b86e

Please sign in to comment.