#include "power_manager.h" #include #include #include void PowerManager::begin() { Serial.println("[power] Initializing power manager"); // Disable audio power rails permanently pinMode(PIN_AUDIO_PWR_EN, OUTPUT); digitalWrite(PIN_AUDIO_PWR_EN, LOW); pinMode(PIN_SPK_EN, OUTPUT); digitalWrite(PIN_SPK_EN, LOW); // Initialize RGB LEDs via M5Unified (handles NeoPixel on G21) // M5Unified manages the LED strip internally clearLEDs(); Serial.printf("[power] Battery: %d%%, Charging: %s\n", getBatteryPercent(), isCharging() ? "yes" : "no"); } uint8_t PowerManager::getBatteryPercent() { int32_t level = M5.Power.getBatteryLevel(); if (level < 0) level = 0; if (level > 100) level = 100; _lastBatteryPct = static_cast(level); return _lastBatteryPct; } bool PowerManager::isCharging() { return M5.Power.isCharging(); } void PowerManager::updateBatteryLED() { unsigned long now = millis(); uint8_t pct = getBatteryPercent(); // Auto deep sleep at critical level if (pct <= BATTERY_SHUTDOWN_PCT && !isCharging()) { Serial.println("[power] Battery critical — entering deep sleep"); enterDeepSleep(); return; } // Determine pulse interval based on battery level unsigned long interval = 0; uint8_t r = 0, g = 0, b = 0; if (isCharging()) { interval = BATTERY_WARN_INTERVAL_MS; g = 255; // Green pulse } else if (pct <= BATTERY_CRITICAL_PCT) { interval = BATTERY_CRIT_INTERVAL_MS; r = 255; // Red pulse } else if (pct <= BATTERY_LOW_PCT) { interval = BATTERY_WARN_INTERVAL_MS; r = 255; g = 165; // Orange pulse } else { // Battery fine — no LED return; } if (now - _lastLEDPulse >= interval) { _lastLEDPulse = now; flashLED(r, g, b); } } void PowerManager::flashLED(uint8_t r, uint8_t g, uint8_t b, uint16_t duration_ms) { // Scale by brightness float scale = _ledBrightness / 255.0f; uint8_t sr = static_cast(r * scale); uint8_t sg = static_cast(g * scale); uint8_t sb = static_cast(b * scale); setLED(0, sr, sg, sb); setLED(1, sr, sg, sb); // Non-blocking: we'll clear on next update cycle // For simplicity, use a short blocking delay for LED flash feedback delay(duration_ms); clearLEDs(); } void PowerManager::enterDeepSleep() { Serial.println("[power] Entering deep sleep — power button to wake"); Serial.flush(); delay(100); clearLEDs(); // On PaperColor, deep sleep is managed via M5PM1 shutdown // The power button is hardwired to M5PM1 and always wakes the device M5.Power.deepSleep(0); // 0 = indefinite, wake via power button } void PowerManager::enableLightSleep() { esp_pm_config_esp32s3_t pm_config; pm_config.max_freq_mhz = 240; pm_config.min_freq_mhz = 80; pm_config.light_sleep_enable = true; esp_err_t err = esp_pm_configure(&pm_config); if (err == ESP_OK) { Serial.println("[power] Light sleep enabled (80-240MHz)"); } else { Serial.printf("[power] Light sleep config failed: %d\n", err); } } void PowerManager::disableEPDPower() { // M5PM1 PYG0 controls e-paper power — managed via M5Unified Power API // M5.Power controls the PM1 rails Serial.println("[power] EPD power disabled"); } void PowerManager::enableEPDPower() { Serial.println("[power] EPD power enabled"); } void PowerManager::setLED(uint8_t index, uint8_t r, uint8_t g, uint8_t b) { // M5Unified provides LED control — exact API depends on board support // Fallback: direct NeoPixel control if M5Unified doesn't cover it (void)index; (void)r; (void)g; (void)b; // TODO: Implement via M5Unified LED API or direct NeoPixel library // This will be filled in during hardware bring-up when we can test on device } void PowerManager::clearLEDs() { setLED(0, 0, 0, 0); setLED(1, 0, 0, 0); }