#include "battery_uv.h" #if POWERPOD_BATTERY_UV_ENABLE #include "led_ring.h" #include "pod_reboot.h" #include "pod_settings.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" static const char *TAG = "[BATTERY_UV]"; #define UV_DEBOUNCE_SAMPLES 2u #define UV_POLL_INTERVAL_MS 30000u static uint8_t s_uv_streak; static uint8_t s_charge_streak; bool battery_uv_is_latched(void) { return pod_settings_is_uv_latched(); } bool battery_uv_reading_is_under(const board_lipo_reading_t *reading) { if (reading == NULL) { return false; } if (reading->lipo1_valid && reading->lipo1_mv < BATTERY_UV_ADC_MV) { return true; } if (reading->lipo2_valid && reading->lipo2_mv < BATTERY_UV_ADC_MV) { return true; } return false; } bool battery_uv_reading_is_charged(const board_lipo_reading_t *reading) { if (reading == NULL) { return false; } bool any_valid = false; if (reading->lipo1_valid) { any_valid = true; if (reading->lipo1_mv < BATTERY_CHARGE_ADC_MV) { return false; } } if (reading->lipo2_valid) { any_valid = true; if (reading->lipo2_mv < BATTERY_CHARGE_ADC_MV) { return false; } } return any_valid; } bool battery_uv_evaluate_boot(void) { board_lipo_reading_t reading; board_input_read_lipo(&reading); if (pod_settings_is_uv_latched() && battery_uv_reading_is_charged(&reading)) { ESP_LOGI(TAG, "charged — clearing UV latch"); pod_settings_clear_uv_latched(); return false; } if (pod_settings_is_uv_latched() || battery_uv_reading_is_under(&reading)) { if (!pod_settings_is_uv_latched()) { ESP_LOGW(TAG, "under-voltage at boot — latching UV"); } else { ESP_LOGW(TAG, "UV latched — energy-save boot"); } pod_settings_set_uv_latched(true); return true; } return false; } void battery_uv_poll(const board_lipo_reading_t *reading) { if (reading == NULL || pod_settings_is_uv_latched()) { return; } if (battery_uv_reading_is_under(reading)) { s_uv_streak++; ESP_LOGW(TAG, "under-voltage sample %u/%u", (unsigned)s_uv_streak, (unsigned)UV_DEBOUNCE_SAMPLES); if (s_uv_streak >= UV_DEBOUNCE_SAMPLES) { ESP_LOGW(TAG, "UV confirmed — latching and restarting"); pod_settings_set_uv_latched(true); pod_schedule_restart(); } return; } s_uv_streak = 0; } void battery_uv_run_mode(void) { s_charge_streak = 0; s_uv_streak = 0; led_ring_init(); led_ring_show_battery_low(); ESP_LOGW(TAG, "energy-save mode — ADC poll every %u ms", UV_POLL_INTERVAL_MS); while (1) { board_lipo_reading_t reading; board_input_read_lipo(&reading); if (battery_uv_reading_is_charged(&reading)) { s_charge_streak++; ESP_LOGI(TAG, "charge sample %u/%u", (unsigned)s_charge_streak, (unsigned)UV_DEBOUNCE_SAMPLES); if (s_charge_streak >= UV_DEBOUNCE_SAMPLES) { ESP_LOGI(TAG, "charged — clearing UV latch and restarting"); pod_settings_clear_uv_latched(); pod_schedule_restart(); } } else { s_charge_streak = 0; } vTaskDelay(pdMS_TO_TICKS(UV_POLL_INTERVAL_MS)); } } #endif