Skip to content

Commit 28147fb

Browse files
Disable wifi when reading ADC2 voltage
1 parent e5d7a11 commit 28147fb

File tree

5 files changed

+162
-37
lines changed

5 files changed

+162
-37
lines changed

internal_filesystem/builtin/apps/com.micropythonos.osupdate/assets/osupdate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ def download_and_install(self, url, progress_callback=None, should_continue_call
528528

529529
except Exception as e:
530530
result['error'] = str(e)
531-
print(f"UpdateDownloader: Error during download: {e}")
531+
print(f"UpdateDownloader: Error during download: {e}") # -113 when wifi disconnected
532532

533533
return result
534534

internal_filesystem/lib/mpos/battery_voltage.py

Lines changed: 140 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,153 @@
55

66
adc = None
77
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+
820

9-
# This gets called by (the device-specific) boot*.py
1021
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
1236
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
1541
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
1943
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.
2150
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
2366
if not adc:
2467
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+
42141
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+
44152

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

internal_filesystem/lib/mpos/board/fri3d_2024.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,11 @@ def keypad_read_cb(indev, data):
258258
indev.enable(True) # NOQA
259259

260260
# Battery voltage ADC measuring
261+
# NOTE: GPIO13 is on ADC2, which requires WiFi to be disabled during reading on ESP32-S3.
262+
# battery_voltage.py handles this automatically: disables WiFi, reads ADC, reconnects WiFi.
263+
# Readings are cached for 30 seconds to minimize WiFi interruptions.
261264
import mpos.battery_voltage
262-
mpos.battery_voltage.init_adc(13, 2 / 1000)
265+
mpos.battery_voltage.init_adc(13, 3.3 * 2 / 4095)
263266

264267
import mpos.sdcard
265268
mpos.sdcard.init(spi_bus, cs_pin=14)

internal_filesystem/lib/mpos/board/linux.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,12 @@ def catch_escape_key(indev, indev_data):
8585
# print(f"boot_unix: code={event_code}") # target={event.get_target()}, user_data={event.get_user_data()}, param={event.get_param()}
8686
#keyboard.add_event_cb(keyboard_cb, lv.EVENT.ALL, None)
8787

88-
print("boot_unix.py finished")
88+
89+
# Simulated battery voltage ADC measuring
90+
import mpos.battery_voltage
91+
mpos.battery_voltage.init_adc(999, (3.3 / 4095) * 2)
92+
93+
print("linux.py finished")
8994

9095

9196

internal_filesystem/lib/mpos/ui/topmenu.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
CLOCK_UPDATE_INTERVAL = 1000 # 10 or even 1 ms doesn't seem to change the framerate but 100ms is enough
1313
WIFI_ICON_UPDATE_INTERVAL = 1500
14-
BATTERY_ICON_UPDATE_INTERVAL = 5000
14+
BATTERY_ICON_UPDATE_INTERVAL = 30000 # not too often, because on fri3d_2024, this briefly disables wifi
1515
TEMPERATURE_UPDATE_INTERVAL = 2000
1616
MEMFREE_UPDATE_INTERVAL = 5000 # not too frequent because there's a forced gc.collect() to give it a reliable value
1717

@@ -92,9 +92,10 @@ def create_notification_bar():
9292
temp_label = lv.label(notification_bar)
9393
temp_label.set_text("00°C")
9494
temp_label.align_to(time_label, lv.ALIGN.OUT_RIGHT_MID, mpos.ui.pct_of_display_width(7) , 0)
95-
memfree_label = lv.label(notification_bar)
96-
memfree_label.set_text("")
97-
memfree_label.align_to(temp_label, lv.ALIGN.OUT_RIGHT_MID, mpos.ui.pct_of_display_width(7), 0)
95+
if False:
96+
memfree_label = lv.label(notification_bar)
97+
memfree_label.set_text("")
98+
memfree_label.align_to(temp_label, lv.ALIGN.OUT_RIGHT_MID, mpos.ui.pct_of_display_width(7), 0)
9899
#style = lv.style_t()
99100
#style.init()
100101
#style.set_text_font(lv.font_montserrat_8) # tiny font
@@ -134,7 +135,11 @@ def update_time(timer):
134135
print("Warning: could not check WLAN status:", str(e))
135136

136137
def update_battery_icon(timer=None):
137-
percent = mpos.battery_voltage.get_battery_percentage()
138+
try:
139+
percent = mpos.battery_voltage.get_battery_percentage()
140+
except Exception as e:
141+
print(f"battery_voltage.get_battery_percentage got exception, not updating battery_icon: {e}")
142+
return
138143
if percent > 80: # 4.1V
139144
battery_icon.set_text(lv.SYMBOL.BATTERY_FULL)
140145
elif percent > 60: # 4.0V
@@ -149,7 +154,6 @@ def update_battery_icon(timer=None):
149154
# Percentage is not shown for now:
150155
#battery_label.set_text(f"{round(percent)}%")
151156
#battery_label.remove_flag(lv.obj.FLAG.HIDDEN)
152-
update_battery_icon() # run it immediately instead of waiting for the timer
153157

154158
def update_wifi_icon(timer):
155159
from mpos.net.wifi_service import WifiService
@@ -182,7 +186,7 @@ def update_memfree(timer):
182186

183187
lv.timer_create(update_time, CLOCK_UPDATE_INTERVAL, None)
184188
lv.timer_create(update_temperature, TEMPERATURE_UPDATE_INTERVAL, None)
185-
lv.timer_create(update_memfree, MEMFREE_UPDATE_INTERVAL, None)
189+
#lv.timer_create(update_memfree, MEMFREE_UPDATE_INTERVAL, None)
186190
lv.timer_create(update_wifi_icon, WIFI_ICON_UPDATE_INTERVAL, None)
187191
lv.timer_create(update_battery_icon, BATTERY_ICON_UPDATE_INTERVAL, None)
188192

0 commit comments

Comments
 (0)