• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pybricks / pybricks-micropython / 19031242574

03 Nov 2025 10:08AM UTC coverage: 57.143% (-0.02%) from 57.167%
19031242574

push

github

laurensvalk
micropython: Update to 1.26.

This also drops most of the previous patches concerning the unix/ev3dev variants.

2 of 3 new or added lines in 1 file covered. (66.67%)

94 existing lines in 4 files now uncovered.

4476 of 7833 relevant lines covered (57.14%)

17182600.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

44.51
/bricks/_common/micropython.c
1
// SPDX-License-Identifier: MIT
2
// Copyright (c) 2018-2023 The Pybricks Authors
3

4
// This file provides a MicroPython runtime to run code in MULTI_MPY_V6 format.
5

6
#include <stdint.h>
7
#include <stdio.h>
8
#include <string.h>
9

10
#include <pbdrv/stack.h>
11

12
#include <pbio/button.h>
13
#include <pbio/main.h>
14
#include <pbio/os.h>
15
#include <pbio/util.h>
16
#include <pbio/protocol.h>
17
#include <pbsys/main.h>
18
#include <pbsys/program_stop.h>
19
#include <pbsys/storage.h>
20

21
#include <pybricks/common.h>
22
#include <pybricks/util_mp/pb_obj_helper.h>
23

24
#include "genhdr/mpversion.h"
25
#include "shared/readline/readline.h"
26
#include "shared/runtime/gchelper.h"
27
#include "shared/runtime/interrupt_char.h"
28
#include "shared/runtime/pyexec.h"
29
#include "py/builtin.h"
30
#include "py/compile.h"
31
#include "py/frozenmod.h"
32
#include "py/gc.h"
33
#include "py/mperrno.h"
34
#include "py/mphal.h"
35
#include "py/objmodule.h"
36
#include "py/persistentcode.h"
37
#include "py/reader.h"
38
#include "py/repl.h"
39
#include "py/runtime.h"
40
#include "py/smallint.h"
41
#include "py/stackctrl.h"
42
#include "py/stream.h"
43

44
// callback for when stop button is pressed in IDE or on hub
UNCOV
45
void pbsys_main_stop_program(bool force_stop) {
×
UNCOV
46
    if (force_stop) {
×
UNCOV
47
        mp_sched_vm_abort();
×
48
    } else {
49
        static mp_obj_exception_t system_exit;
UNCOV
50
        system_exit.base.type = &mp_type_SystemExit;
×
UNCOV
51
        system_exit.traceback_alloc = system_exit.traceback_len = 0;
×
UNCOV
52
        system_exit.traceback_data = NULL;
×
UNCOV
53
        system_exit.args = (mp_obj_tuple_t *)&mp_const_empty_tuple_obj;
×
54

UNCOV
55
        mp_sched_exception(MP_OBJ_FROM_PTR(&system_exit));
×
56
    }
57
}
×
58

UNCOV
59
bool pbsys_main_stdin_event(uint8_t c) {
×
UNCOV
60
    if (c == mp_interrupt_char) {
×
61
        mp_sched_keyboard_interrupt();
×
62
        return true;
×
63
    }
64

UNCOV
65
    return false;
×
66
}
67

68
// Prints the exception that ended the program.
69
static void print_final_exception(mp_obj_t exc, int ret) {
2✔
70

71
    // Ensure exception prints on new line.
72
    pb_stdout_flush_to_new_line();
2✔
73

74
    nlr_buf_t nlr;
75
    nlr.ret_val = NULL;
2✔
76

77
    if (nlr_push(&nlr) == 0) {
2✔
78
        // Handle graceful stop with button.
79
        if ((ret & PYEXEC_FORCED_EXIT) &&
2✔
UNCOV
80
            mp_obj_exception_match(exc, MP_OBJ_FROM_PTR(&mp_type_SystemExit))) {
×
UNCOV
81
            mp_printf(&mp_plat_print, "The program was stopped (%q).\n",
×
UNCOV
82
                ((mp_obj_exception_t *)MP_OBJ_TO_PTR(exc))->base.type->name);
×
UNCOV
83
            return;
×
84
        }
85

86
        // REVISIT: flash the light red a few times to indicate an unhandled exception?
87

88
        // Print unhandled exception with traceback.
89
        mp_obj_print_exception(&mp_plat_print, exc);
2✔
90

91
        nlr_pop();
2✔
92
    } else {
93
        // If we couldn't print the exception, just return. There is nothing
94
        // else we can do.
95

96
        // REVISIT: flash the light with a different pattern here?
97
    }
98
}
99

100
#if PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_REPL
UNCOV
101
static void run_repl(void) {
×
UNCOV
102
    int ret = 0;
×
103

UNCOV
104
    readline_init0();
×
105

106
    nlr_buf_t nlr;
UNCOV
107
    nlr.ret_val = NULL;
×
108

UNCOV
109
    if (nlr_push(&nlr) == 0) {
×
UNCOV
110
        nlr_set_abort(&nlr);
×
111
        // No need to set interrupt_char here, it is done by pyexec.
112
        #if PYBRICKS_OPT_RAW_REPL
113
        if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) {
114
            // Compatibility with mpremote.
115
            mp_printf(&mp_plat_print, "MPY: soft reboot\n");
116
            ret = pyexec_raw_repl();
117
        } else {
118
            ret = pyexec_friendly_repl();
119
        }
120
        #else // PYBRICKS_OPT_RAW_REPL
121
        ret = pyexec_friendly_repl();
×
122
        #endif // PYBRICKS_OPT_RAW_REPL
UNCOV
123
        nlr_pop();
×
124
    } else {
125
        // if vm abort
UNCOV
126
        if (nlr.ret_val == NULL) {
×
127
            // we are shutting down, so don't bother with cleanup
UNCOV
128
            return;
×
129
        }
130

131
        // clear any pending exceptions (and run any callbacks).
132
        mp_handle_pending(false);
×
133
        // Print which exception triggered this.
134
        print_final_exception(MP_OBJ_FROM_PTR(nlr.ret_val), ret);
×
135
    }
136

137
    nlr_set_abort(NULL);
×
138
}
139
#endif // PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_REPL
140

141

142
/** mpy info and data for one script or module. */
143
typedef struct {
144
    /** Size of the mpy program. Not aligned, use pbio_get_uint32_le() to read. */
145
    uint8_t mpy_size[4];
146
    /** Null-terminated name of the script, without file extension. */
147
    char mpy_name[];
148
    /** mpy data follows thereafter. */
149
} mpy_info_t;
150

151
// Program data is a concatenation of multiple mpy files. This sets a reference
152
// to the first script and the total size so we can search for modules.
153
static mpy_info_t *mpy_first;
154
static mpy_info_t *mpy_end;
155
static inline void mpy_data_init(pbsys_main_program_t *program) {
26✔
156
    mpy_first = (mpy_info_t *)program->code_start;
26✔
157
    mpy_end = (mpy_info_t *)program->code_end;
26✔
158
}
26✔
159

160
/**
161
 * Gets a reference to the mpy data of a script.
162
 * @param [in]  info    A pointer to an mpy info header.
163
 * @return              A pointer to the .mpy file.
164
 */
165
static uint8_t *mpy_data_get_buf(mpy_info_t *info) {
53✔
166
    // The header consists of the size and a zero-terminated module name string.
167
    return (uint8_t *)info + sizeof(info->mpy_size) + strlen(info->mpy_name) + 1;
53✔
168
}
169

170
/**
171
 * Finds a MicroPython module in the program data.
172
 * @param [in]  name    The fully qualified name of the module.
173
 * @return              A pointer to the .mpy file in user RAM or NULL if the
174
 *                      module was not found.
175
 */
UNCOV
176
static mpy_info_t *mpy_data_find(qstr name) {
×
UNCOV
177
    const char *name_str = qstr_str(name);
×
178

UNCOV
179
    for (mpy_info_t *info = mpy_first; (uintptr_t)info + sizeof(uint32_t) < (uintptr_t)mpy_end;
×
UNCOV
180
         info = (mpy_info_t *)(mpy_data_get_buf(info) + pbio_get_uint32_le(info->mpy_size))) {
×
UNCOV
181
        if (strcmp(info->mpy_name, name_str) == 0) {
×
UNCOV
182
            return info;
×
183
        }
184
    }
185

UNCOV
186
    return NULL;
×
187
}
188

189
// From micropython/py/builtinimport.c, but copied because it is static.
190
static void do_execute_proto_fun(const mp_module_context_t *context, mp_proto_fun_t proto_fun) {
24✔
191

192
    // execute the module in its context
193
    mp_obj_dict_t *mod_globals = context->module.globals;
24✔
194

195
    // save context
196
    nlr_jump_callback_node_globals_locals_t ctx;
197
    ctx.globals = mp_globals_get();
24✔
198
    ctx.locals = mp_locals_get();
24✔
199

200
    // set new context
201
    mp_globals_set(mod_globals);
24✔
202
    mp_locals_set(mod_globals);
24✔
203

204
    // set exception handler to restore context if an exception is raised
205
    nlr_push_jump_callback(&ctx.callback, mp_globals_locals_set_from_nlr_jump_callback);
24✔
206

207
    // make and execute the function
208
    mp_obj_t module_fun = mp_make_function_from_proto_fun(proto_fun, context, NULL);
24✔
209
    mp_call_function_0(module_fun);
24✔
210

211
    // deregister exception handler and restore context
212
    nlr_pop_jump_callback(true);
24✔
213
}
24✔
214

215
static void execute_rom_mpy_in_context(mp_module_context_t *module_context, mpy_info_t *mpy_info) {
26✔
216
    // Prepare to execute in its own context.
217
    mp_compiled_module_t compiled_module;
218
    compiled_module.context = module_context;
26✔
219

220
    // Execute the MPY file in the new module context.
221
    mp_reader_t reader;
222
    mp_reader_new_mem(&reader, mpy_data_get_buf(mpy_info), pbio_get_uint32_le(mpy_info->mpy_size), MP_READER_IS_ROM);
26✔
223
    mp_raw_code_load(&reader, &compiled_module);
26✔
224
    do_execute_proto_fun(compiled_module.context, compiled_module.rc);
24✔
225
}
24✔
226

227
/**
228
 * Runs the __main__ module from user RAM.
229
 */
230
static void run_user_program(void) {
26✔
231
    int ret = 0;
26✔
232

233
    nlr_buf_t nlr;
234
    nlr.ret_val = NULL;
26✔
235

236
    if (nlr_push(&nlr) == 0) {
26✔
237
        nlr_set_abort(&nlr);
26✔
238

239
        // Run the script while letting CTRL-C interrupt it.
240
        mp_hal_set_interrupt_char(CHAR_CTRL_C);
26✔
241

242
        // Main program runs in __main__ module which is already initialized.
243
        mp_module_context_t main_context = {
26✔
244
            .module = mp_module___main__
245
        };
246
        execute_rom_mpy_in_context(&main_context, mpy_first);
26✔
247

248
        mp_hal_set_interrupt_char(-1);
24✔
249

250
        // Handle any pending exceptions (and any callbacks)
251
        mp_handle_pending(true);
24✔
252

253
        nlr_pop();
24✔
254
    } else {
255
        mp_hal_set_interrupt_char(-1);
2✔
256

257
        // if vm abort
258
        if (nlr.ret_val == NULL) {
2✔
259
            // we are shutting down, so don't bother with cleanup
UNCOV
260
            return;
×
261
        }
262

263
        // Clear any pending exceptions (and run any callbacks).
264
        mp_handle_pending(false);
2✔
265

266
        if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t *)nlr.ret_val)->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) {
2✔
267
            // at the moment, the value of SystemExit is unused
UNCOV
268
            ret = PYEXEC_FORCED_EXIT;
×
269
        }
270

271
        print_final_exception(MP_OBJ_FROM_PTR(nlr.ret_val), ret);
2✔
272

273
        #if PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_REPL
274
        // On KeyboardInterrupt, drop to REPL for debugging.
275
        if (mp_obj_exception_match(MP_OBJ_FROM_PTR(nlr.ret_val), MP_OBJ_FROM_PTR(&mp_type_KeyboardInterrupt))) {
2✔
276

277
            // The global scope is preserved to facilitate debugging, but we
278
            // stop active resources like motors and sounds. They are stopped
279
            // but not reset so the user can restart them in the REPL.
UNCOV
280
            pbio_main_soft_stop();
×
281

282
            // Enter REPL.
UNCOV
283
            run_repl();
×
284
        }
285
        #endif // PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_REPL
286
    }
287

288
    nlr_set_abort(NULL);
26✔
289
}
290

291
pbio_error_t pbsys_main_program_validate(pbsys_main_program_t *program) {
27✔
292

293
    // For builtin programs, check requested ID against feature flags.
294
    #if PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_REPL
295
    if (program->id == PBIO_PYBRICKS_USER_PROGRAM_ID_REPL) {
27✔
UNCOV
296
        return PBIO_SUCCESS;
×
297
    }
298
    #endif
299
    #if PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_PORT_VIEW
300
    if (program->id == PBIO_PYBRICKS_USER_PROGRAM_ID_PORT_VIEW) {
27✔
UNCOV
301
        return PBIO_SUCCESS;
×
302
    }
303
    #endif
304
    #if PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_IMU_CALIBRATION
305
    if (program->id == PBIO_PYBRICKS_USER_PROGRAM_ID_IMU_CALIBRATION) {
306
        return PBIO_SUCCESS;
307
    }
308
    #endif
309

310
    // If requesting a user program, ensure that it exists and is valid.
311
    uint32_t program_size = program->code_end - program->code_start;
27✔
312
    if (program_size == 0 || program_size > pbsys_storage_get_maximum_program_size()) {
27✔
UNCOV
313
        return PBIO_ERROR_NOT_SUPPORTED;
×
314
    }
315

316
    // Application-specific name may be used in system UI.
317
    mpy_info_t *mpy_info = (mpy_info_t *)program->code_start;
27✔
318
    program->name = mpy_info->mpy_name;
27✔
319

320
    // This is the same test done when loading the mpy. Do it early so we don't
321
    // start MicroPython and so the system knows this is valid. Revisit: Consider
322
    // making this part of the public API upstream.
323
    uint8_t *header = mpy_data_get_buf(mpy_info);
27✔
324
    uint8_t arch = MPY_FEATURE_DECODE_ARCH(header[2]);
27✔
325
    if (header[0] != 'M'
27✔
326
        || header[1] != MPY_VERSION
27✔
327
        || (arch != MP_NATIVE_ARCH_NONE && MPY_FEATURE_DECODE_SUB_VERSION(header[2]) != MPY_SUB_VERSION)
27✔
328
        || header[3] > MP_SMALL_INT_BITS) {
26✔
329
        return PBIO_ERROR_INVALID_ARG;
1✔
330
    }
331

332
    return PBIO_SUCCESS;
26✔
333
}
334

335
const char *pbsys_main_get_application_version_hash(void) {
27✔
336
    // This is (somewhat confusingly) passed in as the MICROPY_GIT_HASH.
337
    // REVISIT: Make PYBRICKS_GIT_HASH available in a pbio header via a build step.
338
    return MICROPY_GIT_HASH;
27✔
339
}
340

341
// Runs MicroPython with the given program data.
342
void pbsys_main_run_program(pbsys_main_program_t *program) {
26✔
343

344
    #if PBDRV_CONFIG_STACK_EMBEDDED
345
    // Stack limit should be less than real stack size, so we have a chance
346
    // to recover from limit hit.  (Limit is measured in bytes.)
347
    char *stack_start;
348
    char *stack_end;
349
    pbdrv_stack_get_info(&stack_start, &stack_end);
350
    #if PYBRICKS_OPT_USE_STACK_END_AS_TOP
351
    mp_stack_set_top(stack_end);
352
    #else
353
    // Sets the top to current stack pointer.
354
    mp_stack_ctrl_init();
355
    #endif
356
    mp_stack_set_limit(MP_STATE_THREAD(stack_top) - stack_start - 1024);
357
    #else
358
    mp_cstack_init_with_sp_here(1024 * 1024);
26✔
359
    #endif
360

361
    // MicroPython heap is the free RAM after program data.
362
    gc_init(program->user_ram_start, program->user_ram_end);
26✔
363

364
    // Set program data reference to first script. This is used to run main,
365
    // and to set the starting point for finding downloaded modules.
366
    mpy_data_init(program);
26✔
367

368
    // Initialize MicroPython.
369
    mp_init();
26✔
370

371
    // Runs the requested downloaded or builtin user program.
372
    switch (program->id) {
26✔
373

374
        #if PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_REPL
UNCOV
375
        case PBIO_PYBRICKS_USER_PROGRAM_ID_REPL:
×
376
            // Run REPL with everything auto-imported.
UNCOV
377
            pb_package_pybricks_init(true);
×
UNCOV
378
            run_repl();
×
UNCOV
379
            break;
×
380
        #endif
381

382
        #if PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_PORT_VIEW && MICROPY_MODULE_FROZEN
UNCOV
383
        case PBIO_PYBRICKS_USER_PROGRAM_ID_PORT_VIEW:
×
UNCOV
384
            pb_package_pybricks_init(false);
×
UNCOV
385
            pyexec_frozen_module("_builtin_port_view.py", false);
×
386
            break;
×
387
        #endif
388

389
        #if PBSYS_CONFIG_FEATURE_BUILTIN_USER_PROGRAM_IMU_CALIBRATION
390
        case PBIO_PYBRICKS_USER_PROGRAM_ID_IMU_CALIBRATION:
391
            // Todo
392
            break;
393
        #endif
394

395
        default:
26✔
396
            // Init Pybricks package without auto-import.
397
            pb_package_pybricks_init(false);
26✔
398
            // Run loaded user program (just slot 0 for now).
399
            run_user_program();
26✔
400
            break;
26✔
401
    }
402

403
    // Ensure everything is written before the user application is considered
404
    // done, so that the host does not receive stdout after receiving stop.
405
    pb_stdout_flush_to_new_line();
26✔
406
}
26✔
407

408
void pbsys_main_run_program_cleanup(void) {
26✔
409
    gc_sweep_all();
26✔
410
    mp_deinit();
26✔
411
}
26✔
412

UNCOV
413
void gc_collect(void) {
×
UNCOV
414
    gc_collect_start();
×
UNCOV
415
    gc_helper_collect_regs_and_stack();
×
UNCOV
416
    gc_collect_end();
×
UNCOV
417
}
×
418

UNCOV
419
mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) {
×
UNCOV
420
    mp_raise_OSError(MP_ENOENT);
×
421
}
422
MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open);
423

424
// Overrides MicroPython's mp_builtin___import__
425
// IMPORTANT: this needs to be kept in sync with mp_builtin___import___default().
426
mp_obj_t pb_builtin_import(size_t n_args, const mp_obj_t *args) {
×
427
    // Check that it's not a relative import
428
    if (n_args >= 5 && MP_OBJ_SMALL_INT_VALUE(args[4]) != 0) {
×
UNCOV
429
        mp_raise_NotImplementedError(MP_ERROR_TEXT("relative import"));
×
430
    }
431

432
    // Check if the module is already loaded.
UNCOV
433
    mp_map_elem_t *elem = mp_map_lookup(&MP_STATE_VM(mp_loaded_modules_dict).map, args[0], MP_MAP_LOOKUP);
×
UNCOV
434
    if (elem) {
×
UNCOV
435
        return elem->value;
×
436
    }
437

438
    // Try the name directly as a non-extensible built-in (e.g. `micropython`).
439
    qstr module_name_qstr = mp_obj_str_get_qstr(args[0]);
×
440
    mp_obj_t module_obj = mp_module_get_builtin(module_name_qstr, false);
×
UNCOV
441
    if (module_obj != MP_OBJ_NULL) {
×
UNCOV
442
        return module_obj;
×
443
    }
444

445
    // Now try as an extensible built-in (e.g. `struct`/`ustruct`).
446
    module_obj = mp_module_get_builtin(module_name_qstr, true);
×
UNCOV
447
    if (module_obj != MP_OBJ_NULL) {
×
UNCOV
448
        return module_obj;
×
449
    }
450

451
    // Check for presence of user program in user RAM.
452
    mpy_info_t *info = mpy_data_find(module_name_qstr);
×
453

454
    // If a downloaded module was found but not yet loaded, load it.
UNCOV
455
    if (info) {
×
456
        // Create new module.
457
        mp_module_context_t *module_context = mp_obj_new_module(module_name_qstr);
×
458

459
        // Execute the module in that context.
UNCOV
460
        execute_rom_mpy_in_context(module_context, info);
×
461

462
        // Return the newly imported module.
463
        return MP_OBJ_FROM_PTR(module_context);
×
464
    }
465

466
    // Allow importing of frozen modules if any were included in the firmware.
467
    #if MICROPY_MODULE_FROZEN_MPY
468
    void *modref;
469
    int frozen_type;
UNCOV
470
    const char *ext = ".py";
×
471
    char module_path[(1 << (8 * MICROPY_QSTR_BYTES_IN_LEN)) + sizeof(ext)] = { 0 };
×
UNCOV
472
    strcpy(module_path, mp_obj_str_get_str(args[0]));
×
UNCOV
473
    strcpy(module_path + qstr_len(module_name_qstr), ext);
×
474
    if (mp_find_frozen_module(module_path, &frozen_type, &modref) == MP_IMPORT_STAT_FILE && frozen_type == MP_FROZEN_MPY) {
×
475
        // Create new module to be returned.
UNCOV
476
        mp_module_context_t *module_context = mp_obj_new_module(module_name_qstr);
×
UNCOV
477
        mp_obj_t module_obj = MP_OBJ_FROM_PTR(module_context);
×
478

479
        // Execute frozen code in the new module context.
UNCOV
480
        const mp_frozen_module_t *frozen = modref;
×
481
        module_context->constants = frozen->constants;
×
482
        do_execute_proto_fun(module_context, frozen->proto_fun);
×
483
        return module_obj;
×
484
    }
485
    #endif
486

487
    // Nothing found, raise ImportError.
488
    mp_raise_msg_varg(&mp_type_ImportError, MP_ERROR_TEXT("no module named '%q'"), module_name_qstr);
×
489
}
490

491
mp_import_stat_t mp_import_stat(const char *path) {
×
492
    return MP_IMPORT_STAT_NO_EXIST;
×
493
}
494

UNCOV
495
mp_lexer_t *mp_lexer_new_from_file(qstr filename) {
×
UNCOV
496
    mp_raise_OSError(MP_ENOENT);
×
497
}
498

499
void nlr_jump_fail(void *val) {
×
UNCOV
500
    while (1) {
×
501
        ;
502
    }
503
}
504

UNCOV
505
void NORETURN __fatal_error(const char *msg) {
×
506
    while (1) {
×
507
        ;
508
    }
509
}
510

511
#ifndef NDEBUG
UNCOV
512
void MP_WEAK __assert_func(const char *file, int line, const char *func, const char *expr) {
×
UNCOV
513
    printf("Assertion '%s' failed, at file %s:%d\n", expr, file, line);
×
UNCOV
514
    __fatal_error("Assertion failed");
×
515
}
516
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc