diff --git a/bricks/_common/micropython.c b/bricks/_common/micropython.c index c8d1cb7cd..dcb26c4a4 100644 --- a/bricks/_common/micropython.c +++ b/bricks/_common/micropython.c @@ -32,11 +32,25 @@ #include "py/stackctrl.h" #include "py/stream.h" +// Outermost nlr buffer. +nlr_buf_t nlr_main; + // Implementation for MICROPY_EVENT_POLL_HOOK void pb_event_poll_hook(void) { while (pbio_do_one_event()) { } + // On forced exit, pop back to outer nlr buffer so the exception will + // always be raised and the application program exits cleanly. We also need + // MP_STATE_THREAD(mp_pending_exception) to be the system exit exception + // but we don't check it because it is set along with pyexec_system_exit. + if (pyexec_system_exit == PYEXEC_FORCED_EXIT) { + while (MP_STATE_MAIN_THREAD(nlr_top) != &nlr_main) { + nlr_pop(); + } + pyexec_system_exit = 0; + } + mp_handle_pending(true); // Platform-specific code to run on completing the poll hook. @@ -44,7 +58,7 @@ void pb_event_poll_hook(void) { } // callback for when stop button is pressed in IDE or on hub -void pbsys_main_stop_program(void) { +void pbsys_main_stop_program(bool force_stop) { static const mp_obj_tuple_t args = { .base = { .type = &mp_type_tuple }, @@ -53,9 +67,6 @@ void pbsys_main_stop_program(void) { }; static mp_obj_exception_t system_exit; - // Trigger soft reboot. - pyexec_system_exit = PYEXEC_FORCED_EXIT; - // Schedule SystemExit exception. system_exit.base.type = &mp_type_SystemExit; system_exit.traceback_alloc = 0; @@ -68,6 +79,11 @@ void pbsys_main_stop_program(void) { MP_STATE_VM(sched_state) = MP_SCHED_PENDING; } #endif + + // IDE stop button and long-press power button will force an exit. + if (force_stop) { + pyexec_system_exit = PYEXEC_FORCED_EXIT; + } } bool pbsys_main_stdin_event(uint8_t c) { @@ -112,8 +128,7 @@ static void mp_vfs_map_minimal_new_reader(mp_reader_t *reader, mp_vfs_map_minima static void run_repl() { // Reset REPL history. readline_init0(); - nlr_buf_t nlr; - if (nlr_push(&nlr) == 0) { + if (nlr_push(&nlr_main) == 0) { // Run the REPL. pyexec_friendly_repl(); nlr_pop(); @@ -121,7 +136,7 @@ static void run_repl() { // clear any pending exceptions (and run any callbacks). mp_handle_pending(false); // Print which exception triggered this. - mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); + mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr_main.ret_val); } } #endif @@ -209,8 +224,7 @@ static mpy_info_t *mpy_data_find(qstr name) { */ static void run_user_program(void) { - nlr_buf_t nlr; - if (nlr_push(&nlr) == 0) { + if (nlr_push(&nlr_main) == 0) { mpy_info_t *info = mpy_data_find(MP_QSTR___main__); if (!info) { @@ -241,11 +255,11 @@ static void run_user_program(void) { mp_handle_pending(false); // Print which exception triggered this. - mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); + mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr_main.ret_val); #if PYBRICKS_OPT_COMPILER // On KeyboardInterrupt, drop to REPL for debugging. - if (mp_obj_exception_match((mp_obj_t)nlr.ret_val, &mp_type_KeyboardInterrupt)) { + if (mp_obj_exception_match((mp_obj_t)nlr_main.ret_val, &mp_type_KeyboardInterrupt)) { // The global scope is preserved to facilitate debugging, but we // stop active resources like motors and sounds. They are stopped diff --git a/bricks/virtualhub/mp_port.c b/bricks/virtualhub/mp_port.c index 60caceb94..7870f7570 100644 --- a/bricks/virtualhub/mp_port.c +++ b/bricks/virtualhub/mp_port.c @@ -34,7 +34,7 @@ #define FORCED_EXIT (0x100) // callback for when stop button is pressed in IDE or on hub -void pbsys_main_stop_program(void) { +void pbsys_main_stop_program(bool force_stop) { static const mp_rom_obj_tuple_t args = { .base = { .type = &mp_type_tuple }, .len = 2, diff --git a/lib/pbio/include/pbsys/main.h b/lib/pbio/include/pbsys/main.h index 001c34390..6fc5ac699 100644 --- a/lib/pbio/include/pbsys/main.h +++ b/lib/pbio/include/pbsys/main.h @@ -52,8 +52,12 @@ void pbsys_main_run_program(pbsys_main_program_t *program); * Stops (cancels) the main application program. * * This should be provided by the application running on top of pbio. + * + * @param [in] force_stop Whether to force stop the program instead of asking + * nicely. This is true when the application must stop + * on shutdown. */ -void pbsys_main_stop_program(void); +void pbsys_main_stop_program(bool force_stop); /** * Handles standard input. @@ -68,7 +72,7 @@ bool pbsys_main_stdin_event(uint8_t c); #else -static inline void pbsys_main_stop_program(void) { +static inline void pbsys_main_stop_program(bool force_stop) { } static inline bool pbsys_main_stdin_event(uint8_t c) { diff --git a/lib/pbio/sys/command.c b/lib/pbio/sys/command.c index 953555f8d..c6f44bd1a 100644 --- a/lib/pbio/sys/command.c +++ b/lib/pbio/sys/command.c @@ -23,7 +23,7 @@ pbio_pybricks_error_t pbsys_command(const uint8_t *data, uint32_t size) { switch (cmd) { case PBIO_PYBRICKS_COMMAND_STOP_USER_PROGRAM: - pbsys_program_stop(); + pbsys_program_stop(false); return PBIO_PYBRICKS_ERROR_OK; case PBIO_PYBRICKS_COMMAND_START_USER_PROGRAM: return pbio_pybricks_error_from_pbio_error(pbsys_program_load_start_user_program()); diff --git a/lib/pbio/sys/program_stop.c b/lib/pbio/sys/program_stop.c index 64825242a..aa71b5867 100644 --- a/lib/pbio/sys/program_stop.c +++ b/lib/pbio/sys/program_stop.c @@ -22,10 +22,14 @@ static bool program_stop_on_shutdown_requested; /** * Request the user program to stop. For example, in MicroPython, this may raise * a SystemExit exception. + * + * @param [in] force_stop Whether to force stop the program instead of asking + * nicely. This is true when the application must stop + * on shutdown. */ -void pbsys_program_stop(void) { +void pbsys_program_stop(bool force_stop) { if (pbsys_status_test(PBIO_PYBRICKS_STATUS_USER_PROGRAM_RUNNING)) { - pbsys_main_stop_program(); + pbsys_main_stop_program(force_stop); } } @@ -48,7 +52,7 @@ void pbsys_program_stop_poll(void) { // Should not request stop more than once. return; } else if (pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST)) { - pbsys_program_stop(); + pbsys_program_stop(true); program_stop_on_shutdown_requested = true; return; } @@ -63,7 +67,7 @@ void pbsys_program_stop_poll(void) { if ((btn & stop_buttons) == stop_buttons) { if (!stop_button_pressed) { stop_button_pressed = true; - pbsys_program_stop(); + pbsys_program_stop(false); } } else { stop_button_pressed = false; diff --git a/lib/pbio/sys/program_stop.h b/lib/pbio/sys/program_stop.h index 13b8fcfef..ad18f781d 100644 --- a/lib/pbio/sys/program_stop.h +++ b/lib/pbio/sys/program_stop.h @@ -5,6 +5,6 @@ #define _PBSYS_SYS_USER_PROGRAM_H_ void pbsys_program_stop_poll(void); -void pbsys_program_stop(void); +void pbsys_program_stop(bool force_stop); #endif // _PBSYS_SYS_USER_PROGRAM_H_