You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
186 lines
6.4 KiB
Python
186 lines
6.4 KiB
Python
"""
|
|
BatteryManager - Android-inspired battery and power information API.
|
|
|
|
Provides direct query access to battery voltage, charge percentage, and raw ADC values.
|
|
Handles ADC1/ADC2 pin differences on ESP32-S3 with adaptive caching to minimize WiFi interference.
|
|
"""
|
|
|
|
import time
|
|
|
|
MIN_VOLTAGE = 3.15
|
|
MAX_VOLTAGE = 4.15
|
|
|
|
# Internal state
|
|
_adc = None
|
|
_conversion_func = None
|
|
_adc_pin = None
|
|
|
|
# Cache to reduce WiFi interruptions (ADC2 requires WiFi to be disabled)
|
|
_cached_raw_adc = None
|
|
_last_read_time = 0
|
|
CACHE_DURATION_ADC1_MS = 30000 # 30 seconds (cheaper: no WiFi interference)
|
|
CACHE_DURATION_ADC2_MS = 600000 # 600 seconds (expensive: requires WiFi disable)
|
|
|
|
|
|
def _is_adc2_pin(pin):
|
|
"""Check if pin is on ADC2 (ESP32-S3: GPIO11-20)."""
|
|
return 11 <= pin <= 20
|
|
|
|
|
|
class BatteryManager:
|
|
"""
|
|
Android-inspired BatteryManager for querying battery and power information.
|
|
|
|
Provides static methods for battery voltage, percentage, and raw ADC readings.
|
|
Automatically handles ADC1/ADC2 differences and WiFi coordination on ESP32-S3.
|
|
"""
|
|
|
|
@staticmethod
|
|
def init_adc(pinnr, adc_to_voltage_func):
|
|
"""
|
|
Initialize ADC for battery voltage monitoring.
|
|
|
|
IMPORTANT for ESP32-S3: ADC2 (GPIO11-20) doesn't work when WiFi is active!
|
|
Use ADC1 pins (GPIO1-10) for battery monitoring if possible.
|
|
If using ADC2, WiFi will be temporarily disabled during readings.
|
|
|
|
Args:
|
|
pinnr: GPIO pin number
|
|
adc_to_voltage_func: Conversion function that takes raw ADC value (0-4095)
|
|
and returns battery voltage in volts
|
|
"""
|
|
global _adc, _conversion_func, _adc_pin
|
|
|
|
_conversion_func = adc_to_voltage_func
|
|
_adc_pin = pinnr
|
|
|
|
try:
|
|
print(f"Initializing ADC pin {pinnr} with conversion function")
|
|
if _is_adc2_pin(pinnr):
|
|
print(f" WARNING: GPIO{pinnr} is on ADC2 - WiFi will be disabled during readings")
|
|
from machine import ADC, Pin
|
|
_adc = ADC(Pin(pinnr))
|
|
_adc.atten(ADC.ATTN_11DB) # 0-3.3V range
|
|
except Exception as e:
|
|
print(f"Info: this platform has no ADC for measuring battery voltage: {e}")
|
|
|
|
initial_adc_value = BatteryManager.read_raw_adc()
|
|
print(f"Reading ADC at init to fill cache: {initial_adc_value} => {BatteryManager.read_battery_voltage(raw_adc_value=initial_adc_value)}V => {BatteryManager.get_battery_percentage(raw_adc_value=initial_adc_value)}%")
|
|
|
|
@staticmethod
|
|
def has_battery():
|
|
"""
|
|
Check if battery monitoring is initialized.
|
|
|
|
Returns:
|
|
bool: True if init_adc() was called, False otherwise
|
|
"""
|
|
return _adc_pin is not None
|
|
|
|
@staticmethod
|
|
def read_raw_adc(force_refresh=False):
|
|
"""
|
|
Read raw ADC value (0-4095) with adaptive caching.
|
|
|
|
On ESP32-S3 with ADC2, WiFi is temporarily disabled during reading.
|
|
Raises RuntimeError if WifiService is busy (connecting/scanning) when using ADC2.
|
|
|
|
Args:
|
|
force_refresh: Bypass cache and force fresh reading
|
|
|
|
Returns:
|
|
float: Raw ADC value (0-4095)
|
|
|
|
Raises:
|
|
RuntimeError: If WifiService is busy (only when using ADC2)
|
|
"""
|
|
global _cached_raw_adc, _last_read_time
|
|
|
|
# Desktop mode - return random value in typical ADC range
|
|
if not _adc:
|
|
import random
|
|
return random.randint(1900, 2600)
|
|
|
|
# Check if this is an ADC2 pin (requires WiFi disable)
|
|
needs_wifi_disable = _adc_pin is not None and _is_adc2_pin(_adc_pin)
|
|
|
|
# Use different cache durations based on cost
|
|
cache_duration = CACHE_DURATION_ADC2_MS if needs_wifi_disable else CACHE_DURATION_ADC1_MS
|
|
|
|
# Check cache
|
|
current_time = time.ticks_ms()
|
|
if not force_refresh and _cached_raw_adc is not None:
|
|
age = time.ticks_diff(current_time, _last_read_time)
|
|
if age < cache_duration:
|
|
return _cached_raw_adc
|
|
|
|
# Import WifiService only if needed
|
|
WifiService = None
|
|
if needs_wifi_disable:
|
|
try:
|
|
# Needs actual path, not "from mpos" shorthand because it's mocked by test_battery_voltage.py
|
|
from mpos.net.wifi_service import WifiService
|
|
except ImportError:
|
|
pass
|
|
|
|
# Temporarily disable WiFi for ADC2 reading
|
|
was_connected = False
|
|
if needs_wifi_disable and WifiService:
|
|
# This will raise RuntimeError if WiFi is already busy
|
|
was_connected = WifiService.temporarily_disable()
|
|
time.sleep(0.05) # Brief delay for WiFi to fully disable
|
|
|
|
try:
|
|
# Read ADC (average of 10 samples)
|
|
total = sum(_adc.read() for _ in range(10))
|
|
raw_value = total / 10.0
|
|
|
|
# Update cache
|
|
_cached_raw_adc = raw_value
|
|
_last_read_time = current_time
|
|
|
|
return raw_value
|
|
|
|
finally:
|
|
# Re-enable WiFi (only if we disabled it)
|
|
if needs_wifi_disable and WifiService:
|
|
WifiService.temporarily_enable(was_connected)
|
|
|
|
@staticmethod
|
|
def read_battery_voltage(force_refresh=False, raw_adc_value=None):
|
|
"""
|
|
Read battery voltage in volts.
|
|
|
|
Args:
|
|
force_refresh: Bypass cache and force fresh reading
|
|
raw_adc_value: Optional pre-computed raw ADC value (for testing)
|
|
|
|
Returns:
|
|
float: Battery voltage in volts (clamped to 0-MAX_VOLTAGE)
|
|
"""
|
|
raw = raw_adc_value if raw_adc_value else BatteryManager.read_raw_adc(force_refresh)
|
|
voltage = _conversion_func(raw) if _conversion_func else 0.0
|
|
return voltage
|
|
|
|
@staticmethod
|
|
def get_battery_percentage(raw_adc_value=None):
|
|
"""
|
|
Get battery charge percentage.
|
|
|
|
Args:
|
|
raw_adc_value: Optional pre-computed raw ADC value (for testing)
|
|
|
|
Returns:
|
|
float: Battery percentage (0-100)
|
|
"""
|
|
voltage = BatteryManager.read_battery_voltage(raw_adc_value=raw_adc_value)
|
|
percentage = (voltage - MIN_VOLTAGE) * 100.0 / (MAX_VOLTAGE - MIN_VOLTAGE)
|
|
return max(0, min(100.0, percentage)) # limit to 100.0% and make sure it's positive
|
|
|
|
@staticmethod
|
|
def clear_cache():
|
|
"""Clear the battery voltage cache to force fresh reading on next call."""
|
|
global _cached_raw_adc, _last_read_time
|
|
_cached_raw_adc = None
|
|
_last_read_time = 0
|