|
5 | 5 |
|
6 | 6 | adc = None |
7 | 7 | scale_factor = 0 |
| 8 | +adc_pin = None |
| 9 | + |
| 10 | +# Cache to reduce WiFi interruptions (ADC2 requires WiFi to be disabled) |
| 11 | +_cached_raw_adc = None |
| 12 | +_last_read_time = 0 |
| 13 | +CACHE_DURATION_MS = 30000 # 30 seconds |
| 14 | + |
| 15 | + |
| 16 | +def _is_adc2_pin(pin): |
| 17 | + """Check if pin is on ADC2 (ESP32-S3: GPIO11-20).""" |
| 18 | + return 11 <= pin <= 20 |
| 19 | + |
8 | 20 |
|
9 | | -# This gets called by (the device-specific) boot*.py |
10 | 21 | def init_adc(pinnr, sf): |
11 | | - global adc, scale_factor |
| 22 | + """ |
| 23 | + Initialize ADC for battery voltage monitoring. |
| 24 | +
|
| 25 | + IMPORTANT for ESP32-S3: ADC2 (GPIO11-20) doesn't work when WiFi is active! |
| 26 | + Use ADC1 pins (GPIO1-10) for battery monitoring if possible. |
| 27 | + If using ADC2, WiFi will be temporarily disabled during readings. |
| 28 | +
|
| 29 | + Args: |
| 30 | + pinnr: GPIO pin number |
| 31 | + sf: Scale factor to convert raw ADC (0-4095) to battery voltage |
| 32 | + """ |
| 33 | + global adc, scale_factor, adc_pin |
| 34 | + scale_factor = sf |
| 35 | + adc_pin = pinnr |
12 | 36 | try: |
13 | | - print(f"Initializing ADC pin {pinnr} with scale_factor {scale_factor}") |
14 | | - from machine import ADC, Pin # do this inside the try because it will fail on desktop |
| 37 | + print(f"Initializing ADC pin {pinnr} with scale_factor {sf}") |
| 38 | + if _is_adc2_pin(pinnr): |
| 39 | + print(f" WARNING: GPIO{pinnr} is on ADC2 - WiFi will be disabled during readings") |
| 40 | + from machine import ADC, Pin |
15 | 41 | adc = ADC(Pin(pinnr)) |
16 | | - # Set ADC to 11dB attenuation for 0–3.3V range (common for ESP32) |
17 | | - adc.atten(ADC.ATTN_11DB) |
18 | | - scale_factor = sf |
| 42 | + adc.atten(ADC.ATTN_11DB) # 0-3.3V range |
19 | 43 | except Exception as e: |
20 | | - print("Info: this platform has no ADC for measuring battery voltage") |
| 44 | + print(f"Info: this platform has no ADC for measuring battery voltage: {e}") |
| 45 | + |
| 46 | + |
| 47 | +def read_raw_adc(force_refresh=False): |
| 48 | + """ |
| 49 | + Read raw ADC value (0-4095) with caching. |
21 | 50 |
|
22 | | -def read_battery_voltage(): |
| 51 | + On ESP32-S3 with ADC2, WiFi is temporarily disabled during reading. |
| 52 | + Raises RuntimeError if WifiService is busy (connecting/scanning) when using ADC2. |
| 53 | +
|
| 54 | + Args: |
| 55 | + force_refresh: Bypass cache and force fresh reading |
| 56 | +
|
| 57 | + Returns: |
| 58 | + float: Raw ADC value (0-4095) |
| 59 | +
|
| 60 | + Raises: |
| 61 | + RuntimeError: If WifiService is busy (only when using ADC2) |
| 62 | + """ |
| 63 | + global _cached_raw_adc, _last_read_time |
| 64 | + |
| 65 | + # Desktop mode - return random value |
23 | 66 | if not adc: |
24 | 67 | import random |
25 | | - random_voltage = random.randint(round(MIN_VOLTAGE*100),round(MAX_VOLTAGE*100)) / 100 |
26 | | - #print(f"returning random voltage: {random_voltage}") |
27 | | - return random_voltage |
28 | | - # Read raw ADC value |
29 | | - total = 0 |
30 | | - # Read multiple times to try to reduce variability. |
31 | | - # Reading 10 times takes around 3ms so it's fine... |
32 | | - for _ in range(10): |
33 | | - total = total + adc.read() |
34 | | - raw_value = total / 10 |
35 | | - #print(f"read_battery_voltage raw_value: {raw_value}") |
36 | | - voltage = raw_value * scale_factor |
37 | | - # Clamp to 0–4.2V range for LiPo battery |
38 | | - voltage = max(0, min(voltage, MAX_VOLTAGE)) |
39 | | - return voltage |
40 | | - |
41 | | -# Could be interesting to keep a "rolling average" of the percentage so that it doesn't fluctuate too quickly |
| 68 | + return random.randint(1900, 2600) if scale_factor == 0 else random.randint( |
| 69 | + int(MIN_VOLTAGE / scale_factor), int(MAX_VOLTAGE / scale_factor) |
| 70 | + ) |
| 71 | + |
| 72 | + # Check cache |
| 73 | + current_time = time.ticks_ms() |
| 74 | + if not force_refresh and _cached_raw_adc is not None: |
| 75 | + age = time.ticks_diff(current_time, _last_read_time) |
| 76 | + if age < CACHE_DURATION_MS: |
| 77 | + return _cached_raw_adc |
| 78 | + |
| 79 | + # Check if this is an ADC2 pin (requires WiFi disable) |
| 80 | + needs_wifi_disable = adc_pin is not None and _is_adc2_pin(adc_pin) |
| 81 | + |
| 82 | + # Import WifiService only if needed |
| 83 | + WifiService = None |
| 84 | + if needs_wifi_disable: |
| 85 | + try: |
| 86 | + from mpos.net.wifi_service import WifiService |
| 87 | + except ImportError: |
| 88 | + pass |
| 89 | + |
| 90 | + # Check if WiFi operations are in progress |
| 91 | + if WifiService and WifiService.wifi_busy: |
| 92 | + raise RuntimeError("Cannot read battery voltage: WifiService is busy") |
| 93 | + |
| 94 | + # Disable WiFi for ADC2 reading |
| 95 | + wifi_was_connected = False |
| 96 | + if needs_wifi_disable and WifiService: |
| 97 | + wifi_was_connected = WifiService.is_connected() |
| 98 | + WifiService.wifi_busy = True |
| 99 | + WifiService.disconnect() |
| 100 | + time.sleep(0.05) # Brief delay for WiFi to fully disable |
| 101 | + |
| 102 | + try: |
| 103 | + # Read ADC (average of 10 samples) |
| 104 | + total = sum(adc.read() for _ in range(10)) |
| 105 | + raw_value = total / 10.0 |
| 106 | + |
| 107 | + # Update cache |
| 108 | + _cached_raw_adc = raw_value |
| 109 | + _last_read_time = current_time |
| 110 | + |
| 111 | + return raw_value |
| 112 | + |
| 113 | + finally: |
| 114 | + # Re-enable WiFi (only if we disabled it) |
| 115 | + if needs_wifi_disable and WifiService: |
| 116 | + WifiService.wifi_busy = False |
| 117 | + if wifi_was_connected: |
| 118 | + # Trigger reconnection in background thread |
| 119 | + try: |
| 120 | + import _thread |
| 121 | + _thread.start_new_thread(WifiService.auto_connect, ()) |
| 122 | + except Exception as e: |
| 123 | + print(f"battery_voltage: Failed to start reconnect thread: {e}") |
| 124 | + |
| 125 | + |
| 126 | +def read_battery_voltage(force_refresh=False): |
| 127 | + """ |
| 128 | + Read battery voltage in volts. |
| 129 | +
|
| 130 | + Args: |
| 131 | + force_refresh: Bypass cache and force fresh reading |
| 132 | +
|
| 133 | + Returns: |
| 134 | + float: Battery voltage in volts (clamped to 0-MAX_VOLTAGE) |
| 135 | + """ |
| 136 | + raw = read_raw_adc(force_refresh) |
| 137 | + voltage = raw * scale_factor |
| 138 | + return max(0.0, min(voltage, MAX_VOLTAGE)) |
| 139 | + |
| 140 | + |
42 | 141 | def get_battery_percentage(): |
43 | | - return (read_battery_voltage() - MIN_VOLTAGE) * 100 / (MAX_VOLTAGE - MIN_VOLTAGE) |
| 142 | + """ |
| 143 | + Get battery charge percentage. |
| 144 | +
|
| 145 | + Returns: |
| 146 | + float: Battery percentage (0-100) |
| 147 | + """ |
| 148 | + voltage = read_battery_voltage() |
| 149 | + percentage = (voltage - MIN_VOLTAGE) * 100.0 / (MAX_VOLTAGE - MIN_VOLTAGE) |
| 150 | + return max(0.0, min(100.0, percentage)) |
| 151 | + |
44 | 152 |
|
| 153 | +def clear_cache(): |
| 154 | + """Clear the battery voltage cache to force fresh reading on next call.""" |
| 155 | + global _cached_raw_adc, _last_read_time |
| 156 | + _cached_raw_adc = None |
| 157 | + _last_read_time = 0 |
0 commit comments