diff --git a/software/src/main.py b/software/src/main.py index 768ead1..613ff97 100644 --- a/software/src/main.py +++ b/software/src/main.py @@ -53,6 +53,13 @@ def setup_wifi(): print(f"ifconfig: {wlan.ifconfig()}") +async def wdt_task(wdt): + # TODO: more checking of app health + # Right now this only protects against the asyncio executor crashing completely + while True: + await asyncio.sleep_ms(500) + wdt.feed() + DB_PATH = '/sd/tonberry.db' config = Configuration() @@ -97,8 +104,10 @@ def run(): start_webserver(config, the_app) # Start + wdt = machine.WDT(timeout=1000) asyncio.create_task(aiorepl.task({'timer_manager': TimerManager(), 'app': the_app})) + asyncio.create_task(wdt_task(wdt)) asyncio.get_event_loop().run_forever() diff --git a/software/src/nfc/nfc.py b/software/src/nfc/nfc.py index 01fd8b8..650c80c 100644 --- a/software/src/nfc/nfc.py +++ b/software/src/nfc/nfc.py @@ -6,6 +6,7 @@ Copyright (c) 2025 Matthias Blankertz import asyncio import time +from utils import safe_callback from mfrc522 import MFRC522 try: @@ -74,7 +75,7 @@ class Nfc: self.last_uid = uid self.last_uid_timestamp = time.ticks_us() if self.cb is not None and last_callback_uid != uid: - self.cb.onTagChange(uid) + safe_callback(lambda: self.cb.onTagChange(uid), "nfc tag change") last_callback_uid = uid await asyncio.sleep_ms(poll_interval_ms) diff --git a/software/src/utils/__init__.py b/software/src/utils/__init__.py index 3f5d65d..cdb1ec8 100644 --- a/software/src/utils/__init__.py +++ b/software/src/utils/__init__.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: MIT # Copyright (c) 2025 Matthias Blankertz +from utils.helpers import safe_callback from utils.buttons import Buttons from utils.config import Configuration from utils.leds import LedManager @@ -11,4 +12,4 @@ from utils.sdcontext import SDContext from utils.timer import TimerManager __all__ = ["BTreeDB", "BTreeFileManager", "Buttons", "Configuration", "get_pin_index", "LedManager", "MBRPartition", - "SDContext", "TimerManager"] + "safe_callback", "SDContext", "TimerManager"] diff --git a/software/src/utils/buttons.py b/software/src/utils/buttons.py index a1f1c6e..b6b5a24 100644 --- a/software/src/utils/buttons.py +++ b/software/src/utils/buttons.py @@ -5,6 +5,7 @@ import asyncio import machine import micropython import time +from utils import safe_callback try: from typing import TYPE_CHECKING # type: ignore except ImportError: @@ -74,4 +75,4 @@ class Buttons: await self.int_flag.wait() while len(self.pressed) > 0: what = self.pressed.pop() - self.cb.onButtonPressed(what) + safe_callback(lambda: self.cb.onButtonPressed(what), "button callback") diff --git a/software/src/utils/helpers.py b/software/src/utils/helpers.py new file mode 100644 index 0000000..afb91a6 --- /dev/null +++ b/software/src/utils/helpers.py @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2025 Matthias Blankertz + +import sys + + +def safe_callback(func, name="callback"): + try: + func() + except Exception as ex: + print(f"Uncaught exception in {name}") + sys.print_exception(ex) diff --git a/software/src/utils/timer.py b/software/src/utils/timer.py index e389ee5..ae43f60 100644 --- a/software/src/utils/timer.py +++ b/software/src/utils/timer.py @@ -4,6 +4,7 @@ import asyncio import heapq import time +from utils import safe_callback TIMER_DEBUG = True @@ -49,28 +50,36 @@ class TimerManager(object): heapq.heapify(self.timers) return i + def _next_timeout(self): + if len(self.timers) == 0: + if self.timer_debug: + print("timer: worker: queue empty") + return None + cur_nearest = self.timers[0][0] + next_timeout = cur_nearest - time.ticks_ms() + if self.timer_debug: + if next_timeout > 0: + print(f"timer: worker: next is {self.timers[0]}, sleep {next_timeout} ms") + else: + print(f"timer: worker: {self.timers[0]} elapsed @{cur_nearest}, delay {-next_timeout} ms") + return next_timeout + + async def _wait(self, timeout): + try: + await asyncio.wait_for_ms(self.worker_event.wait(), timeout) + if self.timer_debug: + print("timer: worker: event") + # got woken up due to event + self.worker_event.clear() + return True + except asyncio.TimeoutError: + return False + async def _timer_worker(self): while True: - if len(self.timers) == 0: - # Nothing to do - await self.worker_event.wait() - if self.timer_debug: - print("_timer_worker: event 0") - self.worker_event.clear() - continue - cur_nearest = self.timers[0][0] - wait_time = cur_nearest - time.ticks_ms() - if wait_time > 0: - if self.timer_debug: - print(f"_timer_worker: next is {self.timers[0]}, sleep {wait_time} ms") - try: - await asyncio.wait_for_ms(self.worker_event.wait(), wait_time) - if self.timer_debug: - print("_timer_worker: event 1") - # got woken up due to event - self.worker_event.clear() - continue - except asyncio.TimeoutError: - pass - _, callback = heapq.heappop(self.timers) - callback() + next_timeout = self._next_timeout() + if next_timeout is None or next_timeout > 0: + await self._wait(next_timeout) + else: + _, callback = heapq.heappop(self.timers) + safe_callback(callback, "timer callback")