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

pybricks / pybricks-micropython / 9659196825

25 Jun 2024 08:35AM UTC coverage: 56.592% (+10.5%) from 46.065%
9659196825

push

github

laurensvalk
pybricks.hubs: Add update_heading_correction method to imu.

3760 of 6644 relevant lines covered (56.59%)

20292085.46 hits per line

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

52.27
/pybricks/tools/pb_module_tools.c
1
// SPDX-License-Identifier: MIT
2
// Copyright (c) 2018-2023 The Pybricks Authors
3

4
#include "py/mpconfig.h"
5

6
#if PYBRICKS_PY_TOOLS
7

8
#include "py/builtin.h"
9
#include "py/gc.h"
10
#include "py/mphal.h"
11
#include "py/objmodule.h"
12
#include "py/runtime.h"
13
#include "py/stream.h"
14

15
#include <pbio/int_math.h>
16
#include <pbio/task.h>
17
#include <pbsys/light.h>
18
#include <pbsys/program_stop.h>
19
#include <pbsys/status.h>
20

21
#include <pybricks/parameters.h>
22
#include <pybricks/common.h>
23
#include <pybricks/tools.h>
24
#include <pybricks/tools/pb_type_matrix.h>
25

26
#include <pybricks/util_mp/pb_kwarg_helper.h>
27
#include <pybricks/util_mp/pb_obj_helper.h>
28
#include <pybricks/util_pb/pb_error.h>
29

30

31
// Global state of the run loop for async user programs. Gets set when run_task
32
// is called and cleared when it completes
33
STATIC bool run_loop_is_active;
34

35
bool pb_module_tools_run_loop_is_active(void) {
293✔
36
    return run_loop_is_active;
293✔
37
}
38

39
void pb_module_tools_assert_blocking(void) {
14✔
40
    if (run_loop_is_active) {
14✔
41
        mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("This can only be called before multitasking starts."));
×
42
    }
43
}
14✔
44

45
// The awaitables for the wait() function have no object associated with
46
// it (unlike e.g. a motor), so we make a starting point here. These never
47
// have to cancel each other so shouldn't need to be in a list, but this lets
48
// us share the same code with other awaitables. It also minimizes allocation.
49
MP_REGISTER_ROOT_POINTER(mp_obj_t wait_awaitables);
50

51
STATIC bool pb_module_tools_wait_test_completion(mp_obj_t obj, uint32_t end_time) {
402✔
52
    return mp_hal_ticks_ms() - end_time < UINT32_MAX / 2;
402✔
53
}
54

55
STATIC mp_obj_t pb_module_tools_wait(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
240✔
56
    PB_PARSE_ARGS_FUNCTION(n_args, pos_args, kw_args,
240✔
57
        PB_ARG_REQUIRED(time));
240✔
58

59
    mp_int_t time = pb_obj_get_int(time_in);
240✔
60

61
    // outside run loop, do blocking wait. This would be handled below as well,
62
    // but for this very simple call we'd rather avoid the overhead.
63
    if (!pb_module_tools_run_loop_is_active()) {
240✔
64
        if (time > 0) {
238✔
65
            mp_hal_delay_ms(time);
238✔
66
        }
67
        return mp_const_none;
238✔
68
    }
69

70
    // Require that duration is nonnegative small int. This makes it cheaper to
71
    // test completion state in iteration loop.
72
    time = pbio_int_math_bind(time, 0, INT32_MAX >> 2);
2✔
73

74
    return pb_type_awaitable_await_or_wait(
4✔
75
        NULL, // wait functions are not associated with an object
76
        MP_STATE_PORT(wait_awaitables),
77
        mp_hal_ticks_ms() + (time < 0 ? 0 : time),
2✔
78
        pb_module_tools_wait_test_completion,
79
        pb_type_awaitable_return_none,
80
        pb_type_awaitable_cancel_none,
81
        PB_TYPE_AWAITABLE_OPT_NONE);
82
}
83
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pb_module_tools_wait_obj, 0, pb_module_tools_wait);
84

85
/**
86
 * Waits for a task to complete.
87
 *
88
 * If an exception is raised while waiting, then the task is canceled.
89
 *
90
 * @param [in]  task    The task
91
 * @param [in]  timeout The timeout in milliseconds or -1 to wait forever.
92
 */
93
void pb_module_tools_pbio_task_do_blocking(pbio_task_t *task, mp_int_t timeout) {
×
94

95
    pb_module_tools_assert_blocking();
×
96

97
    nlr_buf_t nlr;
×
98

99
    if (nlr_push(&nlr) == 0) {
×
100
        mp_uint_t start = mp_hal_ticks_ms();
×
101

102
        while (timeout < 0 || mp_hal_ticks_ms() - start < (mp_uint_t)timeout) {
×
103
            MICROPY_EVENT_POLL_HOOK
×
104

105
            if (task->status != PBIO_ERROR_AGAIN) {
×
106
                nlr_pop();
×
107
                pb_assert(task->status);
×
108
                return;
×
109
            }
110
        }
111

112
        mp_raise_OSError(MP_ETIMEDOUT);
×
113
        MP_UNREACHABLE
114
    } else {
115
        pbio_task_cancel(task);
×
116

117
        while (task->status == PBIO_ERROR_AGAIN) {
×
118
            MICROPY_VM_HOOK_LOOP
×
119

120
            // Stop waiting (and potentially blocking) in case of forced shutdown.
121
            if (pbsys_status_test(PBIO_PYBRICKS_STATUS_SHUTDOWN_REQUEST)) {
×
122
                break;
123
            }
124
        }
125

126
        nlr_jump(nlr.ret_val);
×
127
    }
128
}
129

130
// The awaitables associated with pbio tasks can originate from different
131
// objects. At the moment, they are only associated with Bluetooth tasks, and
132
// they cannot run at the same time. So we keep a single list of awaitables
133
// here instead of with each Bluetooth-related MicroPython object.
134
MP_REGISTER_ROOT_POINTER(mp_obj_t pbio_task_awaitables);
135

136
STATIC bool pb_module_tools_pbio_task_test_completion(mp_obj_t obj, uint32_t end_time) {
×
137
    pbio_task_t *task = MP_OBJ_TO_PTR(obj);
×
138

139
    // Keep going if not done yet.
140
    if (task->status == PBIO_ERROR_AGAIN) {
×
141
        return false;
142
    }
143

144
    // If done, make sure it was successful.
145
    pb_assert(task->status);
×
146
    return true;
×
147
}
148

149
mp_obj_t pb_module_tools_pbio_task_wait_or_await(pbio_task_t *task) {
×
150
    return pb_type_awaitable_await_or_wait(
×
151
        MP_OBJ_FROM_PTR(task),
152
        MP_STATE_PORT(pbio_task_awaitables),
153
        pb_type_awaitable_end_time_none,
154
        pb_module_tools_pbio_task_test_completion,
155
        pb_type_awaitable_return_none,
156
        pb_type_awaitable_cancel_none,
157
        PB_TYPE_AWAITABLE_OPT_RAISE_ON_BUSY);
158
}
159

160
/**
161
 * Reads one byte from stdin without blocking if a byte is available, and
162
 * optionally converts it to character representation.
163
 *
164
 * @param [in]  last    Choose @c True to read until the last byte is read
165
 *                      or @c False to get the first available byte.
166
 * @param [in]  chr     Choose @c False to return the integer value of the byte.
167
 *                      Choose @c True to return a single character string of
168
 *                      the resulting byte if it is printable and otherwise
169
 *                      return @c None .
170
 *
171
 * @returns The resulting byte if there was one, converted as above, otherwise @c None .
172
 *
173
 */
174
STATIC mp_obj_t pb_module_tools_read_input_byte(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
×
175
    PB_PARSE_ARGS_FUNCTION(n_args, pos_args, kw_args,
×
176
        PB_ARG_DEFAULT_FALSE(last),
177
        PB_ARG_DEFAULT_FALSE(chr));
×
178

179
    int chr = -1;
×
180

181
    while ((mp_hal_stdio_poll(MP_STREAM_POLL_RD) & MP_STREAM_POLL_RD)) {
×
182
        // REVISIT: In theory, this should not block if mp_hal_stdio_poll() and
183
        // mp_hal_stdin_rx_chr() are implemented correctly and nothing happens
184
        // in a thread/interrupt/kernel that changes the state.
185
        chr = mp_hal_stdin_rx_chr();
×
186

187
        // For last=False, break to stop at first byte. Otherwise, keep reading.
188
        if (!mp_obj_is_true(last_in)) {
×
189
            break;
190
        }
191
    }
192

193
    // If no data is available, return None.
194
    if (chr < 0) {
×
195
        return mp_const_none;
196
    }
197

198
    // If chr=False, return the integer value of the byte.
199
    if (!mp_obj_is_true(chr_in)) {
×
200
        return MP_OBJ_NEW_SMALL_INT(chr);
×
201
    }
202

203
    // If char requested but not printable, return None.
204
    if (chr < 32 || chr > 126) {
×
205
        return mp_const_none;
206
    }
207

208
    // Return the character as a string.
209
    const char result[] = {chr};
×
210
    return mp_obj_new_str(result, sizeof(result));
×
211
}
212
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pb_module_tools_read_input_byte_obj, 0, pb_module_tools_read_input_byte);
213

214
STATIC mp_obj_t pb_module_tools_run_task(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
2✔
215
    PB_PARSE_ARGS_FUNCTION(n_args, pos_args, kw_args,
2✔
216
        PB_ARG_DEFAULT_NONE(task),
217
        PB_ARG_DEFAULT_INT(loop_time, 10));
2✔
218

219
    // Without args, this function is used to test if the run loop is active.
220
    if (n_args == 0) {
2✔
221
        return mp_obj_new_bool(run_loop_is_active);
×
222
    }
223

224
    // Can only run one loop at a time.
225
    if (run_loop_is_active) {
2✔
226
        mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Run loop already active."));
×
227
    }
228

229
    run_loop_is_active = true;
2✔
230

231
    uint32_t start_time = mp_hal_ticks_ms();
2✔
232
    uint32_t loop_time = pb_obj_get_positive_int(loop_time_in);
2✔
233

234
    mp_obj_iter_buf_t iter_buf;
2✔
235
    mp_obj_t iterable = mp_getiter(task_in, &iter_buf);
2✔
236

237
    nlr_buf_t nlr;
2✔
238
    if (nlr_push(&nlr) == 0) {
2✔
239

240
        while (mp_iternext(iterable) != MP_OBJ_STOP_ITERATION) {
1,059✔
241

242
            gc_collect();
1,057✔
243

244
            if (loop_time == 0) {
1,057✔
245
                continue;
5✔
246
            }
247

248
            uint32_t elapsed = mp_hal_ticks_ms() - start_time;
1,052✔
249
            if (elapsed < loop_time) {
1,052✔
250
                mp_hal_delay_ms(loop_time - elapsed);
1,052✔
251
            }
252
            start_time += loop_time;
1,052✔
253
        }
254

255
        nlr_pop();
2✔
256
        run_loop_is_active = false;
2✔
257
    } else {
258
        run_loop_is_active = false;
×
259
        nlr_jump(nlr.ret_val);
×
260
    }
261
    return mp_const_none;
2✔
262
}
263
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pb_module_tools_run_task_obj, 0, pb_module_tools_run_task);
264

265
// Reset global awaitable state when user program starts.
266
void pb_module_tools_init(void) {
23✔
267
    MP_STATE_PORT(wait_awaitables) = mp_obj_new_list(0, NULL);
23✔
268
    MP_STATE_PORT(pbio_task_awaitables) = mp_obj_new_list(0, NULL);
23✔
269
    run_loop_is_active = false;
23✔
270
}
23✔
271

272
#if PYBRICKS_PY_TOOLS_HUB_MENU
273

274
STATIC void pb_module_tools_hub_menu_display_symbol(mp_obj_t symbol) {
275
    if (mp_obj_is_str(symbol)) {
276
        pb_type_LightMatrix_display_char(pbsys_hub_light_matrix, symbol);
277
    } else {
278
        pb_type_LightMatrix_display_number(pbsys_hub_light_matrix, symbol);
279
    }
280
}
281

282
/**
283
 * Waits for a button press or release.
284
 *
285
 * @param [in]  press   Choose @c true to wait for press or @c false to wait for release.
286
 * @returns             When waiting for pressed, it returns the button that was pressed, otherwise returns 0.
287
 */
288
STATIC pbio_button_flags_t pb_module_tools_hub_menu_wait_for_press(bool press) {
289

290
    // This function should only be used in a blocking context.
291
    pb_module_tools_assert_blocking();
292

293
    pbio_error_t err;
294
    pbio_button_flags_t btn;
295
    while ((err = pbio_button_is_pressed(&btn)) == PBIO_SUCCESS && ((bool)btn) == !press) {
296
        MICROPY_EVENT_POLL_HOOK;
297
    }
298
    pb_assert(err);
299
    return btn;
300
}
301

302
/**
303
 * Displays a menu on the hub display and allows the user to pick a symbol
304
 * using the buttons.
305
 *
306
 * @param [in]  n_args  The number of args.
307
 * @param [in]  args    The args passed in Python code (the menu entries).
308
 */
309
STATIC mp_obj_t pb_module_tools_hub_menu(size_t n_args, const mp_obj_t *args) {
310

311
    // Validate arguments by displaying all of them, ending with the first.
312
    // This ensures we fail right away instead of midway through the menu. It
313
    // happens so fast that there isn't a time penalty for this.
314
    for (int i = n_args - 1; i >= 0; i--) {
315
        pb_module_tools_hub_menu_display_symbol(args[i]);
316
    }
317

318
    // Disable stop button and cache original setting to restore later.
319
    pbio_button_flags_t stop_button = pbsys_program_stop_get_buttons();
320
    pbsys_program_stop_set_buttons(0);
321

322
    nlr_buf_t nlr;
323
    if (nlr_push(&nlr) == 0) {
324

325
        size_t selection = 0;
326

327
        while (true) {
328
            pb_module_tools_hub_menu_wait_for_press(false);
329
            pbio_button_flags_t btn = pb_module_tools_hub_menu_wait_for_press(true);
330

331
            // Selection made, exit.
332
            if (btn & PBIO_BUTTON_CENTER) {
333
                break;
334
            }
335

336
            // Increment/decrement selection for left/right buttons.
337
            if (btn & PBIO_BUTTON_RIGHT) {
338
                selection = (selection + 1) % n_args;
339
            } else if (btn & PBIO_BUTTON_LEFT) {
340
                selection = selection == 0 ? n_args - 1 : selection - 1;
341
            }
342

343
            // Display current selection.
344
            pb_module_tools_hub_menu_display_symbol(args[selection]);
345
        }
346

347
        // Wait for release before returning, just like starting a normal program.
348
        pb_module_tools_hub_menu_wait_for_press(false);
349

350
        // Restore stop button setting prior to starting menu.
351
        pbsys_program_stop_set_buttons(stop_button);
352

353
        // Complete and return selected object.
354
        nlr_pop();
355
        return args[selection];
356
    } else {
357
        pbsys_program_stop_set_buttons(stop_button);
358
        nlr_jump(nlr.ret_val);
359
        return mp_const_none;
360
    }
361
}
362
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR(pb_module_tools_hub_menu_obj, 2, pb_module_tools_hub_menu);
363

364
#endif // PYBRICKS_PY_TOOLS_HUB_MENU
365

366
STATIC const mp_rom_map_elem_t tools_globals_table[] = {
367
    { MP_ROM_QSTR(MP_QSTR___name__),    MP_ROM_QSTR(MP_QSTR_tools)                    },
368
    { MP_ROM_QSTR(MP_QSTR_wait),        MP_ROM_PTR(&pb_module_tools_wait_obj)         },
369
    { MP_ROM_QSTR(MP_QSTR_read_input_byte), MP_ROM_PTR(&pb_module_tools_read_input_byte_obj) },
370
    #if PYBRICKS_PY_TOOLS_HUB_MENU
371
    { MP_ROM_QSTR(MP_QSTR_hub_menu),    MP_ROM_PTR(&pb_module_tools_hub_menu_obj)     },
372
    #endif // PYBRICKS_PY_TOOLS_HUB_MENU
373
    { MP_ROM_QSTR(MP_QSTR_run_task),    MP_ROM_PTR(&pb_module_tools_run_task_obj)     },
374
    { MP_ROM_QSTR(MP_QSTR_StopWatch),   MP_ROM_PTR(&pb_type_StopWatch)                },
375
    { MP_ROM_QSTR(MP_QSTR_multitask),   MP_ROM_PTR(&pb_type_Task)                     },
376
    #if MICROPY_PY_BUILTINS_FLOAT
377
    { MP_ROM_QSTR(MP_QSTR_Matrix),      MP_ROM_PTR(&pb_type_Matrix)           },
378
    { MP_ROM_QSTR(MP_QSTR_vector),      MP_ROM_PTR(&pb_geometry_vector_obj)   },
379
    { MP_ROM_QSTR(MP_QSTR_cross),       MP_ROM_PTR(&pb_type_matrix_cross_obj) },
380
    // backwards compatibility for pybricks.geometry.Axis
381
    { MP_ROM_QSTR(MP_QSTR_Axis),        MP_ROM_PTR(&pb_enum_type_Axis) },
382
    #endif // MICROPY_PY_BUILTINS_FLOAT
383
};
384
STATIC MP_DEFINE_CONST_DICT(pb_module_tools_globals, tools_globals_table);
385

386
const mp_obj_module_t pb_module_tools = {
387
    .base = { &mp_type_module },
388
    .globals = (mp_obj_dict_t *)&pb_module_tools_globals,
389
};
390

391
#if PYBRICKS_RUNS_ON_EV3DEV
392
// ev3dev extends the C module in Python
393
MP_REGISTER_MODULE(MP_QSTR__tools, pb_module_tools);
394
#else
395
MP_REGISTER_MODULE(MP_QSTR_pybricks_dot_tools, pb_module_tools);
396
#endif
397

398
// backwards compatibility for pybricks.geometry
399
#if MICROPY_PY_BUILTINS_FLOAT
400
MP_REGISTER_MODULE(MP_QSTR_pybricks_dot_geometry, pb_module_tools);
401
#endif // MICROPY_PY_BUILTINS_FLOAT
402

403
#endif // PYBRICKS_PY_TOOLS
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