Skip to content

Commit 124df71

Browse files
OSUpdate app: use update_ui_threadsafe_if_foreground
Now it no longer crashes if the user moves away while the OSUpdate is ongoing.
1 parent a1ac5a6 commit 124df71

File tree

2 files changed

+32
-15
lines changed

2 files changed

+32
-15
lines changed

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

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
class OSUpdate(Activity):
1212

13-
keep_running = True
1413
download_update_url = None
1514

1615
# Widgets:
@@ -58,9 +57,6 @@ def onStart(self, screen):
5857
print("Showing update info...")
5958
self.show_update_info()
6059

61-
def onStop(self, screen):
62-
self.keep_running = False # this is checked by the update_with_lvgl thread
63-
6460
def show_update_info(self):
6561
self.status_label.set_text("Checking for OS updates...")
6662
hwid = mpos.info.get_hardware_id()
@@ -140,8 +136,8 @@ def force_update_clicked(self):
140136

141137
def progress_callback(self, percent):
142138
print(f"OTA Update: {percent:.1f}%")
143-
lv.async_call(lambda l: self.progress_label.set_text(f"OTA Update: {percent:.2f}%"), None)
144-
lv.async_call(lambda l: self.progress_bar.set_value(int(percent), True), None)
139+
self.update_ui_threadsafe_if_foreground(self.progress_bar.set_value, int(percent), True)
140+
self.update_ui_threadsafe_if_foreground(self.progress_label.set_text, f"OTA Update: {percent:.2f}%")
145141
time.sleep_ms(100)
146142

147143
# Custom OTA update with LVGL progress
@@ -168,7 +164,7 @@ def update_with_lvgl(self, url):
168164
i = 0
169165
total_size = round_up_to_multiple(total_size, chunk_size)
170166
print(f"Starting OTA update of size: {total_size}")
171-
while self.keep_running: # stop if the user navigates away
167+
while self.has_foreground(): # stop if the user navigates away
172168
time.sleep_ms(100) # don't hog the CPU
173169
chunk = response.raw.read(chunk_size)
174170
if not chunk:
@@ -187,7 +183,7 @@ def update_with_lvgl(self, url):
187183
response.close()
188184
try:
189185
if bytes_written >= total_size:
190-
lv.async_call(lambda l: self.status_label.set_text("Update finished! Please restart."), None)
186+
lv.update_ui_threadsafe_if_foreground(self.status_label.set_text, "Update finished! Please restart.")
191187
if not simulate: # if the update was completely installed
192188
next_partition.set_boot()
193189
import machine
@@ -196,12 +192,11 @@ def update_with_lvgl(self, url):
196192
else:
197193
print("This is an OSUpdate simulation, not attempting to restart the device.")
198194
else:
199-
lv.async_call(lambda l: self.status_label.set_text(f"Wrote {bytes_written} < {total_size} so not enough!"), None)
200-
self.install_button.remove_state(lv.STATE.DISABLED) # allow retry
195+
self.update_ui_threadsafe_if_foreground(self.status_label.set_text, f"Wrote {bytes_written} < {total_size} so not enough!")
196+
self.update_ui_threadsafe_if_foreground(self.install_button.remove_state, lv.STATE.DISABLED) # allow retry
201197
except Exception as e:
202-
if self.keep_running:
203-
lv.async_call(lambda l: self.status_label.set_text(f"Update error: {e}"), None)
204-
self.install_button.remove_state(lv.STATE.DISABLED) # allow retry
198+
self.update_ui_threadsafe_if_foreground(self.status_label.set_text, f"Update error: {e}")
199+
self.update_ui_threadsafe_if_foreground(self.install_button.remove_state, lv.STATE.DISABLED)
205200

206201
# Non-class functions:
207202

internal_filesystem/lib/mpos/app/activity.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import lvgl as lv
12
import mpos.ui
23

34
class Activity:
@@ -6,15 +7,16 @@ def __init__(self):
67
self.intent = None # Store the intent that launched this activity
78
self.result = None
89
self._result_callback = None
10+
self._has_foreground = None
911

1012
def onCreate(self):
1113
pass
1214
def onStart(self, screen):
1315
pass
1416
def onResume(self, screen): # app gets foreground
15-
pass
17+
self._has_foreground = True
1618
def onPause(self, screen): # app goes to background
17-
pass
19+
self._has_foreground = False
1820
def onStop(self, screen):
1921
pass
2022
def onDestroy(self, screen):
@@ -55,3 +57,23 @@ def finish(self):
5557
self._result_callback = None # Clean up
5658
except AttributeError as e:
5759
self.initError(e)
60+
61+
# Apps may want to check this to cancel heavy operations if the user moves away
62+
def has_foreground(self):
63+
return self._has_foreground
64+
65+
# Execute a function if the Activity is in the foreground
66+
def if_foreground(self, func, *args, **kwargs):
67+
if self._has_foreground:
68+
return func(*args, **kwargs)
69+
else:
70+
print(f"[if_foreground] Skipped {func} because _has_foreground=False")
71+
return None
72+
73+
# Update the UI in a threadsafe way if the Activity is in the foreground
74+
def update_ui_threadsafe_if_foreground(self, func, *args, **kwargs):
75+
# lv.async_call() is needed to update the UI from another thread than the main one (as LVGL is not thread safe)
76+
lv.async_call(
77+
lambda _: self.if_foreground(func, *args, **kwargs),
78+
None
79+
)

0 commit comments

Comments
 (0)