Files
awtrix-light/lib/webserver/src/esp-fs-webserver.h
Stephan Mühl 1b4a5b2952 add http_api
2023-03-31 14:29:48 +02:00

349 lines
9.8 KiB
C++

#ifndef esp_fs_webserver_H
#define esp_fs_webserver_H
#include <Arduino.h>
#include <memory>
#include <typeinfo>
#include <base64.h>
//#include <FS.h>
#include <LittleFS.h>
#define INCLUDE_EDIT_HTM
#ifdef INCLUDE_EDIT_HTM
#include "edit_htm.h"
#endif
#define INCLUDE_SETUP_HTM
#ifdef INCLUDE_SETUP_HTM
#define ARDUINOJSON_USE_LONG_LONG 1
#include <ArduinoJson.h>
#include "setup_htm.h"
#endif
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPUpdateServer.h> // from Arduino core, OTA update via webbrowser
using WebServerClass = ESP8266WebServer;
using UpdateServerClass = ESP8266HTTPUpdateServer;
#elif defined(ESP32)
#include <esp_wifi.h>
#include <WebServer.h>
#include <WiFi.h>
#include <ESPmDNS.h>
#include <HTTPUpdateServer.h> // from Arduino core, OTA update via webbrowser
using WebServerClass = WebServer;
using UpdateServerClass = HTTPUpdateServer;
#endif
#include <DNSServer.h>
#ifndef DEBUG_ESP_PORT
#define DBG_OUTPUT_PORT Serial
#define DEBUG_MODE_WS false
#else
#define DBG_OUTPUT_PORT DEBUG_ESP_PORT
#endif
#if DEBUG_MODE_WS
#define DebugPrint(x) DBG_OUTPUT_PORT.print(x)
#define DebugPrintln(x) DBG_OUTPUT_PORT.println(x)
#define DebugPrintf(fmt, ...) DBG_OUTPUT_PORT.printf(fmt, ##__VA_ARGS__)
#define DebugPrintf_P(fmt, ...) DBG_OUTPUT_PORT.printf_P(fmt, ##__VA_ARGS__)
#else
#define DebugPrint(x)
#define DebugPrintln(x)
#define DebugPrintf(x, ...)
#define DebugPrintf_P(x, ...)
#endif
enum
{
MSG_OK,
CUSTOM,
NOT_FOUND,
BAD_REQUEST,
ERROR
};
#define TEXT_PLAIN "text/plain"
#define FS_INIT_ERROR "FS INIT ERROR"
#define FILE_NOT_FOUND "FileNotFound"
class FSWebServer
{
// using CallbackF = std::function<void(void)>;
public:
WebServerClass *webserver;
FSWebServer(fs::FS &fs, WebServerClass &server);
bool begin(const char *path = nullptr);
void run();
void addHandler(const Uri &uri, HTTPMethod method, WebServerClass::THandlerFunction fn);
void addHandler(const Uri &uri, WebServerClass::THandlerFunction handler);
void onNotFound(WebServerClass::THandlerFunction handler);
void setCaptiveWebage(const char *url);
IPAddress setAPmode(const char *ssid, const char *psk);
IPAddress startWiFi(uint32_t timeout, const char *apSSID, const char *apPsw);
WebServerClass *getRequest();
#ifdef INCLUDE_SETUP_HTM
#define MIN_F -3.4028235E+38
#define MAX_F 3.4028235E+38
inline bool clearOptions()
{
File file = m_filesystem->open("/config.json", "r");
if (file)
{
file.close();
m_filesystem->remove("/config.json");
return true;
}
return false;
}
inline void addOptionBox(const char *boxTitle)
{
addOption("param-box", boxTitle, false);
}
inline void addHTML(const char *html, const char *id)
{
String elementId = "raw-html-";
elementId += id;
String trimmed = html;
removeWhiteSpaces(trimmed);
addOption(elementId.c_str(), trimmed.c_str(), false);
}
inline void addCSS(const char *css)
{
String trimmed = css;
removeWhiteSpaces(trimmed);
addOption("raw-css", trimmed.c_str(), false);
}
inline void addJavascript(const char *script)
{
// String trimmed = script;
// removeWhiteSpaces(trimmed);
addOption("raw-javascript", script, true);
}
void addDropdownList(const char *label, const char **array, size_t size);
// Only for backward-compatibility
template <typename T>
inline void addOption(fs::FS &fs, const char *label, T val, bool hidden = false)
{
addOption(label, val, hidden);
}
// Add custom option to config webpage (float values)
template <typename T>
inline void addOption(const char *label, T val, double d_min, double d_max, double step)
{
addOption(label, val, false, d_min, d_max, step);
}
// Add custom option to config webpage (type of parameter will be deduced from variable itself)
template <typename T>
inline void addOption(const char *label, T val, bool hidden = false,
double d_min = MIN_F, double d_max = MAX_F, double step = 1.0)
{
File file = m_filesystem->open("/config.json", "r");
int sz = file.size() * 1.33;
int docSize = max(sz, 2048);
DynamicJsonDocument doc((size_t)docSize);
if (file)
{
// If file is present, load actual configuration
DeserializationError error = deserializeJson(doc, file);
if (error)
{
DebugPrintln(F("Failed to deserialize file, may be corrupted"));
DebugPrintln(error.c_str());
file.close();
return;
}
file.close();
}
else
{
DebugPrintln(F("File not found, will be created new configuration file"));
}
numOptions++;
String key = label;
if (hidden)
key += "-hidden";
// Univoque key name
if (key.equals("param-box"))
{
key += numOptions;
}
if (key.equals("raw-javascript"))
{
key += numOptions;
}
// If key is present in json, we don't need to create it.
if (doc.containsKey(key.c_str()))
return;
// if min, max, step != from default, treat this as object in order to set other properties
if (d_min != MIN_F || d_max != MAX_F || step != 1.0)
{
JsonObject obj = doc.createNestedObject(key);
obj["value"] = static_cast<T>(val);
obj["min"] = d_min;
obj["max"] = d_max;
obj["step"] = step;
}
else
{
doc[key] = static_cast<T>(val);
}
file = m_filesystem->open("/config.json", "w");
if (serializeJsonPretty(doc, file) == 0)
{
DebugPrintln(F("Failed to write to file"));
}
file.close();
}
// Get current value for a specific custom option (true on success)
template <typename T>
bool getOptionValue(const char *label, T &var)
{
File file = m_filesystem->open("/config.json", "r");
DynamicJsonDocument doc(file.size() * 1.33);
if (file)
{
DeserializationError error = deserializeJson(doc, file);
if (error)
{
DebugPrintln(F("Failed to deserialize file, may be corrupted"));
DebugPrintln(error.c_str());
file.close();
return false;
}
file.close();
}
else
return false;
if (doc[label]["value"])
var = doc[label]["value"].as<T>();
else if (doc[label]["selected"])
var = doc[label]["selected"].as<T>();
else
var = doc[label].as<T>();
return true;
}
template <typename T>
bool saveOptionValue(const char *label, T val)
{
// Öffne die Datei im Lesemodus, um den Inhalt des Dokuments beizubehalten
File file = m_filesystem->open("/config.json", "r");
DynamicJsonDocument doc(file.size()* 1.33);
if (file)
{
// Deserialisiere das JSON-Dokument aus der Datei
DeserializationError error = deserializeJson(doc, file);
if (error)
{
DebugPrintln(F("Failed to deserialize file, may be corrupted"));
DebugPrintln(error.c_str());
file.close();
return false;
}
file.close();
}
// Füge den Schlüssel mit dem neuen Wert hinzu, falls er nicht vorhanden ist
if (!doc.containsKey(label))
{
doc[label] = val;
}
else
{
// Ändere den Wert mit dem Schlüssel "label"
JsonVariant option = doc[label];
option.set(val);
}
// Öffne die Datei im Schreibmodus und speichere das geänderte Dokument
file = m_filesystem->open("/config.json", "w");
if (!file)
return false;
serializeJsonPretty(doc, file);
file.close();
return true;
}
#endif
private:
char m_basePath[16];
UpdateServerClass m_httpUpdater;
DNSServer m_dnsServer;
fs::FS *m_filesystem;
File m_uploadFile;
bool m_fsOK = false;
bool m_apmode = false;
char *m_apWebpage = (char *)"/setup";
uint32_t m_timeout = 10000;
// Default handler for all URIs not defined above, use it to read files from filesystem
bool checkDir(char *dirname, uint8_t levels);
void doWifiConnection();
void doRestart();
void replyOK();
void getIpAddress();
void handleRequest();
#ifdef INCLUDE_SETUP_HTM
void removeWhiteSpaces(String &str);
void handleSetup();
uint8_t numOptions = 0;
#endif
void handleIndex();
bool handleFileRead(const String &path);
void handleFileUpload();
void replyToCLient(int msg_type, const char *msg);
void checkForUnsupportedPath(String &filename, String &error);
void setCrossOrigin();
void handleScanNetworks();
const char *getContentType(const char *filename);
bool captivePortal();
// edit page, in usefull in some situation, but if you need to provide only a web interface, you can disable
#ifdef INCLUDE_EDIT_HTM
void handleGetEdit();
void handleFileCreate();
void handleFileDelete();
void handleStatus();
void handleFileList();
#endif
};
#endif