diff --git a/components/aes67_health/CMakeLists.txt b/components/aes67_health/CMakeLists.txt index 558a93b..89adf02 100644 --- a/components/aes67_health/CMakeLists.txt +++ b/components/aes67_health/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register(SRCS "aes67_health.c" INCLUDE_DIRS "include" - PRIV_REQUIRES aes67_web esp_app_format esp_timer heap) + PRIV_REQUIRES aes67_web esp_app_format esp_driver_tsens esp_timer heap) diff --git a/components/aes67_health/aes67_health.c b/components/aes67_health/aes67_health.c index 33f1a2c..a155163 100644 --- a/components/aes67_health/aes67_health.c +++ b/components/aes67_health/aes67_health.c @@ -1,9 +1,98 @@ #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) { @@ -11,9 +100,46 @@ static void health_status(cJSON *st) 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); } diff --git a/components/aes67_health/include/aes67_health.h b/components/aes67_health/include/aes67_health.h index a5fdce9..305dc11 100644 --- a/components/aes67_health/include/aes67_health.h +++ b/components/aes67_health/include/aes67_health.h @@ -4,5 +4,11 @@ #include "esp_err.h" -// Registers the health fields (fw, uptime_s, heap_free, psram_free) in /api/status. +// Read one temperature in °C. Return ESP_OK on success. +typedef esp_err_t (*health_temp_read_cb_t)(float *celsius); + +// Registers the health fields (fw, uptime_s, heap_free, psram_free, temps) in /api/status, +// adds the on-die "SoC" sensor and starts the 2 s sampling task. esp_err_t aes67_health_init(void); +// Add a temperature sensor (e.g. an external I2C one from the board). warn_c <= 0: no warning. +esp_err_t health_temp_register(const char *name, health_temp_read_cb_t read, float warn_c); diff --git a/docs/aes67-core-base.md b/docs/aes67-core-base.md index 6110353..e25b9cb 100644 --- a/docs/aes67-core-base.md +++ b/docs/aes67-core-base.md @@ -33,6 +33,7 @@ Reference devices for UI and defaults: Riedel Bolero (PTP status), Riedel Direct ## Temperatures - `status.temps` = array of {name, c, min_c, max_c, warn_c}; min/max since boot, warn_c optional. Any number of sensors; the UI renders one row per sensor and marks HIGH (red) at or above warn_c. - ESP32-P4: on-die sensor via the `temperature_sensor` driver (driver/temperature_sensor.h). Pick the measurement range to suit (e.g. -10..80 °C), sample every ~2 s in the health task, and report it as "SoC". It measures the die, not ambient: expect it to read well above room temperature. + - Implemented with range 20..100 °C (±2 °C; the warning level must lie inside the range), warn_c 85 °C. On the test board the die reads ~27 °C at idle in a cool room. - Boards may add external sensors (e.g. I2C) by appending to the same array; the project or board component registers them. - Log a warning (goes to syslog) when crossing warn_c, with hysteresis (e.g. clear at warn_c - 5).