diff --git a/README.md b/README.md index 6c4681a..a86092e 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,13 @@ GPIO pins can be configured in `main/config.h`. Modify the `bmc_gpio_defaults` a | POST | `/api/ota/update` | Start OTA update from URL | | POST | `/api/ota/upload` | Upload firmware file directly | +### Logs Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/logs` | Get system logs | +| POST | `/api/logs/clear` | Clear system logs | + ## Web Interface Access the web interface by navigating to `http:///` in your browser. @@ -145,6 +152,7 @@ Features: - Serial console with WebSocket support - System information display - OTA firmware update +- System logs viewer ## Example Usage diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 5b7b156..9d3938d 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -4,6 +4,7 @@ idf_component_register(SRCS "main.c" "ethernet_manager.c" "web_server.c" "ota_handler.c" + "log_handler.c" INCLUDE_DIRS "." REQUIRES esp_driver_gpio esp_driver_uart esp_driver_spi esp_http_server esp_eth esp_netif lwip spi_flash nvs_flash log diff --git a/main/html/index.html b/main/html/index.html index 42986e1..d092283 100644 --- a/main/html/index.html +++ b/main/html/index.html @@ -688,6 +688,23 @@ + +
+ +
+

📋 System Logs

+
+
+
Loading logs...
+
+
+ + + +
+
+
+
@@ -1218,12 +1235,61 @@ } }); + // Initialize logs + refreshLogs(); + // Periodic updates setInterval(updateSystemInfo, 5000); setInterval(updateGPIOStates, 2000); setInterval(updatePowerStatus, 2000); setInterval(updateOTAStatus, 5000); }); + + // Log viewer functions + let logsAutoRefreshInterval = null; + + async function refreshLogs() { + try { + const response = await fetch(`${API_BASE}/api/logs`); + const logs = await response.text(); + const logsOutput = document.getElementById('logs-output'); + logsOutput.innerHTML = `
${escapeHtml(logs)}
`; + // Scroll to bottom + logsOutput.scrollTop = logsOutput.scrollHeight; + } catch (error) { + console.error('Failed to fetch logs:', error); + document.getElementById('logs-output').innerHTML = '
Failed to load logs
'; + } + } + + async function clearLogs() { + try { + await fetch(`${API_BASE}/api/logs/clear`, { method: 'POST' }); + refreshLogs(); + showToast('Logs cleared', 'success'); + } catch (error) { + console.error('Failed to clear logs:', error); + showToast('Failed to clear logs', 'error'); + } + } + + function toggleAutoRefreshLogs() { + const checkbox = document.getElementById('auto-refresh-logs'); + if (checkbox.checked) { + logsAutoRefreshInterval = setInterval(refreshLogs, 2000); + } else { + if (logsAutoRefreshInterval) { + clearInterval(logsAutoRefreshInterval); + logsAutoRefreshInterval = null; + } + } + } + + function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } diff --git a/main/log_handler.c b/main/log_handler.c new file mode 100644 index 0000000..47c9ebb --- /dev/null +++ b/main/log_handler.c @@ -0,0 +1,138 @@ +#include "log_handler.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include + +static const char *TAG = "LOG_HANDLER"; + +// Ring buffer for logs +#define LOG_BUFFER_SIZE 8192 +static char log_buffer[LOG_BUFFER_SIZE]; +static size_t log_write_pos = 0; +static size_t log_read_pos = 0; +static size_t log_count = 0; +static SemaphoreHandle_t log_mutex = NULL; +static bool log_initialized = false; + +// Vprintf callback for capturing logs +static int log_vprintf_cb(const char *fmt, va_list args) { + // First, call the default vprintf to output to console + int ret = vprintf(fmt, args); + + // Then, capture to our ring buffer + if (log_mutex && log_initialized) { + char temp_buf[256]; + int len = vsnprintf(temp_buf, sizeof(temp_buf), fmt, args); + if (len > 0 && len < (int)sizeof(temp_buf)) { + xSemaphoreTake(log_mutex, portMAX_DELAY); + + for (int i = 0; i < len; i++) { + log_buffer[log_write_pos] = temp_buf[i]; + log_write_pos = (log_write_pos + 1) % LOG_BUFFER_SIZE; + + if (log_count < LOG_BUFFER_SIZE) { + log_count++; + } else { + // Buffer full, advance read position + log_read_pos = (log_read_pos + 1) % LOG_BUFFER_SIZE; + } + } + + xSemaphoreGive(log_mutex); + } + } + + return ret; +} + +esp_err_t log_handler_init(void) { + if (log_initialized) { + ESP_LOGW(TAG, "Log handler already initialized"); + return ESP_OK; + } + + ESP_LOGI(TAG, "Initializing log handler"); + + log_mutex = xSemaphoreCreateMutex(); + if (!log_mutex) { + ESP_LOGE(TAG, "Failed to create log mutex"); + return ESP_ERR_NO_MEM; + } + + log_write_pos = 0; + log_read_pos = 0; + log_count = 0; + memset(log_buffer, 0, LOG_BUFFER_SIZE); + + // Set our custom vprintf function + esp_log_set_vprintf(log_vprintf_cb); + + log_initialized = true; + ESP_LOGI(TAG, "Log handler initialized"); + + return ESP_OK; +} + +esp_err_t log_handler_deinit(void) { + if (!log_initialized) { + return ESP_OK; + } + + ESP_LOGI(TAG, "Deinitializing log handler"); + + // Restore default vprintf + esp_log_set_vprintf(vprintf); + + if (log_mutex) { + vSemaphoreDelete(log_mutex); + log_mutex = NULL; + } + + log_initialized = false; + return ESP_OK; +} + +size_t log_get_buffer(char *buf, size_t buf_len) { + if (!buf || buf_len == 0 || !log_mutex) { + return 0; + } + + xSemaphoreTake(log_mutex, portMAX_DELAY); + + size_t to_copy = (log_count < buf_len) ? log_count : buf_len; + size_t copied = 0; + + for (size_t i = 0; i < to_copy; i++) { + size_t pos = (log_read_pos + i) % LOG_BUFFER_SIZE; + buf[copied++] = log_buffer[pos]; + } + + xSemaphoreGive(log_mutex); + + return copied; +} + +void log_clear_buffer(void) { + if (!log_mutex) { + return; + } + + xSemaphoreTake(log_mutex, portMAX_DELAY); + log_write_pos = 0; + log_read_pos = 0; + log_count = 0; + xSemaphoreGive(log_mutex); +} + +size_t log_get_size(void) { + if (!log_mutex) { + return 0; + } + + xSemaphoreTake(log_mutex, portMAX_DELAY); + size_t count = log_count; + xSemaphoreGive(log_mutex); + + return count; +} diff --git a/main/log_handler.h b/main/log_handler.h new file mode 100644 index 0000000..1547ca8 --- /dev/null +++ b/main/log_handler.h @@ -0,0 +1,42 @@ +#ifndef LOG_HANDLER_H +#define LOG_HANDLER_H + +#include +#include "esp_err.h" + +/** + * @brief Initialize log handler + * + * @return esp_err_t ESP_OK on success + */ +esp_err_t log_handler_init(void); + +/** + * @brief Deinitialize log handler + * + * @return esp_err_t ESP_OK on success + */ +esp_err_t log_handler_deinit(void); + +/** + * @brief Get log buffer content + * + * @param buf Buffer to copy logs into + * @param buf_len Length of buffer + * @return size_t Number of bytes copied + */ +size_t log_get_buffer(char *buf, size_t buf_len); + +/** + * @brief Clear log buffer + */ +void log_clear_buffer(void); + +/** + * @brief Get current log buffer size + * + * @return size_t Number of bytes in buffer + */ +size_t log_get_size(void); + +#endif // LOG_HANDLER_H diff --git a/main/main.c b/main/main.c index fe521b4..434f805 100644 --- a/main/main.c +++ b/main/main.c @@ -9,6 +9,7 @@ #include "ethernet_manager.h" #include "web_server.h" #include "ota_handler.h" +#include "log_handler.h" static const char *TAG = TAG_BMC; @@ -41,9 +42,17 @@ void app_main(void) { ESP_LOGI(TAG, "ESP32-S3 BMC Firmware Starting..."); ESP_LOGI(TAG, "========================================"); + // Initialize log handler first to capture all logs + ESP_LOGI(TAG, "Initializing log handler..."); + esp_err_t ret = log_handler_init(); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Failed to initialize log handler: %s", esp_err_to_name(ret)); + // Continue anyway - log capture is optional + } + // Initialize GPIO controller ESP_LOGI(TAG, "Initializing GPIO controller..."); - esp_err_t ret = gpio_controller_init(); + ret = gpio_controller_init(); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize GPIO controller: %s", esp_err_to_name(ret)); // Continue anyway - some GPIOs might still work diff --git a/main/web_server.c b/main/web_server.c index d689a32..2b38e02 100644 --- a/main/web_server.c +++ b/main/web_server.c @@ -4,6 +4,7 @@ #include "uart_handler.h" #include "ethernet_manager.h" #include "ota_handler.h" +#include "log_handler.h" #include "esp_log.h" #include "cJSON.h" #include @@ -490,6 +491,42 @@ static esp_err_t api_system_status_handler(httpd_req_t *req) { return send_json_success(req, status); } +// ============================================================================ +// Log API Handlers +// ============================================================================ + +static esp_err_t api_logs_get_handler(httpd_req_t *req) { + // Get log buffer size + size_t log_size = log_get_size(); + if (log_size == 0) { + httpd_resp_set_type(req, "text/plain"); + httpd_resp_sendstr(req, "No logs available"); + return ESP_OK; + } + + // Allocate buffer for logs + char *log_buf = malloc(log_size + 1); + if (!log_buf) { + return send_json_error(req, "Failed to allocate memory for logs"); + } + + // Get logs + size_t copied = log_get_buffer(log_buf, log_size); + log_buf[copied] = '\0'; + + // Send response + httpd_resp_set_type(req, "text/plain"); + httpd_resp_sendstr(req, log_buf); + + free(log_buf); + return ESP_OK; +} + +static esp_err_t api_logs_clear_handler(httpd_req_t *req) { + log_clear_buffer(); + return send_json_success(req, cJSON_CreateObject()); +} + // ============================================================================ // OTA API Handlers // ============================================================================ @@ -797,6 +834,18 @@ static const httpd_uri_t uri_api_ota_upload = { .handler = api_ota_upload_handler, }; +static const httpd_uri_t uri_api_logs_get = { + .uri = "/api/logs", + .method = HTTP_GET, + .handler = api_logs_get_handler, +}; + +static const httpd_uri_t uri_api_logs_clear = { + .uri = "/api/logs/clear", + .method = HTTP_POST, + .handler = api_logs_clear_handler, +}; + // ============================================================================ // Public Functions // ============================================================================ @@ -846,6 +895,8 @@ esp_err_t web_server_start(void) { httpd_register_uri_handler(server, &uri_api_ota_status); httpd_register_uri_handler(server, &uri_api_ota_update); httpd_register_uri_handler(server, &uri_api_ota_upload); + httpd_register_uri_handler(server, &uri_api_logs_get); + httpd_register_uri_handler(server, &uri_api_logs_clear); // Register UART callback for WebSocket broadcast at startup esp_err_t cb_ret = uart_register_rx_callback(uart_to_ws_callback);