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

pybricks / pybricks-micropython / 6675885095

28 Oct 2023 08:38AM UTC coverage: 56.053% (+10.0%) from 46.074%
6675885095

push

github

laurensvalk
pybricks.hubs.MoveHub: Use standard hub init.

The Move Hub cannot have true vector axes, but we can still use this API to initialize the hub using numeric indices.

This ensures we can use the custom orientation not just in tilt, but also in acceleration like we do on other hubs.

3616 of 6451 relevant lines covered (56.05%)

20895680.75 hits per line

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

58.67
/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

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

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

29

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

34
bool pb_module_tools_run_loop_is_active(void) {
237✔
35
    return run_loop_is_active;
237✔
36
}
37

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

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

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

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

58
    mp_int_t time = pb_obj_get_int(time_in);
207✔
59

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

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

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

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

94
    pb_module_tools_assert_blocking();
×
95

96
    nlr_buf_t nlr;
×
97

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

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

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

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

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

120
        nlr_jump(nlr.ret_val);
×
121
    }
122
}
123

124
// The awaitables associated with pbio tasks can originate from different
125
// objects. At the moment, they are only associated with Bluetooth tasks, and
126
// they cannot run at the same time. So we keep a single list of awaitables
127
// here instead of with each Bluetooth-related MicroPython object.
128
MP_REGISTER_ROOT_POINTER(mp_obj_t pbio_task_awaitables);
129

130
STATIC bool pb_module_tools_pbio_task_test_completion(mp_obj_t obj, uint32_t end_time) {
×
131
    pbio_task_t *task = MP_OBJ_TO_PTR(obj);
×
132

133
    // Keep going if not done yet.
134
    if (task->status == PBIO_ERROR_AGAIN) {
×
135
        pbio_task_run_once(task);
×
136
    }
137
    if (task->status == PBIO_ERROR_AGAIN) {
×
138
        return false;
139
    }
140

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

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

157
/**
158
 * Reads one byte from stdin without blocking.
159
 *
160
 * @returns The integer value of the byte read or @c None if no data is available.
161
 */
162
STATIC mp_obj_t pb_module_tools_read_input_byte(void) {
×
163
    if (!(mp_hal_stdio_poll(MP_STREAM_POLL_RD) & MP_STREAM_POLL_RD)) {
×
164
        // No bytes available.
165
        return mp_const_none;
166
    }
167

168
    // REVISIT: In theory, this should not block if mp_hal_stdio_poll() and
169
    // mp_hal_stdin_rx_chr() are implemented correctly and nothing happens
170
    // in a thread/interrupt/kernel that changes the state.
171
    return MP_OBJ_NEW_SMALL_INT(mp_hal_stdin_rx_chr());
×
172
}
173
STATIC MP_DEFINE_CONST_FUN_OBJ_0(pb_module_tools_read_input_byte_obj, pb_module_tools_read_input_byte);
174

175
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✔
176
    PB_PARSE_ARGS_FUNCTION(n_args, pos_args, kw_args,
2✔
177
        PB_ARG_REQUIRED(task),
178
        PB_ARG_DEFAULT_INT(loop_time, 10));
2✔
179

180
    run_loop_is_active = true;
2✔
181

182
    uint32_t start_time = mp_hal_ticks_ms();
2✔
183
    uint32_t loop_time = pb_obj_get_positive_int(loop_time_in);
2✔
184

185
    mp_obj_iter_buf_t iter_buf;
2✔
186
    mp_obj_t iterable = mp_getiter(task_in, &iter_buf);
2✔
187

188
    nlr_buf_t nlr;
2✔
189
    if (nlr_push(&nlr) == 0) {
2✔
190

191
        while (mp_iternext(iterable) != MP_OBJ_STOP_ITERATION) {
1,051✔
192

193
            gc_collect();
1,049✔
194

195
            if (loop_time == 0) {
1,049✔
196
                continue;
5✔
197
            }
198

199
            uint32_t elapsed = mp_hal_ticks_ms() - start_time;
1,044✔
200
            if (elapsed < loop_time) {
1,044✔
201
                mp_hal_delay_ms(loop_time - elapsed);
1,044✔
202
            }
203
            start_time += loop_time;
1,044✔
204
        }
205

206
        nlr_pop();
2✔
207
        run_loop_is_active = false;
2✔
208
    } else {
209
        run_loop_is_active = false;
×
210
        nlr_jump(nlr.ret_val);
×
211
    }
212
    return mp_const_none;
2✔
213
}
214
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pb_module_tools_run_task_obj, 1, pb_module_tools_run_task);
215

216
// Reset global awaitable state when user program starts.
217
void pb_module_tools_init(void) {
21✔
218
    MP_STATE_PORT(wait_awaitables) = mp_obj_new_list(0, NULL);
21✔
219
    MP_STATE_PORT(pbio_task_awaitables) = mp_obj_new_list(0, NULL);
21✔
220
    run_loop_is_active = false;
21✔
221
}
21✔
222

223
#if PYBRICKS_PY_TOOLS_HUB_MENU
224

225
STATIC void pb_module_tools_hub_menu_display_symbol(mp_obj_t symbol) {
226
    if (mp_obj_is_str(symbol)) {
227
        pb_type_LightMatrix_display_char(pbsys_hub_light_matrix, symbol);
228
    } else {
229
        pb_type_LightMatrix_display_number(pbsys_hub_light_matrix, symbol);
230
    }
231
}
232

233
/**
234
 * Waits for a button press or release.
235
 *
236
 * @param [in]  press   Choose @c true to wait for press or @c false to wait for release.
237
 * @returns             When waiting for pressed, it returns the button that was pressed, otherwise returns 0.
238
 */
239
STATIC pbio_button_flags_t pb_module_tools_hub_menu_wait_for_press(bool press) {
240

241
    // This function should only be used in a blocking context.
242
    pb_module_tools_assert_blocking();
243

244
    pbio_error_t err;
245
    pbio_button_flags_t btn;
246
    while ((err = pbio_button_is_pressed(&btn)) == PBIO_SUCCESS && ((bool)btn) == !press) {
247
        MICROPY_EVENT_POLL_HOOK;
248
    }
249
    pb_assert(err);
250
    return btn;
251
}
252

253
/**
254
 * Displays a menu on the hub display and allows the user to pick a symbol
255
 * using the buttons.
256
 *
257
 * @param [in]  n_args  The number of args.
258
 * @param [in]  args    The args passed in Python code (the menu entries).
259
 */
260
STATIC mp_obj_t pb_module_tools_hub_menu(size_t n_args, const mp_obj_t *args) {
261

262
    // Validate arguments by displaying all of them, ending with the first.
263
    // This ensures we fail right away instead of midway through the menu. It
264
    // happens so fast that there isn't a time penalty for this.
265
    for (int i = n_args - 1; i >= 0; i--) {
266
        pb_module_tools_hub_menu_display_symbol(args[i]);
267
    }
268

269
    // Disable stop button and cache original setting to restore later.
270
    pbio_button_flags_t stop_button = pbsys_program_stop_get_buttons();
271
    pbsys_program_stop_set_buttons(0);
272

273
    nlr_buf_t nlr;
274
    if (nlr_push(&nlr) == 0) {
275

276
        size_t selection = 0;
277

278
        while (true) {
279
            pb_module_tools_hub_menu_wait_for_press(false);
280
            pbio_button_flags_t btn = pb_module_tools_hub_menu_wait_for_press(true);
281

282
            // Selection made, exit.
283
            if (btn & PBIO_BUTTON_CENTER) {
284
                break;
285
            }
286

287
            // Increment/decrement selection for left/right buttons.
288
            if (btn & PBIO_BUTTON_RIGHT) {
289
                selection = (selection + 1) % n_args;
290
            } else if (btn & PBIO_BUTTON_LEFT) {
291
                selection = selection == 0 ? n_args - 1 : selection - 1;
292
            }
293

294
            // Display current selection.
295
            pb_module_tools_hub_menu_display_symbol(args[selection]);
296
        }
297

298
        // Wait for release before returning, just like starting a normal program.
299
        pb_module_tools_hub_menu_wait_for_press(false);
300

301
        // Restore stop button setting prior to starting menu.
302
        pbsys_program_stop_set_buttons(stop_button);
303

304
        // Complete and return selected object.
305
        nlr_pop();
306
        return args[selection];
307
    } else {
308
        pbsys_program_stop_set_buttons(stop_button);
309
        nlr_jump(nlr.ret_val);
310
        return mp_const_none;
311
    }
312
}
313
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR(pb_module_tools_hub_menu_obj, 2, pb_module_tools_hub_menu);
314

315
#endif // PYBRICKS_PY_TOOLS_HUB_MENU
316

317
STATIC const mp_rom_map_elem_t tools_globals_table[] = {
318
    { MP_ROM_QSTR(MP_QSTR___name__),    MP_ROM_QSTR(MP_QSTR_tools)                    },
319
    { MP_ROM_QSTR(MP_QSTR_wait),        MP_ROM_PTR(&pb_module_tools_wait_obj)         },
320
    { MP_ROM_QSTR(MP_QSTR_read_input_byte), MP_ROM_PTR(&pb_module_tools_read_input_byte_obj) },
321
    #if PYBRICKS_PY_TOOLS_HUB_MENU
322
    { MP_ROM_QSTR(MP_QSTR_hub_menu),    MP_ROM_PTR(&pb_module_tools_hub_menu_obj)     },
323
    #endif // PYBRICKS_PY_TOOLS_HUB_MENU
324
    { MP_ROM_QSTR(MP_QSTR_run_task),    MP_ROM_PTR(&pb_module_tools_run_task_obj)     },
325
    { MP_ROM_QSTR(MP_QSTR_StopWatch),   MP_ROM_PTR(&pb_type_StopWatch)                },
326
    { MP_ROM_QSTR(MP_QSTR_multitask),   MP_ROM_PTR(&pb_type_Task)                     },
327
    #if MICROPY_PY_BUILTINS_FLOAT
328
    { MP_ROM_QSTR(MP_QSTR_Matrix),      MP_ROM_PTR(&pb_type_Matrix)           },
329
    { MP_ROM_QSTR(MP_QSTR_vector),      MP_ROM_PTR(&pb_geometry_vector_obj)   },
330
    { MP_ROM_QSTR(MP_QSTR_cross),       MP_ROM_PTR(&pb_type_matrix_cross_obj) },
331
    // backwards compatibility for pybricks.geometry.Axis
332
    { MP_ROM_QSTR(MP_QSTR_Axis),        MP_ROM_PTR(&pb_enum_type_Axis) },
333
    #endif // MICROPY_PY_BUILTINS_FLOAT
334
};
335
STATIC MP_DEFINE_CONST_DICT(pb_module_tools_globals, tools_globals_table);
336

337
const mp_obj_module_t pb_module_tools = {
338
    .base = { &mp_type_module },
339
    .globals = (mp_obj_dict_t *)&pb_module_tools_globals,
340
};
341

342
#if PYBRICKS_RUNS_ON_EV3DEV
343
// ev3dev extends the C module in Python
344
MP_REGISTER_MODULE(MP_QSTR__tools, pb_module_tools);
345
#else
346
MP_REGISTER_MODULE(MP_QSTR_pybricks_dot_tools, pb_module_tools);
347
#endif
348

349
// backwards compatibility for pybricks.geometry
350
#if MICROPY_PY_BUILTINS_FLOAT
351
MP_REGISTER_MODULE(MP_QSTR_pybricks_dot_geometry, pb_module_tools);
352
#endif // MICROPY_PY_BUILTINS_FLOAT
353

354
#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