327 lines
9.7 KiB
C++
327 lines
9.7 KiB
C++
#include "WebServer.hpp"
|
|
#include "SdCard.hpp"
|
|
#include "WidgetManager.hpp"
|
|
#include "KnxWorker.hpp"
|
|
#include "Gui.hpp"
|
|
#include "esp_log.h"
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
#include <sys/stat.h>
|
|
|
|
static const char* TAG = "WebServer";
|
|
|
|
WebServer& WebServer::instance() {
|
|
static WebServer instance;
|
|
return instance;
|
|
}
|
|
|
|
WebServer::~WebServer() {
|
|
stop();
|
|
}
|
|
|
|
void WebServer::start() {
|
|
if (server_ != nullptr) {
|
|
ESP_LOGW(TAG, "Server already running");
|
|
return;
|
|
}
|
|
|
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
|
config.uri_match_fn = httpd_uri_match_wildcard;
|
|
config.stack_size = 8192;
|
|
config.max_uri_handlers = 8;
|
|
|
|
ESP_LOGI(TAG, "Starting HTTP server on port %d", config.server_port);
|
|
|
|
if (httpd_start(&server_, &config) != ESP_OK) {
|
|
ESP_LOGE(TAG, "Failed to start HTTP server");
|
|
return;
|
|
}
|
|
|
|
// GET / - Web page (loads index.html from SD card)
|
|
httpd_uri_t root = {
|
|
.uri = "/",
|
|
.method = HTTP_GET,
|
|
.handler = rootHandler,
|
|
.user_ctx = nullptr
|
|
};
|
|
httpd_register_uri_handler(server_, &root);
|
|
|
|
// GET /webseite/* - Static files from SD card
|
|
httpd_uri_t webseite = {
|
|
.uri = "/webseite/*",
|
|
.method = HTTP_GET,
|
|
.handler = staticFileHandler,
|
|
.user_ctx = nullptr
|
|
};
|
|
httpd_register_uri_handler(server_, &webseite);
|
|
|
|
// GET /images/* - Images from SD card
|
|
httpd_uri_t images = {
|
|
.uri = "/images/*",
|
|
.method = HTTP_GET,
|
|
.handler = imagesHandler,
|
|
.user_ctx = nullptr
|
|
};
|
|
httpd_register_uri_handler(server_, &images);
|
|
|
|
// GET /api/config - Get full configuration
|
|
httpd_uri_t getConfig = {
|
|
.uri = "/api/config",
|
|
.method = HTTP_GET,
|
|
.handler = getConfigHandler,
|
|
.user_ctx = nullptr
|
|
};
|
|
httpd_register_uri_handler(server_, &getConfig);
|
|
|
|
// POST /api/config - Update configuration (from editor)
|
|
httpd_uri_t postConfig = {
|
|
.uri = "/api/config",
|
|
.method = HTTP_POST,
|
|
.handler = postConfigHandler,
|
|
.user_ctx = nullptr
|
|
};
|
|
httpd_register_uri_handler(server_, &postConfig);
|
|
|
|
// POST /api/save - Save and apply configuration
|
|
httpd_uri_t postSave = {
|
|
.uri = "/api/save",
|
|
.method = HTTP_POST,
|
|
.handler = postSaveHandler,
|
|
.user_ctx = nullptr
|
|
};
|
|
httpd_register_uri_handler(server_, &postSave);
|
|
|
|
// POST /api/reset - Reset to defaults
|
|
httpd_uri_t postReset = {
|
|
.uri = "/api/reset",
|
|
.method = HTTP_POST,
|
|
.handler = postResetHandler,
|
|
.user_ctx = nullptr
|
|
};
|
|
httpd_register_uri_handler(server_, &postReset);
|
|
|
|
// GET /api/knx/addresses - Get KNX group addresses
|
|
httpd_uri_t getKnxAddresses = {
|
|
.uri = "/api/knx/addresses",
|
|
.method = HTTP_GET,
|
|
.handler = getKnxAddressesHandler,
|
|
.user_ctx = nullptr
|
|
};
|
|
httpd_register_uri_handler(server_, &getKnxAddresses);
|
|
|
|
ESP_LOGI(TAG, "HTTP server started successfully");
|
|
}
|
|
|
|
void WebServer::stop() {
|
|
if (server_ != nullptr) {
|
|
httpd_stop(server_);
|
|
server_ = nullptr;
|
|
ESP_LOGI(TAG, "HTTP server stopped");
|
|
}
|
|
}
|
|
|
|
const char* WebServer::getContentType(const char* filepath) {
|
|
const char* ext = strrchr(filepath, '.');
|
|
if (!ext) return "application/octet-stream";
|
|
|
|
if (strcasecmp(ext, ".html") == 0 || strcasecmp(ext, ".htm") == 0) return "text/html; charset=utf-8";
|
|
if (strcasecmp(ext, ".css") == 0) return "text/css";
|
|
if (strcasecmp(ext, ".js") == 0) return "application/javascript";
|
|
if (strcasecmp(ext, ".json") == 0) return "application/json";
|
|
if (strcasecmp(ext, ".png") == 0) return "image/png";
|
|
if (strcasecmp(ext, ".jpg") == 0 || strcasecmp(ext, ".jpeg") == 0) return "image/jpeg";
|
|
if (strcasecmp(ext, ".gif") == 0) return "image/gif";
|
|
if (strcasecmp(ext, ".svg") == 0) return "image/svg+xml";
|
|
if (strcasecmp(ext, ".ico") == 0) return "image/x-icon";
|
|
|
|
return "application/octet-stream";
|
|
}
|
|
|
|
esp_err_t WebServer::sendFile(httpd_req_t* req, const char* filepath) {
|
|
struct stat st;
|
|
if (stat(filepath, &st) != 0) {
|
|
ESP_LOGE(TAG, "File not found: %s", filepath);
|
|
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File not found");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
FILE* f = fopen(filepath, "r");
|
|
if (!f) {
|
|
ESP_LOGE(TAG, "Failed to open file: %s", filepath);
|
|
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to open file");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
httpd_resp_set_type(req, getContentType(filepath));
|
|
|
|
// Send file in chunks
|
|
char* chunk = new char[1024];
|
|
if (!chunk) {
|
|
fclose(f);
|
|
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Out of memory");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
size_t read;
|
|
while ((read = fread(chunk, 1, 1024, f)) > 0) {
|
|
if (httpd_resp_send_chunk(req, chunk, read) != ESP_OK) {
|
|
delete[] chunk;
|
|
fclose(f);
|
|
ESP_LOGE(TAG, "Failed to send chunk");
|
|
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Send failed");
|
|
return ESP_FAIL;
|
|
}
|
|
}
|
|
|
|
delete[] chunk;
|
|
fclose(f);
|
|
|
|
// End chunked response
|
|
httpd_resp_send_chunk(req, nullptr, 0);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t WebServer::rootHandler(httpd_req_t* req) {
|
|
if (!SdCard::instance().isMounted()) {
|
|
httpd_resp_set_type(req, "text/html; charset=utf-8");
|
|
const char* errorPage = "<!DOCTYPE html><html><body><h1>SD Card Error</h1>"
|
|
"<p>SD card not mounted. Please insert SD card with /webseite/index.html</p></body></html>";
|
|
httpd_resp_send(req, errorPage, strlen(errorPage));
|
|
return ESP_OK;
|
|
}
|
|
|
|
return sendFile(req, "/sdcard/webseite/index.html");
|
|
}
|
|
|
|
esp_err_t WebServer::staticFileHandler(httpd_req_t* req) {
|
|
// URI: /webseite/filename -> /sdcard/webseite/filename
|
|
char filepath[CONFIG_HTTPD_MAX_URI_LEN + 8];
|
|
snprintf(filepath, sizeof(filepath), "/sdcard%.*s",
|
|
(int)(sizeof(filepath) - 8), req->uri);
|
|
|
|
return sendFile(req, filepath);
|
|
}
|
|
|
|
esp_err_t WebServer::imagesHandler(httpd_req_t* req) {
|
|
// URI: /images/filename -> /sdcard/images/filename
|
|
char filepath[CONFIG_HTTPD_MAX_URI_LEN + 8];
|
|
snprintf(filepath, sizeof(filepath), "/sdcard%.*s",
|
|
(int)(sizeof(filepath) - 8), req->uri);
|
|
|
|
return sendFile(req, filepath);
|
|
}
|
|
|
|
esp_err_t WebServer::getConfigHandler(httpd_req_t* req) {
|
|
// Allocate buffer for JSON (widgets can be large)
|
|
char* buf = new char[8192];
|
|
if (buf == nullptr) {
|
|
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Out of memory");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
WidgetManager::instance().getConfigJson(buf, 8192);
|
|
|
|
httpd_resp_set_type(req, "application/json");
|
|
httpd_resp_send(req, buf, strlen(buf));
|
|
|
|
delete[] buf;
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t WebServer::postConfigHandler(httpd_req_t* req) {
|
|
// Read request body
|
|
int total_len = req->content_len;
|
|
if (total_len > 8192) {
|
|
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Content too large");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
char* buf = new char[total_len + 1];
|
|
if (buf == nullptr) {
|
|
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Out of memory");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
int received = 0;
|
|
while (received < total_len) {
|
|
int ret = httpd_req_recv(req, buf + received, total_len - received);
|
|
if (ret <= 0) {
|
|
delete[] buf;
|
|
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Receive failed");
|
|
return ESP_FAIL;
|
|
}
|
|
received += ret;
|
|
}
|
|
buf[received] = '\0';
|
|
|
|
// Update config (does NOT apply yet)
|
|
bool success = WidgetManager::instance().updateConfigFromJson(buf);
|
|
delete[] buf;
|
|
|
|
if (success) {
|
|
httpd_resp_set_type(req, "application/json");
|
|
httpd_resp_send(req, "{\"status\":\"ok\"}", 15);
|
|
} else {
|
|
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
|
|
}
|
|
|
|
return success ? ESP_OK : ESP_FAIL;
|
|
}
|
|
|
|
esp_err_t WebServer::postSaveHandler(httpd_req_t* req) {
|
|
ESP_LOGI(TAG, "Saving and applying configuration");
|
|
|
|
WidgetManager::instance().saveAndApply();
|
|
|
|
httpd_resp_set_type(req, "application/json");
|
|
httpd_resp_send(req, "{\"status\":\"ok\"}", 15);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t WebServer::postResetHandler(httpd_req_t* req) {
|
|
ESP_LOGI(TAG, "Resetting to defaults");
|
|
|
|
WidgetManager::instance().resetToDefaults();
|
|
|
|
httpd_resp_set_type(req, "application/json");
|
|
httpd_resp_send(req, "{\"status\":\"ok\"}", 15);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t WebServer::getKnxAddressesHandler(httpd_req_t* req) {
|
|
char buf[2048];
|
|
int pos = 0;
|
|
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos, "[");
|
|
|
|
// Get group objects from KnxWorker
|
|
KnxWorker& knxWorker = Gui::knxWorker;
|
|
size_t count = knxWorker.getGroupObjectCount();
|
|
|
|
for (size_t i = 1; i <= count && pos < (int)sizeof(buf) - 100; i++) {
|
|
KnxGroupObjectInfo info;
|
|
if (knxWorker.getGroupObjectInfo(i, info)) {
|
|
char addrStr[16];
|
|
KnxWorker::formatGroupAddress(info.groupAddress, addrStr, sizeof(addrStr));
|
|
|
|
if (i > 1) pos += snprintf(buf + pos, sizeof(buf) - pos, ",");
|
|
|
|
pos += snprintf(buf + pos, sizeof(buf) - pos,
|
|
"{\"index\":%d,\"addr\":%d,\"addrStr\":\"%s\",\"comm\":%s,\"read\":%s,\"write\":%s}",
|
|
info.goIndex,
|
|
info.groupAddress,
|
|
addrStr,
|
|
info.commFlag ? "true" : "false",
|
|
info.readFlag ? "true" : "false",
|
|
info.writeFlag ? "true" : "false"
|
|
);
|
|
}
|
|
}
|
|
|
|
snprintf(buf + pos, sizeof(buf) - pos, "]");
|
|
|
|
httpd_resp_set_type(req, "application/json");
|
|
httpd_resp_send(req, buf, strlen(buf));
|
|
return ESP_OK;
|
|
}
|