Replaced Logger usage with LOG_x (#548)

This commit is contained in:
Ken Van Hoeylandt
2026-07-04 23:49:19 +02:00
committed by GitHub
parent ecad2248d9
commit 9d5993930d
162 changed files with 1776 additions and 1842 deletions
@@ -3,7 +3,6 @@
#include <Tactility/service/webserver/AssetVersion.h>
#include <Tactility/file/File.h>
#include <Tactility/Logger.h>
#include <cJSON.h>
#include <cstdio>
@@ -12,9 +11,11 @@
#include <memory>
#include <esp_random.h>
#include <tactility/log.h>
namespace tt::service::webserver {
static const auto LOGGER = tt::Logger("AssetVersion");
constexpr auto* TAG = "AssetVersion";
constexpr auto* DATA_VERSION_FILE = "/system/app/WebServer/version.json";
constexpr auto* SD_VERSION_FILE = "/sdcard/tactility/webserver/version.json";
constexpr auto* DATA_ASSETS_DIR = "/system/app/WebServer";
@@ -22,65 +23,65 @@ constexpr auto* SD_ASSETS_DIR = "/sdcard/tactility/webserver";
static bool loadVersionFromFile(const char* path, AssetVersion& version) {
if (!file::isFile(path)) {
LOGGER.warn("Version file not found: {}", path);
LOG_W(TAG, "Version file not found: %s", path);
return false;
}
// Read file content
std::string content;
{
auto lock = file::getLock(path);
lock->lock(portMAX_DELAY);
FILE* fp = fopen(path, "r");
if (!fp) {
LOGGER.error("Failed to open version file: {}", path);
LOG_E(TAG, "Failed to open version file: %s", path);
lock->unlock();
return false;
}
char buffer[256];
size_t bytesRead = fread(buffer, 1, sizeof(buffer) - 1, fp);
bool readError = ferror(fp) != 0;
fclose(fp);
lock->unlock();
if (readError) {
LOGGER.error("Error reading version file: {}", path);
LOG_E(TAG, "Error reading version file: %s", path);
return false;
}
if (bytesRead == 0) {
LOGGER.error("Version file is empty: {}", path);
LOG_E(TAG, "Version file is empty: %s", path);
return false;
}
buffer[bytesRead] = '\0';
content = buffer;
}
// Parse JSON
cJSON* json = cJSON_Parse(content.c_str());
if (json == nullptr) {
LOGGER.error("Failed to parse version JSON: {}", path);
LOG_E(TAG, "Failed to parse version JSON: %s", path);
return false;
}
cJSON* versionItem = cJSON_GetObjectItem(json, "version");
if (versionItem == nullptr || !cJSON_IsNumber(versionItem)) {
LOGGER.error("Invalid version JSON format: {}", path);
LOG_E(TAG, "Invalid version JSON format: %s", path);
cJSON_Delete(json);
return false;
}
double versionValue = versionItem->valuedouble;
if (versionValue < 0 || versionValue > UINT32_MAX) {
LOGGER.error("Version out of valid range [0, {}]: {}", UINT32_MAX, path);
LOG_E(TAG, "Version out of valid range [0, %u]: %s", (unsigned)UINT32_MAX, path);
cJSON_Delete(json);
return false;
}
version.version = static_cast<uint32_t>(versionValue);
cJSON_Delete(json);
LOGGER.info("Loaded version {} from {}", version.version, path);
LOG_I(TAG, "Loaded version %u from %s", (unsigned)version.version, path);
return true;
}
@@ -92,23 +93,23 @@ static bool saveVersionToFile(const char* path, const AssetVersion& version) {
dirPath = dirPath.substr(0, lastSlash);
if (!file::isDirectory(dirPath.c_str())) {
if (!file::findOrCreateDirectory(dirPath.c_str(), 0755)) {
LOGGER.error("Failed to create directory: {}", dirPath);
LOG_E(TAG, "Failed to create directory: %s", dirPath.c_str());
return false;
}
}
}
// Create JSON
cJSON* json = cJSON_CreateObject();
if (json == nullptr) {
LOGGER.error("Failed to create JSON object for version");
LOG_E(TAG, "Failed to create JSON object for version");
return false;
}
cJSON_AddNumberToObject(json, "version", version.version);
char* jsonString = cJSON_Print(json);
if (jsonString == nullptr) {
LOGGER.error("Failed to serialize version JSON");
LOG_E(TAG, "Failed to serialize version JSON");
cJSON_Delete(json);
return false;
}
@@ -126,12 +127,12 @@ static bool saveVersionToFile(const char* path, const AssetVersion& version) {
success = (written == len);
if (success) {
if (fflush(fp) != 0) {
LOGGER.error("Failed to flush version file: {}", path);
LOG_E(TAG, "Failed to flush version file: %s", path);
success = false;
} else {
int fd = fileno(fp);
if (fd >= 0 && fsync(fd) != 0) {
LOGGER.error("Failed to fsync version file: {}", path);
LOG_E(TAG, "Failed to fsync version file: %s", path);
success = false;
}
}
@@ -145,9 +146,9 @@ static bool saveVersionToFile(const char* path, const AssetVersion& version) {
cJSON_Delete(json);
if (success) {
LOGGER.info("Saved version {} to {}", version.version, path);
LOG_I(TAG, "Saved version %u to %s", (unsigned)version.version, path);
} else {
LOGGER.error("Failed to write version file: {}", path);
LOG_E(TAG, "Failed to write version file: %s", path);
}
return success;
@@ -180,15 +181,15 @@ bool hasSdAssets() {
static bool copyDirectory(const char* src, const char* dst, int depth = 0) {
constexpr int MAX_DEPTH = 16;
if (depth >= MAX_DEPTH) {
LOGGER.error("Max directory depth exceeded: {}", src);
LOG_E(TAG, "Max directory depth exceeded: %s", src);
return false;
}
LOGGER.info("Copying directory: {} -> {}", src, dst);
LOG_I(TAG, "Copying directory: %s -> %s", src, dst);
// Create destination directory
if (!file::isDirectory(dst)) {
if (!file::findOrCreateDirectory(dst, 0755)) {
LOGGER.error("Failed to create destination directory: {}", dst);
LOG_E(TAG, "Failed to create destination directory: %s", dst);
return false;
}
}
@@ -216,7 +217,7 @@ static bool copyDirectory(const char* src, const char* dst, int depth = 0) {
isDir = S_ISDIR(st.st_mode);
isReg = S_ISREG(st.st_mode);
} else {
LOGGER.warn("Failed to stat entry, skipping: {}", srcPath);
LOG_W(TAG, "Failed to stat entry, skipping: %s", srcPath.c_str());
return;
}
}
@@ -233,14 +234,14 @@ static bool copyDirectory(const char* src, const char* dst, int depth = 0) {
FILE* srcFile = fopen(srcPath.c_str(), "rb");
if (!srcFile) {
LOGGER.error("Failed to open source file: {}", srcPath);
LOG_E(TAG, "Failed to open source file: %s", srcPath.c_str());
copySuccess = false;
return;
}
FILE* tempFile = fopen(tempPath.c_str(), "wb");
if (!tempFile) {
LOGGER.error("Failed to create temp file: {}", tempPath);
LOG_E(TAG, "Failed to create temp file: %s", tempPath.c_str());
fclose(srcFile);
copySuccess = false;
return;
@@ -254,7 +255,7 @@ static bool copyDirectory(const char* src, const char* dst, int depth = 0) {
while ((bytesRead = fread(buffer.get(), 1, COPY_BUF_SIZE, srcFile)) > 0) {
size_t bytesWritten = fwrite(buffer.get(), 1, bytesRead, tempFile);
if (bytesWritten != bytesRead) {
LOGGER.error("Failed to write to temp file: {}", tempPath);
LOG_E(TAG, "Failed to write to temp file: %s", tempPath.c_str());
fileCopySuccess = false;
copySuccess = false;
break;
@@ -262,7 +263,7 @@ static bool copyDirectory(const char* src, const char* dst, int depth = 0) {
}
if (fileCopySuccess && ferror(srcFile)) {
LOGGER.error("Error reading source file: {}", srcPath);
LOG_E(TAG, "Error reading source file: %s", srcPath.c_str());
fileCopySuccess = false;
copySuccess = false;
}
@@ -272,13 +273,13 @@ static bool copyDirectory(const char* src, const char* dst, int depth = 0) {
// Flush and sync temp file before closing
if (fileCopySuccess) {
if (fflush(tempFile) != 0) {
LOGGER.error("Failed to flush temp file: {}", tempPath);
LOG_E(TAG, "Failed to flush temp file: %s", tempPath.c_str());
fileCopySuccess = false;
copySuccess = false;
} else {
int fd = fileno(tempFile);
if (fd >= 0 && fsync(fd) != 0) {
LOGGER.error("Failed to fsync temp file: {}", tempPath);
LOG_E(TAG, "Failed to fsync temp file: %s", tempPath.c_str());
fileCopySuccess = false;
copySuccess = false;
}
@@ -292,7 +293,7 @@ static bool copyDirectory(const char* src, const char* dst, int depth = 0) {
remove(dstPath.c_str());
// Rename temp file to destination
if (rename(tempPath.c_str(), dstPath.c_str()) != 0) {
LOGGER.error("Failed to rename temp file {} to {}", tempPath, dstPath);
LOG_E(TAG, "Failed to rename temp file %s to %s", tempPath.c_str(), dstPath.c_str());
remove(tempPath.c_str());
fileCopySuccess = false;
copySuccess = false;
@@ -303,13 +304,13 @@ static bool copyDirectory(const char* src, const char* dst, int depth = 0) {
}
if (fileCopySuccess) {
LOGGER.info("Copied file: {}", entry.d_name);
LOG_I(TAG, "Copied file: %s", entry.d_name);
}
}
});
if (!listSuccess) {
LOGGER.error("Failed to list source directory: {}", src);
LOG_E(TAG, "Failed to list source directory: %s", src);
return false;
}
@@ -317,7 +318,7 @@ static bool copyDirectory(const char* src, const char* dst, int depth = 0) {
}
bool syncAssets() {
LOGGER.info("Starting asset synchronization...");
LOG_I(TAG, "Starting asset synchronization...");
// Check if Data partition and SD card exist
bool dataExists = hasDataAssets();
@@ -325,26 +326,26 @@ bool syncAssets() {
// FIRST BOOT SCENARIO: Data has version 0, SD card is missing
if (dataExists && !sdExists) {
LOGGER.info("First boot - Data exists but SD card backup missing");
LOGGER.warn("Skipping SD backup during boot - will be created on first settings save");
LOGGER.warn("This avoids watchdog timeout if SD card is slow or corrupted");
LOG_I(TAG, "First boot - Data exists but SD card backup missing");
LOG_W(TAG, "Skipping SD backup during boot - will be created on first settings save");
LOG_W(TAG, "This avoids watchdog timeout if SD card is slow or corrupted");
return true; // Don't block boot - defer copy to runtime
}
// NO SD CARD: Just ensure Data has default structure
if (!sdExists) {
LOGGER.warn("No SD card available - creating default Data structure if needed");
LOG_W(TAG, "No SD card available - creating default Data structure if needed");
if (!dataExists) {
if (!file::findOrCreateDirectory(DATA_ASSETS_DIR, 0755)) {
LOGGER.error("Failed to create Data assets directory");
LOG_E(TAG, "Failed to create Data assets directory");
return false;
}
AssetVersion defaultVersion(0); // Start at version 0 - SD card updates will be version 1+
if (!saveDataVersion(defaultVersion)) {
LOGGER.error("Failed to save default Data version");
LOG_E(TAG, "Failed to save default Data version");
return false;
}
LOGGER.info("Created default Data assets structure (version 0)");
LOG_I(TAG, "Created default Data assets structure (version 0)");
}
return true;
}
@@ -355,39 +356,39 @@ bool syncAssets() {
bool hasSdVer = loadSdVersion(sdVersion);
if (!hasDataVer) {
LOGGER.warn("No Data version.json - assuming version 0");
LOG_W(TAG, "No Data version.json - assuming version 0");
dataVersion.version = 0;
if (!saveDataVersion(dataVersion)) {
LOGGER.warn("Failed to save default Data version (non-fatal)");
LOG_W(TAG, "Failed to save default Data version (non-fatal)");
}
}
if (!hasSdVer) {
LOGGER.warn("No SD version.json - assuming version 0");
LOG_W(TAG, "No SD version.json - assuming version 0");
sdVersion.version = 0;
// DON'T save to SD during boot - defer to runtime
LOGGER.warn("Skipping SD version.json creation during boot - will be created on first settings save");
LOG_W(TAG, "Skipping SD version.json creation during boot - will be created on first settings save");
}
LOGGER.info("Version comparison - Data: {}, SD: {}", dataVersion.version, sdVersion.version);
LOG_I(TAG, "Version comparison - Data: %u, SD: %u", (unsigned)dataVersion.version, (unsigned)sdVersion.version);
if (sdVersion.version > dataVersion.version) {
// Firmware update - copy SD -> Data
LOGGER.info("SD card newer (v{} > v{}) - copying assets SD -> Data (firmware update)",
sdVersion.version, dataVersion.version);
LOG_I(TAG, "SD card newer (v%u > v%u) - copying assets SD -> Data (firmware update)",
(unsigned)sdVersion.version, (unsigned)dataVersion.version);
if (!copyDirectory(SD_ASSETS_DIR, DATA_ASSETS_DIR)) {
LOGGER.error("Failed to copy assets from SD to Data");
LOG_E(TAG, "Failed to copy assets from SD to Data");
return false;
}
LOGGER.info("Firmware update complete - assets updated from SD card");
LOG_I(TAG, "Firmware update complete - assets updated from SD card");
} else if (dataVersion.version > sdVersion.version) {
// User customization - backup Data -> SD
LOGGER.warn("Data newer (v{} > v{}) - deferring SD backup to avoid boot watchdog",
dataVersion.version, sdVersion.version);
LOGGER.warn("SD backup will occur on first WebServer settings save");
LOG_W(TAG, "Data newer (v%u > v%u) - deferring SD backup to avoid boot watchdog",
(unsigned)dataVersion.version, (unsigned)sdVersion.version);
LOG_W(TAG, "SD backup will occur on first WebServer settings save");
return true; // Don't block boot - defer copy to runtime
} else {
LOGGER.info("Versions match (v{}) - no sync needed", dataVersion.version);
LOG_I(TAG, "Versions match (v%u) - no sync needed", (unsigned)dataVersion.version);
}
return true;
@@ -7,7 +7,6 @@
#include <Tactility/settings/WebServerSettings.h>
#include <Tactility/MountPoints.h>
#include <Tactility/file/File.h>
#include <Tactility/Logger.h>
#include <Tactility/lvgl/Statusbar.h>
#include <Tactility/Mutex.h>
@@ -52,9 +51,11 @@
#include <mbedtls/base64.h>
#include <sstream>
#include <tactility/log.h>
namespace tt::service::webserver {
static const auto LOGGER = tt::Logger("WebServerService");
constexpr auto* TAG = "WebServerService";
// Helper to convert chip model enum to human-readable string
static const char* getChipModelName(esp_chip_model_t model) {
@@ -142,14 +143,14 @@ static esp_err_t validateRequestAuth(httpd_req_t* request, bool& authPassed) {
std::string auth_header(auth_len + 1, '\0');
if (httpd_req_get_hdr_value_str(request, "Authorization", auth_header.data(), auth_len + 1) != ESP_OK) {
LOGGER.warn("Failed to read Authorization header");
LOG_W(TAG, "Failed to read Authorization header");
return sendUnauthorized(request, "Authorization required");
}
auth_header.resize(auth_len); // Remove null terminator from string length
// Check for "Basic " prefix
if (auth_header.rfind("Basic ", 0) != 0) {
LOGGER.warn("Authorization header is not Basic auth");
LOG_W(TAG, "Authorization header is not Basic auth");
return sendUnauthorized(request, "Basic authorization required");
}
@@ -170,7 +171,7 @@ static esp_err_t validateRequestAuth(httpd_req_t* request, bool& authPassed) {
reinterpret_cast<const unsigned char*>(base64_creds.c_str()),
base64_creds.length());
if (ret != 0) {
LOGGER.warn("Failed to decode base64 credentials");
LOG_W(TAG, "Failed to decode base64 credentials");
return sendUnauthorized(request, "Invalid credentials format");
}
decoded.resize(actual_len);
@@ -178,7 +179,7 @@ static esp_err_t validateRequestAuth(httpd_req_t* request, bool& authPassed) {
// Parse username:password
size_t colon_pos = decoded.find(':');
if (colon_pos == std::string::npos) {
LOGGER.warn("Invalid credentials format (no colon separator)");
LOG_W(TAG, "Invalid credentials format (no colon separator)");
return sendUnauthorized(request, "Invalid credentials format");
}
@@ -189,7 +190,7 @@ static esp_err_t validateRequestAuth(httpd_req_t* request, bool& authPassed) {
bool usernameMatch = secureCompare(username, settings.webServerUsername);
bool passwordMatch = secureCompare(password, settings.webServerPassword);
if (!usernameMatch || !passwordMatch) {
LOGGER.warn("Invalid credentials for user '{}'", username);
LOG_W(TAG, "Invalid credentials for user '%s'", username.c_str());
return sendUnauthorized(request, "Invalid credentials");
}
@@ -198,7 +199,7 @@ static esp_err_t validateRequestAuth(httpd_req_t* request, bool& authPassed) {
}
bool WebServerService::onStart(ServiceContext& service) {
LOGGER.info("Starting WebServer service...");
LOG_I(TAG, "Starting WebServer service...");
// Register global instance
g_webServerInstance.store(this);
@@ -228,10 +229,10 @@ bool WebServerService::onStart(ServiceContext& service) {
// Start HTTP server only if enabled in settings (default: OFF to save memory)
if (serverEnabled) {
LOGGER.info("WebServer enabled in settings, starting HTTP server...");
LOG_I(TAG, "WebServer enabled in settings, starting HTTP server...");
setEnabled(true);
} else {
LOGGER.info("WebServer disabled in settings, NOT starting HTTP server (saves ~10KB RAM)");
LOG_I(TAG, "WebServer disabled in settings, NOT starting HTTP server (saves ~10KB RAM)");
setEnabled(false);
}
@@ -288,15 +289,15 @@ bool WebServerService::startApMode() {
}
if (settings.wifiMode != settings::webserver::WiFiMode::AccessPoint) {
LOGGER.info("Not in AP mode, skipping AP WiFi initialization");
LOG_I(TAG, "Not in AP mode, skipping AP WiFi initialization");
return true; // Not an error, just not needed
}
LOGGER.info("Starting WiFi in Access Point mode...");
LOG_I(TAG, "Starting WiFi in Access Point mode...");
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
if (esp_wifi_init(&cfg) != ESP_OK) {
LOGGER.error("esp_wifi_init() failed");
LOG_E(TAG, "esp_wifi_init() failed");
return false;
}
apWifiInitialized = true;
@@ -304,14 +305,14 @@ bool WebServerService::startApMode() {
// Create the AP network interface
apNetif = esp_netif_create_default_wifi_ap();
if (apNetif == nullptr) {
LOGGER.error("esp_netif_create_default_wifi_ap() failed");
LOG_E(TAG, "esp_netif_create_default_wifi_ap() failed");
esp_wifi_deinit();
apWifiInitialized = false;
return false;
}
if (esp_wifi_set_mode(WIFI_MODE_AP) != ESP_OK) {
LOGGER.error("esp_wifi_set_mode(AP) failed");
LOG_E(TAG, "esp_wifi_set_mode(AP) failed");
stopApMode();
return false;
}
@@ -324,19 +325,19 @@ bool WebServerService::startApMode() {
ip_info.netmask.addr = ipaddr_addr("255.255.255.0");
if (esp_netif_dhcps_stop(apNetif) != ESP_OK) {
LOGGER.error("esp_netif_dhcps_stop() failed");
LOG_E(TAG, "esp_netif_dhcps_stop() failed");
stopApMode();
return false;
}
if (esp_netif_set_ip_info(apNetif, &ip_info) != ESP_OK) {
LOGGER.error("esp_netif_set_ip_info() failed");
LOG_E(TAG, "esp_netif_set_ip_info() failed");
stopApMode();
return false;
}
if (esp_netif_dhcps_start(apNetif) != ESP_OK) {
LOGGER.error("esp_netif_dhcps_start() failed");
LOG_E(TAG, "esp_netif_dhcps_start() failed");
stopApMode();
return false;
}
@@ -354,36 +355,36 @@ bool WebServerService::startApMode() {
if (settings.apOpenNetwork) {
// User explicitly chose an open network
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
LOGGER.info("AP configured with OPEN authentication (user choice)");
LOG_I(TAG, "AP configured with OPEN authentication (user choice)");
} else if (settings.apPassword.length() >= 8 && settings.apPassword.length() <= 63) {
wifi_config.ap.authmode = WIFI_AUTH_WPA2_PSK;
strncpy(reinterpret_cast<char*>(wifi_config.ap.password), settings.apPassword.c_str(), sizeof(wifi_config.ap.password) - 1);
wifi_config.ap.password[sizeof(wifi_config.ap.password) - 1] = '\0';
LOGGER.info("AP configured with WPA2-PSK authentication");
LOG_I(TAG, "AP configured with WPA2-PSK authentication");
} else {
if (!settings.apPassword.empty()) {
LOGGER.warn("AP password invalid (must be 8-63 chars, got {}) - using OPEN mode", settings.apPassword.length());
LOG_W(TAG, "AP password invalid (must be 8-63 chars, got %zu) - using OPEN mode", settings.apPassword.length());
}
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
LOGGER.warn("AP configured with OPEN authentication (no password)");
LOG_W(TAG, "AP configured with OPEN authentication (no password)");
}
wifi_config.ap.max_connection = 4;
wifi_config.ap.channel = settings.apChannel;
if (esp_wifi_set_config(WIFI_IF_AP, &wifi_config) != ESP_OK) {
LOGGER.error("esp_wifi_set_config(AP) failed");
LOG_E(TAG, "esp_wifi_set_config(AP) failed");
stopApMode();
return false;
}
if (esp_wifi_start() != ESP_OK) {
LOGGER.error("esp_wifi_start() failed");
LOG_E(TAG, "esp_wifi_start() failed");
stopApMode();
return false;
}
LOGGER.info("WiFi AP started - SSID: '{}', Channel: {}, IP: 192.168.4.1", settings.apSsid, settings.apChannel);
LOG_I(TAG, "WiFi AP started - SSID: '%s', Channel: %u, IP: 192.168.4.1", settings.apSsid.c_str(), (unsigned)settings.apChannel);
return true;
}
@@ -395,15 +396,15 @@ void WebServerService::stopApMode() {
}
err = esp_wifi_stop();
if (err != ESP_OK && err != ESP_ERR_WIFI_NOT_STARTED) {
LOGGER.warn("esp_wifi_stop() in cleanup: {}", esp_err_to_name(err));
LOG_W(TAG, "esp_wifi_stop() in cleanup: %s", esp_err_to_name(err));
}
LOGGER.info("WiFi AP stopped");
LOG_I(TAG, "WiFi AP stopped");
err = esp_wifi_set_mode(WIFI_MODE_STA);
if (err != ESP_OK) {
LOGGER.warn("esp_wifi_set_mode() in cleanup: {}", esp_err_to_name(err));
LOG_W(TAG, "esp_wifi_set_mode() in cleanup: %s", esp_err_to_name(err));
}
LOGGER.info("Wifi mode set back to STA");
LOG_I(TAG, "Wifi mode set back to STA");
apWifiInitialized = false;
}
@@ -428,7 +429,7 @@ bool WebServerService::startServer() {
// Start AP mode WiFi if configured
if (settings.wifiMode == settings::webserver::WiFiMode::AccessPoint) {
if (!startApMode()) {
LOGGER.error("Failed to start AP mode WiFi - HTTP server will not start");
LOG_E(TAG, "Failed to start AP mode WiFi - HTTP server will not start");
return false;
}
}
@@ -505,19 +506,19 @@ bool WebServerService::startServer() {
httpServer->start();
if (!httpServer->isStarted()) {
LOGGER.error("Failed to start HTTP server on port {}", settings.webServerPort);
LOG_E(TAG, "Failed to start HTTP server on port %u", (unsigned)settings.webServerPort);
httpServer.reset();
return false;
}
LOGGER.info("HTTP server started successfully on port {}", settings.webServerPort);
LOG_I(TAG, "HTTP server started successfully on port %u", (unsigned)settings.webServerPort);
publish_event(this, WebServerEvent::WebServerStarted);
// Show statusbar icon
if (statusbarIconId >= 0) {
lvgl::statusbar_icon_set_image(statusbarIconId, LVGL_ICON_STATUSBAR_CLOUD);
lvgl::statusbar_icon_set_visibility(statusbarIconId, true);
LOGGER.info("WebServer statusbar icon shown ({} mode)",
LOG_I(TAG, "WebServer statusbar icon shown (%s mode)",
settings.wifiMode == settings::webserver::WiFiMode::AccessPoint ? "AP" : "Station");
}
@@ -537,7 +538,7 @@ void WebServerService::stopServer() {
stopApMode();
}
LOGGER.info("HTTP server stopped");
LOG_I(TAG, "HTTP server stopped");
publish_event(this, WebServerEvent::WebServerStopped);
if (statusbarIconId >= 0) {
@@ -550,7 +551,7 @@ void WebServerService::stopServer() {
esp_err_t WebServerService::handleRoot(httpd_req_t* request) {
LOGGER.info("GET / -> redirecting to /dashboard.html");
LOG_I(TAG, "GET / -> redirecting to /dashboard.html");
httpd_resp_set_status(request, "302 Found");
httpd_resp_set_hdr(request, "Location", "/dashboard.html");
return httpd_resp_send(request, nullptr, 0);
@@ -722,7 +723,7 @@ static bool uriMatches(const char* uri, const char* route) {
}
esp_err_t WebServerService::handleFileBrowser(httpd_req_t* request) {
LOGGER.info("GET /filebrowser -> redirecting to /dashboard.html#files");
LOG_I(TAG, "GET /filebrowser -> redirecting to /dashboard.html#files");
httpd_resp_set_status(request, "302 Found");
httpd_resp_set_hdr(request, "Location", "/dashboard.html#files");
return httpd_resp_send(request, nullptr, 0);
@@ -735,17 +736,17 @@ esp_err_t WebServerService::handleFsList(httpd_req_t* request) {
if (qlen > 1) {
std::unique_ptr<char[]> qbuf(new char[qlen]);
if (httpd_req_get_url_query_str(request, qbuf.get(), qlen) == ESP_OK) {
LOGGER.info("GET /fs/list raw query: {}", qbuf.get());
LOG_I(TAG, "GET /fs/list raw query: %s", qbuf.get());
}
}
if (!getQueryParam(request, "path", path) || path.empty()) path = "/";
std::string norm = normalizePath(path);
LOGGER.info("GET /fs/list decoded path: '{}' normalized: '{}'", path, norm);
LOG_I(TAG, "GET /fs/list decoded path: '%s' normalized: '%s'", path.c_str(), norm.c_str());
// Allow root path for listing mount points
if (!isAllowedBasePath(norm, true)) {
LOGGER.warn("GET /fs/list - invalid path requested: '{}' normalized: '{}'", path, norm);
LOG_W(TAG, "GET /fs/list - invalid path requested: '%s' normalized: '%s'", path.c_str(), norm.c_str());
httpd_resp_set_type(request, "application/json");
httpd_resp_sendstr(request, "{\"error\":\"invalid path\"}");
return ESP_OK;
@@ -811,7 +812,7 @@ esp_err_t WebServerService::handleFsDownload(httpd_req_t* request) {
}
std::string norm = normalizePath(path);
if (!isAllowedBasePath(norm) || !file::isFile(norm)) {
LOGGER.warn("GET /fs/download - not found or invalid path: '{}' normalized: '{}'", path, norm);
LOG_W(TAG, "GET /fs/download - not found or invalid path: '%s' normalized: '%s'", path.c_str(), norm.c_str());
httpd_resp_send_err(request, HTTPD_404_NOT_FOUND, "not found");
return ESP_FAIL;
}
@@ -859,7 +860,7 @@ esp_err_t WebServerService::handleFsUpload(httpd_req_t* request) {
if (qlen > 1) {
std::unique_ptr<char[]> qbuf(new char[qlen]);
if (httpd_req_get_url_query_str(request, qbuf.get(), qlen) == ESP_OK) {
LOGGER.info("POST /fs/upload raw query: {}", qbuf.get());
LOG_I(TAG, "POST /fs/upload raw query: %s", qbuf.get());
}
}
@@ -872,10 +873,10 @@ esp_err_t WebServerService::handleFsUpload(httpd_req_t* request) {
char content_type[64] = {0};
httpd_req_get_hdr_value_str(request, "Content-Type", content_type, sizeof(content_type));
std::string norm = normalizePath(path);
LOGGER.info("POST /fs/upload decoded path: '{}' normalized: '{}' Content-Length: {} Content-Type: {}", path, norm, (int)request->content_len, content_type[0] ? content_type : "(null)");
LOG_I(TAG, "POST /fs/upload decoded path: '%s' normalized: '%s' Content-Length: %d Content-Type: %s", path.c_str(), norm.c_str(), (int)request->content_len, content_type[0] ? content_type : "(null)");
if (!isAllowedBasePath(norm)) {
LOGGER.warn("POST /fs/upload - invalid path requested: '{}' normalized: '{}'", path, norm);
LOG_W(TAG, "POST /fs/upload - invalid path requested: '%s' normalized: '%s'", path.c_str(), norm.c_str());
httpd_resp_send_err(request, HTTPD_403_FORBIDDEN, "invalid path");
return ESP_FAIL;
}
@@ -902,18 +903,18 @@ esp_err_t WebServerService::handleFsUpload(httpd_req_t* request) {
// Timeout - retry with backoff
timeout_retries++;
if (timeout_retries >= MAX_TIMEOUT_RETRIES) {
LOGGER.error("Upload recv timeout after {} retries", timeout_retries);
LOG_E(TAG, "Upload recv timeout after %d retries", timeout_retries);
fclose(fp);
remove(norm.c_str()); // Clean up partial file
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "recv timeout");
return ESP_FAIL;
}
LOGGER.warn("Upload recv timeout, retry {}/{}", timeout_retries, MAX_TIMEOUT_RETRIES);
LOG_W(TAG, "Upload recv timeout, retry %d/%d", timeout_retries, MAX_TIMEOUT_RETRIES);
vTaskDelay(pdMS_TO_TICKS(100 * timeout_retries)); // Linear backoff
continue;
}
if (ret <= 0) {
LOGGER.error("Upload recv failed with error {}", ret);
LOG_E(TAG, "Upload recv failed with error %d", ret);
fclose(fp);
remove(norm.c_str()); // Clean up partial file
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "recv failed");
@@ -951,7 +952,7 @@ esp_err_t WebServerService::handleFsGenericGet(httpd_req_t* request) {
if (uriMatches(uri, "/fs/list")) return handleFsList(request);
if (uriMatches(uri, "/fs/download")) return handleFsDownload(request);
if (uriMatches(uri, "/fs/tree")) return handleFsTree(request);
LOGGER.warn("GET {} - not found in fs generic dispatcher", uri);
LOG_W(TAG, "GET %s - not found in fs generic dispatcher", uri);
httpd_resp_send_err(request, HTTPD_404_NOT_FOUND, "not found");
return ESP_FAIL;
}
@@ -970,7 +971,7 @@ esp_err_t WebServerService::handleFsGenericPost(httpd_req_t* request) {
if (uriMatches(uri, "/fs/delete")) return handleFsDelete(request);
if (uriMatches(uri, "/fs/rename")) return handleFsRename(request);
if (uriMatches(uri, "/fs/upload")) return handleFsUpload(request);
LOGGER.warn("POST {} - not found in fs generic dispatcher", uri);
LOG_W(TAG, "POST %s - not found in fs generic dispatcher", uri);
httpd_resp_send_err(request, HTTPD_404_NOT_FOUND, "not found");
return ESP_FAIL;
}
@@ -986,7 +987,7 @@ esp_err_t WebServerService::handleAdminPost(httpd_req_t* request) {
const char* uri = request->uri;
if (strncmp(uri, "/admin/reboot", 13) == 0) return handleReboot(request);
LOGGER.info("POST {} - not found in admin dispatcher", uri);
LOG_I(TAG, "POST %s - not found in admin dispatcher", uri);
httpd_resp_send_err(request, HTTPD_404_NOT_FOUND, "not found");
return ESP_FAIL;
}
@@ -1019,7 +1020,7 @@ esp_err_t WebServerService::handleApiGet(httpd_req_t* request) {
return handleApiScreenshot(request);
}
LOGGER.warn("GET {} - not found in api dispatcher", uri);
LOG_W(TAG, "GET %s - not found in api dispatcher", uri);
httpd_resp_send_err(request, HTTPD_404_NOT_FOUND, "not found");
return ESP_FAIL;
}
@@ -1040,7 +1041,7 @@ esp_err_t WebServerService::handleApiPost(httpd_req_t* request) {
return handleApiAppsUninstall(request);
}
LOGGER.warn("POST {} - not found in api dispatcher", uri);
LOG_W(TAG, "POST %s - not found in api dispatcher", uri);
httpd_resp_send_err(request, HTTPD_404_NOT_FOUND, "not found");
return ESP_FAIL;
}
@@ -1058,13 +1059,13 @@ esp_err_t WebServerService::handleApiPut(httpd_req_t* request) {
return handleApiAppsInstall(request);
}
LOGGER.warn("PUT {} - not found in api dispatcher", uri);
LOG_W(TAG, "PUT %s - not found in api dispatcher", uri);
httpd_resp_send_err(request, HTTPD_404_NOT_FOUND, "not found");
return ESP_FAIL;
}
esp_err_t WebServerService::handleApiSysinfo(httpd_req_t* request) {
LOGGER.info("GET /api/sysinfo");
LOG_I(TAG, "GET /api/sysinfo");
std::ostringstream json;
json << "{";
@@ -1214,7 +1215,7 @@ esp_err_t WebServerService::handleApiSysinfo(httpd_req_t* request) {
// GET /api/apps - List installed apps
esp_err_t WebServerService::handleApiApps(httpd_req_t* request) {
LOGGER.info("GET /api/apps");
LOG_I(TAG, "GET /api/apps");
auto manifests = app::getAppManifests();
@@ -1255,7 +1256,7 @@ esp_err_t WebServerService::handleApiApps(httpd_req_t* request) {
// POST /api/apps/run?id=xxx - Run an app
esp_err_t WebServerService::handleApiAppsRun(httpd_req_t* request) {
LOGGER.info("POST /api/apps/run");
LOG_I(TAG, "POST /api/apps/run");
std::string appId;
if (!getQueryParam(request, "id", appId) || appId.empty()) {
@@ -1276,14 +1277,14 @@ esp_err_t WebServerService::handleApiAppsRun(httpd_req_t* request) {
app::start(appId);
LOGGER.info("[200] /api/apps/run {}", appId);
LOG_I(TAG, "[200] /api/apps/run %s", appId.c_str());
httpd_resp_sendstr(request, "ok");
return ESP_OK;
}
// POST /api/apps/uninstall?id=xxx - Uninstall an app
esp_err_t WebServerService::handleApiAppsUninstall(httpd_req_t* request) {
LOGGER.info("POST /api/apps/uninstall");
LOG_I(TAG, "POST /api/apps/uninstall");
std::string appId;
if (!getQueryParam(request, "id", appId) || appId.empty()) {
@@ -1293,7 +1294,7 @@ esp_err_t WebServerService::handleApiAppsUninstall(httpd_req_t* request) {
auto manifest = app::findAppManifestById(appId);
if (!manifest) {
LOGGER.info("[200] /api/apps/uninstall {} (app wasn't installed)", appId);
LOG_I(TAG, "[200] /api/apps/uninstall %s (app wasn't installed)", appId.c_str());
httpd_resp_sendstr(request, "ok");
return ESP_OK;
}
@@ -1305,11 +1306,11 @@ esp_err_t WebServerService::handleApiAppsUninstall(httpd_req_t* request) {
}
if (app::uninstall(appId)) {
LOGGER.info("[200] /api/apps/uninstall {}", appId);
LOG_I(TAG, "[200] /api/apps/uninstall %s", appId.c_str());
httpd_resp_sendstr(request, "ok");
return ESP_OK;
} else {
LOGGER.warn("[500] /api/apps/uninstall {}", appId);
LOG_W(TAG, "[500] /api/apps/uninstall %s", appId.c_str());
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "uninstall failed");
return ESP_FAIL;
}
@@ -1317,7 +1318,7 @@ esp_err_t WebServerService::handleApiAppsUninstall(httpd_req_t* request) {
// PUT /api/apps/install - Install an app from multipart form upload
esp_err_t WebServerService::handleApiAppsInstall(httpd_req_t* request) {
LOGGER.info("PUT /api/apps/install");
LOG_I(TAG, "PUT /api/apps/install");
std::string boundary;
if (!network::getMultiPartBoundaryOrSendError(request, boundary)) {
@@ -1340,14 +1341,14 @@ esp_err_t WebServerService::handleApiAppsInstall(httpd_req_t* request) {
auto content_disposition_map = network::parseContentDisposition(content_headers);
if (content_disposition_map.empty()) {
LOGGER.warn("parseContentDisposition returned empty map for: {}", content_headers_data);
LOG_W(TAG, "parseContentDisposition returned empty map for: %s", content_headers_data.c_str());
httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "invalid content disposition");
return ESP_FAIL;
}
auto filename_entry = content_disposition_map.find("filename");
if (filename_entry == content_disposition_map.end()) {
LOGGER.warn("filename not found in content disposition map");
LOG_W(TAG, "filename not found in content disposition map");
httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "filename parameter missing");
return ESP_FAIL;
}
@@ -1402,10 +1403,10 @@ esp_err_t WebServerService::handleApiAppsInstall(httpd_req_t* request) {
// Cleanup temp file
if (!file::deleteFile(file_path)) {
LOGGER.warn("Failed to delete temp file {}", file_path);
LOG_W(TAG, "Failed to delete temp file %s", file_path.c_str());
}
LOGGER.info("[200] /api/apps/install -> {}", file_path);
LOG_I(TAG, "[200] /api/apps/install -> %s", file_path.c_str());
httpd_resp_sendstr(request, "ok");
return ESP_OK;
}
@@ -1425,7 +1426,7 @@ static const char* radioStateToJsonString(wifi::RadioState state) {
// GET /api/wifi - WiFi status
esp_err_t WebServerService::handleApiWifi(httpd_req_t* request) {
LOGGER.info("GET /api/wifi");
LOG_I(TAG, "GET /api/wifi");
auto state = wifi::getRadioState();
auto ip = wifi::getIp();
@@ -1450,7 +1451,7 @@ esp_err_t WebServerService::handleApiWifi(httpd_req_t* request) {
// GET /api/screenshot - Capture and return screenshot as PNG
// Screenshots are saved to SD card root (if available) or /data with incrementing numbers
esp_err_t WebServerService::handleApiScreenshot(httpd_req_t* request) {
LOGGER.info("GET /api/screenshot");
LOG_I(TAG, "GET /api/screenshot");
#if TT_FEATURE_SCREENSHOT_ENABLED
// Determine save location: prefer SD card root if mounted, otherwise /data
@@ -1471,7 +1472,7 @@ esp_err_t WebServerService::handleApiScreenshot(httpd_req_t* request) {
return ESP_FAIL;
}
LOGGER.info("Screenshot will be saved to: {}", screenshot_path);
LOG_I(TAG, "Screenshot will be saved to: %s", screenshot_path.c_str());
// LVGL's lodepng uses lv_fs which requires the "A:" prefix
std::string lvgl_screenshot_path = lvgl::PATH_PREFIX + screenshot_path;
@@ -1482,13 +1483,13 @@ esp_err_t WebServerService::handleApiScreenshot(httpd_req_t* request) {
lvgl::unlock();
if (!success) {
LOGGER.error("lv_screenshot_create failed for path: {}", lvgl_screenshot_path);
LOG_E(TAG, "lv_screenshot_create failed for path: %s", lvgl_screenshot_path.c_str());
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "screenshot capture failed");
return ESP_FAIL;
}
LOGGER.info("Screenshot captured successfully");
LOG_I(TAG, "Screenshot captured successfully");
} else {
LOGGER.error("Could not acquire LVGL lock within 100ms");
LOG_E(TAG, "Could not acquire LVGL lock within 100ms");
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "could not acquire LVGL lock");
return ESP_FAIL;
}
@@ -1514,7 +1515,7 @@ esp_err_t WebServerService::handleApiScreenshot(httpd_req_t* request) {
httpd_resp_send_chunk(request, nullptr, 0);
// File is kept on storage (not deleted) for user access
LOGGER.info("[200] /api/screenshot -> {}", screenshot_path);
LOG_I(TAG, "[200] /api/screenshot -> %s", screenshot_path.c_str());
return ESP_OK;
#else
httpd_resp_send_err(request, HTTPD_501_METHOD_NOT_IMPLEMENTED, "screenshot feature not enabled");
@@ -1524,7 +1525,7 @@ esp_err_t WebServerService::handleApiScreenshot(httpd_req_t* request) {
esp_err_t WebServerService::handleFsTree(httpd_req_t* request) {
LOGGER.info("GET /fs/tree");
LOG_I(TAG, "GET /fs/tree");
std::ostringstream json;
json << "{";
@@ -1569,7 +1570,7 @@ esp_err_t WebServerService::handleFsMkdir(httpd_req_t* request) {
return ESP_FAIL;
}
std::string norm = normalizePath(path);
LOGGER.info("POST /fs/mkdir requested: '{}' normalized: '{}'", path, norm);
LOG_I(TAG, "POST /fs/mkdir requested: '%s' normalized: '%s'", path.c_str(), norm.c_str());
if (!isAllowedBasePath(norm)) {
httpd_resp_send_err(request, HTTPD_403_FORBIDDEN, "invalid path");
return ESP_FAIL;
@@ -1592,7 +1593,7 @@ esp_err_t WebServerService::handleFsDelete(httpd_req_t* request) {
return ESP_FAIL;
}
std::string norm = normalizePath(path);
LOGGER.info("POST /fs/delete requested: '{}' normalized: '{}'", path, norm);
LOG_I(TAG, "POST /fs/delete requested: '%s' normalized: '%s'", path.c_str(), norm.c_str());
if (!isAllowedBasePath(norm)) {
httpd_resp_send_err(request, HTTPD_403_FORBIDDEN, "invalid path");
return ESP_FAIL;
@@ -1623,7 +1624,7 @@ esp_err_t WebServerService::handleFsRename(httpd_req_t* request) {
return ESP_FAIL;
}
std::string norm = normalizePath(path);
LOGGER.info("POST /fs/rename requested: '{}' normalized: '{}' -> newName: '{}'", path.c_str(), norm.c_str(), newName.c_str());
LOG_I(TAG, "POST /fs/rename requested: '%s' normalized: '%s' -> newName: '%s'", path.c_str(), norm.c_str(), newName.c_str());
if (!isAllowedBasePath(norm)) {
httpd_resp_send_err(request, HTTPD_403_FORBIDDEN, "invalid path");
return ESP_FAIL;
@@ -1662,7 +1663,7 @@ esp_err_t WebServerService::handleFsRename(httpd_req_t* request) {
int r = rename(norm.c_str(), target.c_str());
if (r != 0) {
int e = errno;
LOGGER.warn("rename failed errno={} ({}) -> {} -> {}", e, strerror(e), norm, target);
LOG_W(TAG, "rename failed errno=%d (%s) -> %s -> %s", e, strerror(e), norm.c_str(), target.c_str());
// Return errno string to client to aid debugging
std::string msg = std::string("rename failed: ") + strerror(e);
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, msg.c_str());
@@ -1676,7 +1677,7 @@ esp_err_t WebServerService::handleFsRename(httpd_req_t* request) {
esp_err_t WebServerService::handleReboot(httpd_req_t* request) {
LOGGER.info("POST /reboot");
LOG_I(TAG, "POST /reboot");
httpd_resp_sendstr(request, "Rebooting...");
// Reboot after a short delay to allow response to be sent
@@ -1695,7 +1696,7 @@ esp_err_t WebServerService::handleAssets(httpd_req_t* request) {
}
const char* uri = request->uri;
LOGGER.info("GET {}", uri);
LOG_I(TAG, "GET %s", uri);
// Special case: serve favicon from system assets
if (strcmp(uri, "/favicon.ico") == 0) {
@@ -1721,7 +1722,7 @@ esp_err_t WebServerService::handleAssets(httpd_req_t* request) {
fclose(fp);
lock->unlock();
httpd_resp_send_chunk(request, nullptr, 0);
LOGGER.info("[200] {} (favicon)", uri);
LOG_I(TAG, "[200] %s (favicon)", uri);
return ESP_OK;
}
lock->unlock();
@@ -1745,7 +1746,7 @@ esp_err_t WebServerService::handleAssets(httpd_req_t* request) {
std::string dataPath = std::string("/system/app/WebServer") + requestedPath;
if (requestedPath == "/dashboard.html" && !file::isFile(dataPath.c_str())) {
LOGGER.info("dashboard.html not found, serving default.html");
LOG_I(TAG, "dashboard.html not found, serving default.html");
}
// Try to serve from Data partition first
@@ -1771,7 +1772,7 @@ esp_err_t WebServerService::handleAssets(httpd_req_t* request) {
lock->unlock();
httpd_resp_send_chunk(request, nullptr, 0); // End of chunks
LOGGER.info("[200] {} (from Data)", uri);
LOG_I(TAG, "[200] %s (from Data)", uri);
return ESP_OK;
}
lock->unlock();
@@ -1800,14 +1801,14 @@ esp_err_t WebServerService::handleAssets(httpd_req_t* request) {
lock->unlock();
httpd_resp_send_chunk(request, nullptr, 0); // End of chunks
LOGGER.info("[200] {} (from SD)", uri);
LOG_I(TAG, "[200] %s (from SD)", uri);
return ESP_OK;
}
lock->unlock();
}
// File not found
LOGGER.warn("[404] {}", uri);
LOG_W(TAG, "[404] %s", uri);
httpd_resp_send_err(request, HTTPD_404_NOT_FOUND, "File not found");
return ESP_FAIL;
}
@@ -1823,7 +1824,7 @@ void setWebServerEnabled(bool enabled) {
instance->setEnabled(enabled);
// Don't log here - startServer()/stopServer() already log the actual result
} else {
LOGGER.warn("WebServer service not available, cannot {}", enabled ? "start" : "stop");
LOG_W(TAG, "WebServer service not available, cannot %s", enabled ? "start" : "stop");
}
}