Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App error handling #51

Merged
merged 2 commits into from
May 28, 2024
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: 6 additions & 2 deletions drivers/gc9a01/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "mp_uctx.h"
#include <math.h>

bool gfx_inited = false;

static mp_obj_t bsp_init() {
flow3r_bsp_display_init();
Expand All @@ -12,8 +13,11 @@ static mp_obj_t bsp_init() {
static MP_DEFINE_CONST_FUN_OBJ_0(bsp_init_obj, bsp_init);

static mp_obj_t gfx_init() {
st3m_gfx_init();
return MP_ROM_QSTR(MP_QSTR_sample);
if (!gfx_inited) {
st3m_gfx_init();
gfx_inited = true;
}
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_0(gfx_init_obj, gfx_init);

Expand Down
41 changes: 25 additions & 16 deletions modules/firmware_apps/poweroff.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,43 @@
import app
from app_components.tokens import clear_background

from app_components.dialog import YesNoDialog


class PowerOff(app.App):
async def run(self, render_update):
# Render initial state
await render_update()
def __init__(self):
super().__init__()
self.off = False

# Create a yes/no dialogue, add it to the overlays
dialog = YesNoDialog("Power off?", self)
self.overlays = [dialog]
async def run(self, render_update):
while not self.off:
# Render initial state
await render_update()

# Wait for an answer from the dialogue, and if it was yes, randomise colour
if await dialog.run(render_update):
import machine
import bq25895
# Create a yes/no dialogue, add it to the overlays
dialog = YesNoDialog("Power off?", self)

bq25895.bq25895(machine.I2C(7)).disconnect_battery()
# Wait for an answer from the dialogue, and if it was yes, randomise colour
if await dialog.run(render_update):
import machine
import bq25895

# Remove the dialogue and re-render
self.overlays = []
bq25895.bq25895(machine.I2C(7)).disconnect_battery()
self.off = True
else:
self.minimise()

while True:
await render_update()

def draw(self, ctx):
ctx.save()
clear_background(ctx)
if not self.off:
clear_background(ctx)
if self.off:
ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill()
ctx.font_size = 22
ctx.text_align = ctx.CENTER
ctx.rgb(0.96, 0.49, 0).move_to(0, -11).text("It is now safe to unplug")
ctx.move_to(0, 11).text("your badge.")
ctx.restore()

self.draw_overlays(ctx)
22 changes: 17 additions & 5 deletions modules/system/launcher/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
from app_components import clear_background
from perf_timer import PerfTimer
from system.eventbus import eventbus
from system.scheduler.events import RequestStartAppEvent, RequestForegroundPushEvent
from system.scheduler.events import (
RequestStartAppEvent,
RequestForegroundPushEvent,
RequestStopAppEvent,
)


def path_isfile(path):
Expand Down Expand Up @@ -36,6 +40,18 @@ def recursive_delete(path):


class Launcher(App):
def __init__(self):
self.update_menu()
self._apps = {}
eventbus.on_async(RequestStopAppEvent, self._handle_stop_app, self)

async def _handle_stop_app(self, event: RequestStopAppEvent):
# If an app is stopped, remove our cache of it as it needs restarting
for key, app in self._apps.items():
if app == event.app:
self._apps[key] = None
print(f"Removing launcher cache for {key}")

def list_user_apps(self):
with PerfTimer("List user apps"):
apps = []
Expand Down Expand Up @@ -129,10 +145,6 @@ def launch(self, item):
# f.write(str(self.window.focus_idx()))
# eventbus.emit(RequestForegroundPopEvent(self))

def __init__(self):
self.update_menu()
self._apps = {}

def select_handler(self, item):
for app in self.menu_items:
if item == app["name"]:
Expand Down
34 changes: 29 additions & 5 deletions modules/system/scheduler/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio
import time
import display
import sys
import time

from perf_timer import PerfTimer
from system.eventbus import eventbus
Expand All @@ -10,6 +11,7 @@
RequestStartAppEvent,
RequestStopAppEvent,
)
from system.notification.events import ShowNotificationEvent


class _Scheduler:
Expand Down Expand Up @@ -90,15 +92,23 @@ def stop_app(self, app):

try:
self.background_tasks[app].cancel()
print("Stopping ", app)
print("Stopping background", app)
del self.background_tasks[app]
except KeyError:
pass

try:
self.update_tasks[app].cancel()
print("Stopping ", app)
del self.update_tasks[app]
except KeyError:
pass

del self.apps[app_idx]
del self.last_update_times[app_idx]

eventbus.deregister(app)
self.mark_focused()

def app_is_foregrounded(self, app):
return self.app_is_focused(app) or app in self.on_top_stack
Expand Down Expand Up @@ -170,8 +180,13 @@ async def app_wrapper():
try:
await app.run(mark_update_finished)
except Exception as e:
print(e)
app.minimise()
eventbus.emit(RequestStopAppEvent(app=app))
eventbus.emit(
ShowNotificationEvent(
message=f"{app.__class__.__name__} has crashed"
)
)
sys.print_exception(e, sys.stderr)

self.update_tasks[app] = asyncio.create_task(app_wrapper())

Expand Down Expand Up @@ -201,7 +216,16 @@ async def _render_task(self):
for app in self.foreground_stack:
with PerfTimer(f"rendering {app}"):
ctx.save()
app.draw(ctx)
try:
app.draw(ctx)
except Exception as e:
eventbus.emit(RequestStopAppEvent(app=app))
sys.print_exception(e, sys.stderr)
eventbus.emit(
ShowNotificationEvent(
message=f"{app.__class__.__name__} has crashed"
)
)
ctx.restore()
for app in self.on_top_stack:
with PerfTimer(f"rendering {app}"):
Expand Down
Loading