diff --git a/internal_filesystem/builtin/apps/com.micropythonos.osupdate/assets/osupdate.py b/internal_filesystem/builtin/apps/com.micropythonos.osupdate/assets/osupdate.py index ee5e6a6c..ab5c518e 100644 --- a/internal_filesystem/builtin/apps/com.micropythonos.osupdate/assets/osupdate.py +++ b/internal_filesystem/builtin/apps/com.micropythonos.osupdate/assets/osupdate.py @@ -528,7 +528,7 @@ class UpdateDownloader: except Exception as e: result['error'] = str(e) - print(f"UpdateDownloader: Error during download: {e}") + print(f"UpdateDownloader: Error during download: {e}") # -113 when wifi disconnected return result diff --git a/internal_filesystem/lib/mpos/battery_voltage.py b/internal_filesystem/lib/mpos/battery_voltage.py index e25aaf0c..f6bc3571 100644 --- a/internal_filesystem/lib/mpos/battery_voltage.py +++ b/internal_filesystem/lib/mpos/battery_voltage.py @@ -5,40 +5,153 @@ MAX_VOLTAGE = 4.15 adc = None scale_factor = 0 +adc_pin = None + +# Cache to reduce WiFi interruptions (ADC2 requires WiFi to be disabled) +_cached_raw_adc = None +_last_read_time = 0 +CACHE_DURATION_MS = 30000 # 30 seconds + + +def _is_adc2_pin(pin): + """Check if pin is on ADC2 (ESP32-S3: GPIO11-20).""" + return 11 <= pin <= 20 + -# This gets called by (the device-specific) boot*.py def init_adc(pinnr, sf): - global adc, scale_factor - try: - print(f"Initializing ADC pin {pinnr} with scale_factor {scale_factor}") - from machine import ADC, Pin # do this inside the try because it will fail on desktop - adc = ADC(Pin(pinnr)) - # Set ADC to 11dB attenuation for 0–3.3V range (common for ESP32) - adc.atten(ADC.ATTN_11DB) - scale_factor = sf - except Exception as e: - print("Info: this platform has no ADC for measuring battery voltage") + """ + Initialize ADC for battery voltage monitoring. -def read_battery_voltage(): + 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 + sf: Scale factor to convert raw ADC (0-4095) to battery voltage + """ + global adc, scale_factor, adc_pin + scale_factor = sf + adc_pin = pinnr + try: + print(f"Initializing ADC pin {pinnr} with scale_factor {sf}") + 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}") + + +def read_raw_adc(force_refresh=False): + """ + Read raw ADC value (0-4095) with 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 if not adc: import random - random_voltage = random.randint(round(MIN_VOLTAGE*100),round(MAX_VOLTAGE*100)) / 100 - #print(f"returning random voltage: {random_voltage}") - return random_voltage - # Read raw ADC value - total = 0 - # Read multiple times to try to reduce variability. - # Reading 10 times takes around 3ms so it's fine... - for _ in range(10): - total = total + adc.read() - raw_value = total / 10 - #print(f"read_battery_voltage raw_value: {raw_value}") - voltage = raw_value * scale_factor - # Clamp to 0–4.2V range for LiPo battery - voltage = max(0, min(voltage, MAX_VOLTAGE)) - return voltage + return random.randint(1900, 2600) if scale_factor == 0 else random.randint( + int(MIN_VOLTAGE / scale_factor), int(MAX_VOLTAGE / scale_factor) + ) + + # 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_MS: + return _cached_raw_adc + + # Check if this is an ADC2 pin (requires WiFi disable) + needs_wifi_disable = adc_pin is not None and _is_adc2_pin(adc_pin) + + # Import WifiService only if needed + WifiService = None + if needs_wifi_disable: + try: + from mpos.net.wifi_service import WifiService + except ImportError: + pass + + # Check if WiFi operations are in progress + if WifiService and WifiService.wifi_busy: + raise RuntimeError("Cannot read battery voltage: WifiService is busy") + + # Disable WiFi for ADC2 reading + wifi_was_connected = False + if needs_wifi_disable and WifiService: + wifi_was_connected = WifiService.is_connected() + WifiService.wifi_busy = True + WifiService.disconnect() + 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.wifi_busy = False + if wifi_was_connected: + # Trigger reconnection in background thread + try: + import _thread + _thread.start_new_thread(WifiService.auto_connect, ()) + except Exception as e: + print(f"battery_voltage: Failed to start reconnect thread: {e}") + + +def read_battery_voltage(force_refresh=False): + """ + Read battery voltage in volts. + + Args: + force_refresh: Bypass cache and force fresh reading + + Returns: + float: Battery voltage in volts (clamped to 0-MAX_VOLTAGE) + """ + raw = read_raw_adc(force_refresh) + voltage = raw * scale_factor + return max(0.0, min(voltage, MAX_VOLTAGE)) + -# Could be interesting to keep a "rolling average" of the percentage so that it doesn't fluctuate too quickly def get_battery_percentage(): - return (read_battery_voltage() - MIN_VOLTAGE) * 100 / (MAX_VOLTAGE - MIN_VOLTAGE) + """ + Get battery charge percentage. + Returns: + float: Battery percentage (0-100) + """ + voltage = read_battery_voltage() + percentage = (voltage - MIN_VOLTAGE) * 100.0 / (MAX_VOLTAGE - MIN_VOLTAGE) + return max(0.0, min(100.0, percentage)) + + +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 diff --git a/internal_filesystem/lib/mpos/board/fri3d_2024.py b/internal_filesystem/lib/mpos/board/fri3d_2024.py index 243c75c7..db3c4ebf 100644 --- a/internal_filesystem/lib/mpos/board/fri3d_2024.py +++ b/internal_filesystem/lib/mpos/board/fri3d_2024.py @@ -258,8 +258,11 @@ indev.set_display(disp) # different from display indev.enable(True) # NOQA # Battery voltage ADC measuring +# NOTE: GPIO13 is on ADC2, which requires WiFi to be disabled during reading on ESP32-S3. +# battery_voltage.py handles this automatically: disables WiFi, reads ADC, reconnects WiFi. +# Readings are cached for 30 seconds to minimize WiFi interruptions. import mpos.battery_voltage -mpos.battery_voltage.init_adc(13, 2 / 1000) +mpos.battery_voltage.init_adc(13, 3.3 * 2 / 4095) import mpos.sdcard mpos.sdcard.init(spi_bus, cs_pin=14) diff --git a/internal_filesystem/lib/mpos/board/linux.py b/internal_filesystem/lib/mpos/board/linux.py index 3256f53b..54f2ab23 100644 --- a/internal_filesystem/lib/mpos/board/linux.py +++ b/internal_filesystem/lib/mpos/board/linux.py @@ -85,7 +85,12 @@ except Exception as e: # print(f"boot_unix: code={event_code}") # target={event.get_target()}, user_data={event.get_user_data()}, param={event.get_param()} #keyboard.add_event_cb(keyboard_cb, lv.EVENT.ALL, None) -print("boot_unix.py finished") + +# Simulated battery voltage ADC measuring +import mpos.battery_voltage +mpos.battery_voltage.init_adc(999, (3.3 / 4095) * 2) + +print("linux.py finished") diff --git a/internal_filesystem/lib/mpos/ui/topmenu.py b/internal_filesystem/lib/mpos/ui/topmenu.py index ac59bbc3..715047a4 100644 --- a/internal_filesystem/lib/mpos/ui/topmenu.py +++ b/internal_filesystem/lib/mpos/ui/topmenu.py @@ -11,7 +11,7 @@ NOTIFICATION_BAR_HEIGHT=24 CLOCK_UPDATE_INTERVAL = 1000 # 10 or even 1 ms doesn't seem to change the framerate but 100ms is enough WIFI_ICON_UPDATE_INTERVAL = 1500 -BATTERY_ICON_UPDATE_INTERVAL = 5000 +BATTERY_ICON_UPDATE_INTERVAL = 30000 # not too often, because on fri3d_2024, this briefly disables wifi TEMPERATURE_UPDATE_INTERVAL = 2000 MEMFREE_UPDATE_INTERVAL = 5000 # not too frequent because there's a forced gc.collect() to give it a reliable value @@ -92,9 +92,10 @@ def create_notification_bar(): temp_label = lv.label(notification_bar) temp_label.set_text("00°C") temp_label.align_to(time_label, lv.ALIGN.OUT_RIGHT_MID, mpos.ui.pct_of_display_width(7) , 0) - memfree_label = lv.label(notification_bar) - memfree_label.set_text("") - memfree_label.align_to(temp_label, lv.ALIGN.OUT_RIGHT_MID, mpos.ui.pct_of_display_width(7), 0) + if False: + memfree_label = lv.label(notification_bar) + memfree_label.set_text("") + memfree_label.align_to(temp_label, lv.ALIGN.OUT_RIGHT_MID, mpos.ui.pct_of_display_width(7), 0) #style = lv.style_t() #style.init() #style.set_text_font(lv.font_montserrat_8) # tiny font @@ -134,7 +135,11 @@ def create_notification_bar(): print("Warning: could not check WLAN status:", str(e)) def update_battery_icon(timer=None): - percent = mpos.battery_voltage.get_battery_percentage() + try: + percent = mpos.battery_voltage.get_battery_percentage() + except Exception as e: + print(f"battery_voltage.get_battery_percentage got exception, not updating battery_icon: {e}") + return if percent > 80: # 4.1V battery_icon.set_text(lv.SYMBOL.BATTERY_FULL) elif percent > 60: # 4.0V @@ -149,7 +154,6 @@ def create_notification_bar(): # Percentage is not shown for now: #battery_label.set_text(f"{round(percent)}%") #battery_label.remove_flag(lv.obj.FLAG.HIDDEN) - update_battery_icon() # run it immediately instead of waiting for the timer def update_wifi_icon(timer): from mpos.net.wifi_service import WifiService @@ -182,7 +186,7 @@ def create_notification_bar(): lv.timer_create(update_time, CLOCK_UPDATE_INTERVAL, None) lv.timer_create(update_temperature, TEMPERATURE_UPDATE_INTERVAL, None) - lv.timer_create(update_memfree, MEMFREE_UPDATE_INTERVAL, None) + #lv.timer_create(update_memfree, MEMFREE_UPDATE_INTERVAL, None) lv.timer_create(update_wifi_icon, WIFI_ICON_UPDATE_INTERVAL, None) lv.timer_create(update_battery_icon, BATTERY_ICON_UPDATE_INTERVAL, None)