Skip to content

Commit d67fb85

Browse files
authored
Merge pull request #3454 from cwalther/setnextcode
Add supervisor.set_next_code_file()
2 parents 6900513 + 372ddfd commit d67fb85

File tree

6 files changed

+200
-16
lines changed

6 files changed

+200
-16
lines changed

locale/circuitpython.pot

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ msgstr ""
4747
msgid " is of type %q\n"
4848
msgstr ""
4949

50+
#: main.c
51+
msgid " not found.\n"
52+
msgstr ""
53+
5054
#: main.c
5155
msgid " output:\n"
5256
msgstr ""
@@ -2507,7 +2511,7 @@ msgstr ""
25072511
msgid "argsort is not implemented for flattened arrays"
25082512
msgstr ""
25092513

2510-
#: py/runtime.c
2514+
#: py/runtime.c shared-bindings/supervisor/__init__.c
25112515
msgid "argument has wrong type"
25122516
msgstr ""
25132517

main.c

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,12 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
290290
result.exception_type = NULL;
291291
result.exception_line = 0;
292292

293+
bool skip_repl;
294+
bool skip_wait = false;
293295
bool found_main = false;
296+
uint8_t next_code_options = 0;
297+
// Collects stickiness bits that apply in the current situation.
298+
uint8_t next_code_stickiness_situation = SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET;
294299

295300
if (safe_mode == NO_SAFE_MODE) {
296301
static const char * const supported_filenames[] = STRING_LIST(
@@ -312,25 +317,71 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
312317
usb_setup_with_vm();
313318
#endif
314319

315-
// This is where the user's python code is actually executed:
316-
found_main = maybe_run_list(supported_filenames, &result);
317-
// If that didn't work, double check the extensions
318-
#if CIRCUITPY_FULL_BUILD
319-
if (!found_main){
320-
found_main = maybe_run_list(double_extension_filenames, &result);
321-
if (found_main) {
322-
serial_write_compressed(translate("WARNING: Your code filename has two extensions\n"));
320+
// Check if a different run file has been allocated
321+
if (next_code_allocation) {
322+
((next_code_info_t*)next_code_allocation->ptr)->options &= ~SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET;
323+
next_code_options = ((next_code_info_t*)next_code_allocation->ptr)->options;
324+
if (((next_code_info_t*)next_code_allocation->ptr)->filename[0] != '\0') {
325+
const char* next_list[] = {((next_code_info_t*)next_code_allocation->ptr)->filename, ""};
326+
// This is where the user's python code is actually executed:
327+
found_main = maybe_run_list(next_list, &result);
328+
if (!found_main) {
329+
serial_write(((next_code_info_t*)next_code_allocation->ptr)->filename);
330+
serial_write_compressed(translate(" not found.\n"));
331+
}
323332
}
324333
}
325-
#else
326-
(void) found_main;
327-
#endif
334+
// Otherwise, default to the standard list of filenames
335+
if (!found_main) {
336+
// This is where the user's python code is actually executed:
337+
found_main = maybe_run_list(supported_filenames, &result);
338+
// If that didn't work, double check the extensions
339+
#if CIRCUITPY_FULL_BUILD
340+
if (!found_main){
341+
found_main = maybe_run_list(double_extension_filenames, &result);
342+
if (found_main) {
343+
serial_write_compressed(translate("WARNING: Your code filename has two extensions\n"));
344+
}
345+
}
346+
#else
347+
(void) found_main;
348+
#endif
349+
}
328350

329351
// Finished executing python code. Cleanup includes a board reset.
330352
cleanup_after_vm(heap);
331353

354+
// If a new next code file was set, that is a reason to keep it (obviously). Stuff this into
355+
// the options because it can be treated like any other reason-for-stickiness bit. The
356+
// source is different though: it comes from the options that will apply to the next run,
357+
// while the rest of next_code_options is what applied to this run.
358+
if (next_code_allocation != NULL && (((next_code_info_t*)next_code_allocation->ptr)->options & SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET)) {
359+
next_code_options |= SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET;
360+
}
361+
362+
if (reload_requested) {
363+
next_code_stickiness_situation |= SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_RELOAD;
364+
}
365+
else if (result.return_code == 0) {
366+
next_code_stickiness_situation |= SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_SUCCESS;
367+
if (next_code_options & SUPERVISOR_NEXT_CODE_OPT_RELOAD_ON_SUCCESS) {
368+
skip_repl = true;
369+
skip_wait = true;
370+
}
371+
}
372+
else {
373+
next_code_stickiness_situation |= SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_ERROR;
374+
// Deep sleep cannot be skipped
375+
// TODO: settings in deep sleep should persist, using a new sleep memory API
376+
if (next_code_options & SUPERVISOR_NEXT_CODE_OPT_RELOAD_ON_ERROR
377+
&& !(result.return_code & PYEXEC_DEEP_SLEEP)) {
378+
skip_repl = true;
379+
skip_wait = true;
380+
}
381+
}
332382
if (result.return_code & PYEXEC_FORCED_EXIT) {
333-
return reload_requested;
383+
skip_repl = reload_requested;
384+
skip_wait = true;
334385
}
335386

336387
if (reload_requested && result.return_code == PYEXEC_EXCEPTION) {
@@ -378,12 +429,17 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
378429
#if CIRCUITPY_ALARM
379430
bool fake_sleeping = false;
380431
#endif
381-
bool skip_repl = false;
382-
while (true) {
432+
while (!skip_wait) {
383433
RUN_BACKGROUND_TASKS;
384434

385435
// If a reload was requested by the supervisor or autoreload, return
386436
if (reload_requested) {
437+
next_code_stickiness_situation |= SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_RELOAD;
438+
// Should the STICKY_ON_SUCCESS and STICKY_ON_ERROR bits be cleared in
439+
// next_code_stickiness_situation? I can see arguments either way, but I'm deciding
440+
// "no" for now, mainly because it's a bit less code. At this point, we have both a
441+
// success or error and a reload, so let's have both of the respective options take
442+
// effect (in OR combination).
387443
reload_requested = false;
388444
skip_repl = true;
389445
break;
@@ -518,6 +574,13 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
518574
port_idle_until_interrupt();
519575
}
520576
}
577+
578+
// free code allocation if unused
579+
if ((next_code_options & next_code_stickiness_situation) == 0) {
580+
free_memory(next_code_allocation);
581+
next_code_allocation = NULL;
582+
}
583+
521584
// Done waiting, start the board back up.
522585
#if CIRCUITPY_STATUS_LED
523586
if (led_active) {
@@ -531,6 +594,7 @@ STATIC bool run_code_py(safe_mode_t safe_mode) {
531594
board_init();
532595
}
533596
#endif
597+
534598
return skip_repl;
535599
}
536600

shared-bindings/supervisor/__init__.c

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@
2323
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2424
* THE SOFTWARE.
2525
*/
26+
#include <string.h>
27+
2628
#include "py/obj.h"
2729
#include "py/runtime.h"
2830
#include "py/reload.h"
31+
#include "py/objstr.h"
2932

3033
#include "lib/utils/interrupt_char.h"
3134
#include "supervisor/shared/autoreload.h"
@@ -112,6 +115,98 @@ STATIC mp_obj_t supervisor_set_next_stack_limit(mp_obj_t size_obj) {
112115
}
113116
MP_DEFINE_CONST_FUN_OBJ_1(supervisor_set_next_stack_limit_obj, supervisor_set_next_stack_limit);
114117

118+
//| def set_next_code_file(filename: Optional[str], *, reload_on_success : bool = False, reload_on_error: bool = False, sticky_on_success: bool = False, sticky_on_error: bool = False, sticky_on_reload: bool = False) -> None:
119+
//| """Set what file to run on the next vm run.
120+
//|
121+
//| When not ``None``, the given ``filename`` is inserted at the front of the usual ['code.py',
122+
//| 'main.py'] search sequence.
123+
//|
124+
//| The optional keyword arguments specify what happens after the specified file has run:
125+
//|
126+
//| ``sticky_on_…`` determine whether the newly set filename and options stay in effect: If
127+
//| True, further runs will continue to run that file (unless it says otherwise by calling
128+
//| ``set_next_code_filename()`` itself). If False, the settings will only affect one run and
129+
//| revert to the standard code.py/main.py afterwards.
130+
//|
131+
//| ``reload_on_…`` determine how to continue: If False, wait in the usual "Code done running.
132+
//| Waiting for reload. / Press any key to enter the REPL. Use CTRL-D to reload." state. If
133+
//| True, reload immediately as if CTRL-D was pressed.
134+
//|
135+
//| ``…_on_success`` take effect when the program runs to completion or calls ``sys.exit()``.
136+
//|
137+
//| ``…_on_error`` take effect when the program exits with an exception, including the
138+
//| KeyboardInterrupt caused by CTRL-C.
139+
//|
140+
//| ``…_on_reload`` take effect when the program is interrupted by files being written to the USB
141+
//| drive (auto-reload) or when it calls ``supervisor.reload()``.
142+
//|
143+
//| These settings are stored in RAM, not in persistent memory, and will therefore only affect
144+
//| soft reloads. Powering off or resetting the device will always revert to standard settings.
145+
//|
146+
//| When called multiple times in the same run, only the last call takes effect, replacing any
147+
//| settings made by previous ones. This is the main use of passing ``None`` as a filename: to
148+
//| reset to the standard search sequence."""
149+
//| ...
150+
//|
151+
STATIC mp_obj_t supervisor_set_next_code_file(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
152+
static const mp_arg_t allowed_args[] = {
153+
{ MP_QSTR_filename, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} },
154+
{ MP_QSTR_reload_on_success, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
155+
{ MP_QSTR_reload_on_error, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
156+
{ MP_QSTR_sticky_on_success, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
157+
{ MP_QSTR_sticky_on_error, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
158+
{ MP_QSTR_sticky_on_reload, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
159+
};
160+
struct {
161+
mp_arg_val_t filename;
162+
mp_arg_val_t reload_on_success;
163+
mp_arg_val_t reload_on_error;
164+
mp_arg_val_t sticky_on_success;
165+
mp_arg_val_t sticky_on_error;
166+
mp_arg_val_t sticky_on_reload;
167+
} args;
168+
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, (mp_arg_val_t *)&args);
169+
if (!mp_obj_is_str_or_bytes(args.filename.u_obj) && args.filename.u_obj != mp_const_none) {
170+
mp_raise_TypeError(translate("argument has wrong type"));
171+
}
172+
if (args.filename.u_obj == mp_const_none) {
173+
args.filename.u_obj = mp_const_empty_bytes;
174+
}
175+
uint8_t options = 0;
176+
if (args.reload_on_success.u_bool) {
177+
options |= SUPERVISOR_NEXT_CODE_OPT_RELOAD_ON_SUCCESS;
178+
}
179+
if (args.reload_on_error.u_bool) {
180+
options |= SUPERVISOR_NEXT_CODE_OPT_RELOAD_ON_ERROR;
181+
}
182+
if (args.sticky_on_success.u_bool) {
183+
options |= SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_SUCCESS;
184+
}
185+
if (args.sticky_on_error.u_bool) {
186+
options |= SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_ERROR;
187+
}
188+
if (args.sticky_on_reload.u_bool) {
189+
options |= SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_RELOAD;
190+
}
191+
size_t len;
192+
const char *filename = mp_obj_str_get_data(args.filename.u_obj, &len);
193+
free_memory(next_code_allocation);
194+
if (options != 0 || len != 0) {
195+
next_code_allocation = allocate_memory(align32_size(sizeof(next_code_info_t) + len + 1), false, true);
196+
if (next_code_allocation == NULL) {
197+
m_malloc_fail(sizeof(next_code_info_t) + len + 1);
198+
}
199+
next_code_info_t *next_code = (next_code_info_t *)next_code_allocation->ptr;
200+
next_code->options = options | SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET;
201+
memcpy(&next_code->filename, filename, len);
202+
next_code->filename[len] = '\0';
203+
} else {
204+
next_code_allocation = NULL;
205+
}
206+
return mp_const_none;
207+
}
208+
MP_DEFINE_CONST_FUN_OBJ_KW(supervisor_set_next_code_file_obj, 0, supervisor_set_next_code_file);
209+
115210
STATIC const mp_rom_map_elem_t supervisor_module_globals_table[] = {
116211
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_supervisor) },
117212
{ MP_ROM_QSTR(MP_QSTR_enable_autoreload), MP_ROM_PTR(&supervisor_enable_autoreload_obj) },
@@ -121,7 +216,7 @@ STATIC const mp_rom_map_elem_t supervisor_module_globals_table[] = {
121216
{ MP_ROM_QSTR(MP_QSTR_reload), MP_ROM_PTR(&supervisor_reload_obj) },
122217
{ MP_ROM_QSTR(MP_QSTR_RunReason), MP_ROM_PTR(&supervisor_run_reason_type) },
123218
{ MP_ROM_QSTR(MP_QSTR_set_next_stack_limit), MP_ROM_PTR(&supervisor_set_next_stack_limit_obj) },
124-
219+
{ MP_ROM_QSTR(MP_QSTR_set_next_code_file), MP_ROM_PTR(&supervisor_set_next_code_file_obj) },
125220
};
126221

127222
STATIC MP_DEFINE_CONST_DICT(supervisor_module_globals, supervisor_module_globals_table);

supervisor/shared/autoreload.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "py/reload.h"
3131
#include "supervisor/shared/tick.h"
3232

33+
supervisor_allocation *next_code_allocation;
3334
#include "shared-bindings/supervisor/Runtime.h"
3435

3536
static volatile uint32_t autoreload_delay_ms = 0;

supervisor/shared/autoreload.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,24 @@
2929

3030
#include <stdbool.h>
3131

32+
#include "supervisor/memory.h"
33+
34+
enum {
35+
SUPERVISOR_NEXT_CODE_OPT_RELOAD_ON_SUCCESS = 0x1,
36+
SUPERVISOR_NEXT_CODE_OPT_RELOAD_ON_ERROR = 0x2,
37+
SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_SUCCESS = 0x4,
38+
SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_ERROR = 0x8,
39+
SUPERVISOR_NEXT_CODE_OPT_STICKY_ON_RELOAD = 0x10,
40+
SUPERVISOR_NEXT_CODE_OPT_NEWLY_SET = 0x20,
41+
};
42+
43+
typedef struct {
44+
uint8_t options;
45+
char filename[];
46+
} next_code_info_t;
47+
48+
extern supervisor_allocation *next_code_allocation;
49+
3250
extern volatile bool reload_requested;
3351

3452
void autoreload_tick(void);

supervisor/shared/memory.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ enum {
3636
CIRCUITPY_SUPERVISOR_IMMOVABLE_ALLOC_COUNT =
3737
// stack + heap
3838
2
39+
// next_code_allocation
40+
+ 1
3941

4042
#if INTERNAL_FLASH_FILESYSTEM == 0
4143
+ 1

0 commit comments

Comments
 (0)