Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/about/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ def on_button_press(self, btn):
elif btn == api.BTN_DOWN:
self._scroll = min(self._max_scroll, self._scroll + 12)
self._dirty = True
elif btn == api.BTN_A:
# Version is the headline detail on this page — pressing A
# opens the dedicated Updates app where the user can run
# Check + Install. Matches the Settings → Version flow.
try:
self._os.launch("updates")
except Exception:
pass

def _info_rows(self):
secs = self._last_s
Expand Down
2 changes: 1 addition & 1 deletion apps/launcher/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ def on_enter(self, os):
# bt + wifi are surfaced through Settings rather than as their
# own drawer tiles — Settings has dedicated rows that launch
# those screens, so a separate tile would just be duplication.
DRAWER_HIDDEN = ("launcher", "bt", "wifi", "gestures")
DRAWER_HIDDEN = ("launcher", "bt", "wifi", "gestures", "updates")
self._apps = [a for a in list_apps() if a["dir"] not in DRAWER_HIDDEN]

# Icon cache lives at module scope (see _ICON_CACHE above). We
Expand Down
112 changes: 12 additions & 100 deletions apps/settings/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,13 @@ def on_enter(self, os):
getter=lambda: self._gestures_summary(),
setter=lambda v: self._open_gestures()),

_Row("Version", "info",
getter=self._os_version),
_Row("Check Update","action",
setter=lambda v: self._check_update()),
_Row("Install Update","action",
setter=lambda v: self._install_update()),
# Version → tap to open the full Updates page (Check +
# Install live there now, mirroring the GitHub Releases UI).
# The value column shows the version string so the row
# still reads as a status line at a glance.
_Row("Version", "action",
getter=self._os_version,
setter=lambda v: self._open_updates()),
_Row("Reboot", "action",
setter=lambda v: self._reboot()),
]
Expand Down Expand Up @@ -204,104 +205,15 @@ def _os_version():
except Exception:
return "?"

# ── OTA actions ──────────────────────────────────────────────────────
# Three-stage flow so the UI never blocks on a long HTTP call without
# the user knowing what's happening:
# 1. check() — hit GitHub releases (T_GH_API timeout, ~10 s max)
# 2. peek() — fetch the manifest, compute SHA diff, total bytes
# 3. download() — only the changed files (bounded per-file timeout)
#
# We store intermediate state on the OS settings dict so the home
# screen / About page can render a status pill without re-checking.
def _check_update(self):
# ── Updates page launcher ───────────────────────────────────────────
# All version / OTA actions live in apps/updates/ now. Settings just
# forwards there, same pattern as WiFi / BT / Gestures.
def _open_updates(self):
try:
from oreoOS import ota
except Exception:
self._set_ota_status("error"); return

rel = self._ota_safe(lambda: ota.check())
if not rel:
self._set_ota_status("up-to-date")
return

# Surface the discovery in the notification ring so the user
# sees the new version arrive even if they leave Settings before
# the install finishes. De-duped on version inside the helper.
try:
ota.push_update_notification(rel.get("version", ""))
self._os.launch("updates")
except Exception:
pass

peeked = self._ota_safe(lambda: ota.peek(rel))
if not peeked:
self._set_ota_status("peek-failed")
return

# Park everything we just learned so _install_update / the
# confirmation popup can act on it without re-fetching.
self._os.settings_set("ota_pending_version", rel.get("version", ""))
self._os.settings_set("ota_pending_bytes", peeked["bytes"])
self._os.settings_set("ota_pending_major", peeked["major"])
self._os.settings_set("ota_pending_changed", len(peeked["changed"]))
# Stash the manifest URL too in case the popup needs to re-peek
# on the next boot (cleared in _install_update).
self._os.settings_set("ota_pending_url", rel["manifest_url"])

# Small + non-major patches auto-stage in the background. Anything
# bigger requires user confirmation — Settings flips a flag the
# home screen reads to draw a confirmation popup.
if peeked["small"] and not peeked["major"]:
self._set_ota_status("downloading")
ok = self._ota_safe(lambda: ota.download(peeked))
self._set_ota_status("ready" if ok else "download-failed")
else:
# Defer to the popup. The home screen reads this flag.
self._set_ota_status("needs-confirm")
self._os.settings_set("ota_pending_peek_ok", True)

def _install_update(self):
"""Triggered after the user confirms in the popup. Downloads (if
not already staged) then reboots so the boot hook applies."""
try:
from oreoOS import ota
except Exception:
return

if not ota.is_pending():
# Need to actually fetch the bytes first. Re-peek so we don't
# rely on transient state; this is the user-explicit path so
# showing "downloading" is appropriate.
rel = self._ota_safe(lambda: ota.check())
if not rel:
return
peeked = self._ota_safe(lambda: ota.peek(rel))
if not peeked:
return
self._set_ota_status("downloading")
ok = self._ota_safe(lambda: ota.download(peeked))
if not ok:
self._set_ota_status("download-failed")
return
self._set_ota_status("ready")
# Clear the popup flag and kick the chip.
self._os.settings_set("ota_pending_peek_ok", False)
self._reboot()

def _set_ota_status(self, s):
try:
self._os.settings_set("ota_status", s)
except Exception:
pass

@staticmethod
def _ota_safe(callable_):
"""Run an OTA call inside try/except so a thrown HTTP error never
bubbles up into the UI's button-press handler."""
try:
return callable_()
except Exception:
return None

def _reboot(self):
try:
import machine
Expand Down
Empty file added apps/updates/__init__.py
Empty file.
Loading
Loading