#include "aes67_health.h" #include #include "aes67_web.h" #include "driver/temperature_sensor.h" #include "esp_app_desc.h" #include "esp_heap_caps.h" #include "esp_log.h" #include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/task.h" #define MAX_SENSORS 8 #define SAMPLE_MS 2000 #define HYSTERESIS_C 5.0f #define SOC_WARN_C 85.0f static const char *TAG = "health"; typedef struct { char name[16]; health_temp_read_cb_t read; float warn_c; bool valid, hot; float c, min_c, max_c; } sensor_t; static sensor_t s_sensors[MAX_SENSORS]; static int s_n; static SemaphoreHandle_t s_lock; static temperature_sensor_handle_t s_tsens; esp_err_t health_temp_register(const char *name, health_temp_read_cb_t read, float warn_c) { if (!s_lock || s_n >= MAX_SENSORS) { return ESP_ERR_INVALID_STATE; } xSemaphoreTake(s_lock, portMAX_DELAY); sensor_t *t = &s_sensors[s_n++]; strlcpy(t->name, name, sizeof(t->name)); t->read = read; t->warn_c = warn_c; xSemaphoreGive(s_lock); return ESP_OK; } // On-die sensor: measures the die, not ambient; expect well above room temperature. static esp_err_t soc_read(float *c) { return temperature_sensor_get_celsius(s_tsens, c); } static void health_task(void *arg) { while (1) { for (int i = 0; i < s_n; i++) { float c; if (s_sensors[i].read(&c) != ESP_OK) { continue; } xSemaphoreTake(s_lock, portMAX_DELAY); sensor_t *t = &s_sensors[i]; t->c = c; t->min_c = t->valid && t->min_c < c ? t->min_c : c; t->max_c = t->valid && t->max_c > c ? t->max_c : c; t->valid = true; bool was_hot = t->hot; if (t->warn_c > 0) { if (!t->hot && c >= t->warn_c) { t->hot = true; } else if (t->hot && c < t->warn_c - HYSTERESIS_C) { t->hot = false; } } xSemaphoreGive(s_lock); // Logged outside the lock; goes to syslog like any warning. if (t->hot != was_hot) { if (t->hot) { ESP_LOGW(TAG, "temperature %s %.1f °C: at or above %.0f °C", t->name, c, t->warn_c); } else { ESP_LOGI(TAG, "temperature %s %.1f °C: back below %.0f °C", t->name, c, t->warn_c - HYSTERESIS_C); } } } vTaskDelay(pdMS_TO_TICKS(SAMPLE_MS)); } } static double round1(float v) { return (double)(int)(v * 10.0f + (v < 0 ? -0.5f : 0.5f)) / 10.0; } static void health_status(cJSON *st) { cJSON_AddStringToObject(st, "fw", esp_app_get_description()->version); cJSON_AddNumberToObject(st, "uptime_s", (double)(esp_timer_get_time() / 1000000)); cJSON_AddNumberToObject(st, "heap_free", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); cJSON_AddNumberToObject(st, "psram_free", heap_caps_get_free_size(MALLOC_CAP_SPIRAM)); cJSON *temps = cJSON_AddArrayToObject(st, "temps"); xSemaphoreTake(s_lock, portMAX_DELAY); for (int i = 0; i < s_n; i++) { const sensor_t *t = &s_sensors[i]; if (!t->valid) { continue; } cJSON *o = cJSON_CreateObject(); cJSON_AddStringToObject(o, "name", t->name); cJSON_AddNumberToObject(o, "c", round1(t->c)); cJSON_AddNumberToObject(o, "min_c", round1(t->min_c)); cJSON_AddNumberToObject(o, "max_c", round1(t->max_c)); if (t->warn_c > 0) { cJSON_AddNumberToObject(o, "warn_c", t->warn_c); } cJSON_AddItemToArray(temps, o); } xSemaphoreGive(s_lock); } esp_err_t aes67_health_init(void) { s_lock = xSemaphoreCreateMutex(); if (!s_lock) { return ESP_ERR_NO_MEM; } // 20..100 °C (+-2 °C): the die runs well above ambient and the warning level must be in range. temperature_sensor_config_t cfg = TEMPERATURE_SENSOR_CONFIG_DEFAULT(20, 100); esp_err_t err = temperature_sensor_install(&cfg, &s_tsens); if (err == ESP_OK) { err = temperature_sensor_enable(s_tsens); } if (err == ESP_OK) { health_temp_register("SoC", soc_read, SOC_WARN_C); } else { ESP_LOGE(TAG, "on-die temperature sensor: %s", esp_err_to_name(err)); } if (xTaskCreate(health_task, "health", 3072, NULL, 2, NULL) != pdPASS) { return ESP_ERR_NO_MEM; } return status_register(health_status); }