/**
* @file error_handler.c
* @brief Implementation of the thread-safe error handling system
*/
#include "error_handler.h"
#include <string.h>
#include <stdio.h>
#include "task.h"
/**
* @brief Internal structure for error handling system state
*/
typedef struct {
ErrorRecord_t records[ERROR_MAX_RECORDS]; /**< Circular buffer of error
records */
uint32_t write_index; /**< Current write position in
buffer */
ErrorStats_t stats; /**< Error statistics */
ErrorCallback_t callback; /**< Error notification callback */
SemaphoreHandle_t mutex; /**< Mutex for thread safety */
StaticSemaphore_t mutex_buffer; /**< Static buffer for mutex */
bool initialized; /**< Initialization flag */
} ErrorHandler_t;
/* Static instance of error handler */
static ErrorHandler_t error_handler;
/* Private function prototypes */
static void ErrorHandler_TruncateFilename(const char* src, char* dst, size_t
max_len);
static void ErrorHandler_UpdateStats(ErrorSeverity_t severity, ErrorCategory_t
category);
static bool ErrorHandler_AcquireMutex(void);
static void ErrorHandler_ReleaseMutex(void);
BaseType_t ErrorHandler_Init(void) {
/* Prevent multiple initialization */
if (error_handler.initialized) {
return pdFAIL;
}
/* Clear all data */
memset(&error_handler, 0, sizeof(ErrorHandler_t));
/* Create mutex using static buffer */
error_handler.mutex = xSemaphoreCreateMutexStatic(&error_handler.mutex_buffer);
if (error_handler.mutex == NULL) {
return pdFAIL;
}
error_handler.initialized = true;
return pdPASS;
}
bool ErrorHandler_Log(
ErrorSeverity_t severity,
ErrorCategory_t category,
uint32_t error_code,
const char* file,
uint32_t line,
const char* message,
uint32_t data
) {
if (!error_handler.initialized || !file || !message) {
return false;
}
/* Validate parameters */
if (severity > ERROR_SEVERITY_CRITICAL ||
category >= ERROR_CAT_COUNT) {
return false;
}
if (!ErrorHandler_AcquireMutex()) {
error_handler.stats.mutex_timeouts++;
return false;
}
/* Get pointer to current record */
ErrorRecord_t* record = &error_handler.records[error_handler.write_index];
/* Fill record */
record->timestamp = xTaskGetTickCount();
record->severity = severity;
record->category = category;
record->error_code = error_code;
record->line = line;
record->data = data;
/* Safely copy strings */
ErrorHandler_TruncateFilename(file, record->file, ERROR_FILE_NAME_LEN);
strncpy(record->message, message, ERROR_MAX_MSG_LEN - 1);
record->message[ERROR_MAX_MSG_LEN - 1] = '\0';
/* Update indices and statistics */
error_handler.write_index = (error_handler.write_index + 1) %
ERROR_MAX_RECORDS;
ErrorHandler_UpdateStats(severity, category);
/* Notify callback if registered and error is severe enough */
if (error_handler.callback && severity >= ERROR_SEVERITY_ERROR) {
error_handler.callback(record);
}
ErrorHandler_ReleaseMutex();
return true;
}
bool ErrorHandler_RegisterCallback(ErrorCallback_t callback) {
if (!error_handler.initialized) {
return false;
}
if (!ErrorHandler_AcquireMutex()) {
return false;
}
bool has_critical = false;
for (uint32_t i = 0; i < ERROR_MAX_RECORDS; i++) {
if (error_handler.records[i].severity == ERROR_SEVERITY_CRITICAL) {
has_critical = true;
break;
}
}
ErrorHandler_ReleaseMutex();
return has_critical;
}
/**
* @brief Truncate filename to remove path
*
* @param src Source filename with path
* @param dst Destination buffer
* @param max_len Maximum length of destination buffer
*/
static void ErrorHandler_TruncateFilename(const char* src, char* dst, size_t
max_len) {
size_t len = strlen(src);
const char* start = src;
/* Find last slash or backslash */
for (const char* p = src + len - 1; p >= src; p--) {
if (*p == '/' || *p == '\\') {
start = p + 1;
break;
}
}
strncpy(dst, start, max_len - 1);
dst[max_len - 1] = '\0';
}
/**
* @brief Update error statistics
*
* @param severity Error severity level
* @param category Error category
*/
static void ErrorHandler_UpdateStats(ErrorSeverity_t severity, ErrorCategory_t
category) {
error_handler.stats.total_errors++;
error_handler.stats.errors_by_severity[severity]++;
error_handler.stats.errors_by_category[category]++;
/* Check for buffer overrun */
if (error_handler.stats.total_errors > ERROR_MAX_RECORDS) {
error_handler.stats.buffer_overruns++;
}
}
/**
* @brief Acquire the error handler mutex with timeout
*
* @return true if mutex was acquired, false if timeout
*/
static bool ErrorHandler_AcquireMutex(void) {
if (xSemaphoreTake(error_handler.mutex,
pdMS_TO_TICKS(ERROR_MUTEX_TIMEOUT_MS)) != pdTRUE) {
return false;
}
return true;
}
/**
* @brief Release the error handler mutex
*/
static void ErrorHandler_ReleaseMutex(void) {
xSemaphoreGive(error_handler.mutex);
}
return false;
}
if (!ErrorHandler_AcquireMutex()) {
return false;
}
error_handler.callback = callback;
ErrorHandler_ReleaseMutex();
return true;
}
const ErrorRecord_t* ErrorHandler_GetLatest(void) {
if (!error_handler.initialized || error_handler.stats.total_errors == 0) {
return NULL;
}
if (!ErrorHandler_AcquireMutex()) {
return NULL;
}
uint32_t latest_index = (error_handler.write_index - 1) % ERROR_MAX_RECORDS;
const ErrorRecord_t* latest = &error_handler.records[latest_index];
ErrorHandler_ReleaseMutex();
return latest;
}
bool ErrorHandler_GetStats(ErrorStats_t* stats) {
if (!error_handler.initialized || !stats) {
return false;
}
if (!ErrorHandler_AcquireMutex()) {
return false;
}
*stats = error_handler.stats;
ErrorHandler_ReleaseMutex();
return true;
}
uint32_t ErrorHandler_GetLogString(char* buffer, uint32_t max_len, uint32_t
num_errors) {
if (!error_handler.initialized || !buffer || max_len == 0) {
return 0;
}
uint32_t written = 0;
uint32_t available = max_len;
/* Header */
written += snprintf(buffer + written, available,
"Error Log (Last %lu of %lu errors):\r\n",
num_errors, error_handler.stats.total_errors);
available = max_len - written;
if (!ErrorHandler_AcquireMutex()) {
return written;
}
/* Calculate start index */
uint32_t count = 0;
uint32_t index = error_handler.write_index;
while (count < num_errors &&
count < error_handler.stats.total_errors &&
available > 0) {
if (index == 0) {
index = ERROR_MAX_RECORDS - 1;
} else {
index--;
}
const ErrorRecord_t* record = &error_handler.records[index];
/* Format timestamp as seconds.milliseconds */
uint32_t ms_per_tick = portTICK_PERIOD_MS;
uint32_t timestamp_s = (record->timestamp * ms_per_tick) / 1000;
uint32_t timestamp_ms = (record->timestamp * ms_per_tick) % 1000;
written += snprintf(buffer + written, available,
"%lu.%03lu - [%s:%lu] %s: %s (0x%lX)\r\n",
timestamp_s, timestamp_ms,
record->file, record->line,
record->severity == ERROR_SEVERITY_CRITICAL ? "CRIT" :
record->severity == ERROR_SEVERITY_ERROR ? "ERROR" :
record->severity == ERROR_SEVERITY_WARNING ? "WARN" : "INFO",
record->message,
record->data);
available = max_len - written;
count++;
}
ErrorHandler_ReleaseMutex();
return written;
}
bool ErrorHandler_Clear(void) {
if (!error_handler.initialized) {
return false;
}
if (!ErrorHandler_AcquireMutex()) {
return false;
}
memset(error_handler.records, 0, sizeof(error_handler.records));
memset(&error_handler.stats, 0, sizeof(error_handler.stats));
error_handler.write_index = 0;
ErrorHandler_ReleaseMutex();
return true;
}
bool ErrorHandler_HasCritical(void) {
if (!error_handler.initialized) {