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

pybricks / pybricks-micropython / 19016791193

02 Nov 2025 06:40PM UTC coverage: 57.167% (-2.6%) from 59.744%
19016791193

Pull #406

github

laurensvalk
bricks/virtualhub: Replace with embedded simulation.

Instead of using the newly introduced simhub alongside the virtualhub, we'll just replace the old one entirely now that it has reached feature parity. We can keep calling it the virtualhub.
Pull Request #406: New virtual hub for more effective debugging

41 of 48 new or added lines in 7 files covered. (85.42%)

414 existing lines in 53 files now uncovered.

4479 of 7835 relevant lines covered (57.17%)

17178392.75 hits per line

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

11.82
/pybricks/common/pb_type_control.c
1
// SPDX-License-Identifier: MIT
2
// Copyright (c) 2018-2023 The Pybricks Authors
3

4
#include <pbio/control.h>
5

6
#include "py/mpconfig.h"
7
#include "py/obj.h"
8

9
#include <pybricks/common.h>
10

11
#include <pybricks/util_pb/pb_error.h>
12
#include <pybricks/util_mp/pb_obj_helper.h>
13
#include <pybricks/util_mp/pb_kwarg_helper.h>
14

15
mp_obj_t make_acceleration_return_value(int32_t acceleration, int32_t deceleration) {
×
16
    // For backwards compatibility, return a single integer if acceleration
17
    // and deceleration are equal.
18
    if (acceleration == deceleration) {
×
19
        return mp_obj_new_int(acceleration);
×
20
    }
21
    // Otherwise return a tuple with both values.
22
    mp_obj_t accel[] = {
×
23
        mp_obj_new_int(acceleration),
×
24
        mp_obj_new_int(deceleration),
×
25
    };
26
    return mp_obj_new_tuple(MP_ARRAY_SIZE(accel), accel);
×
27
}
28

29
void unpack_acceleration_value(mp_obj_t accel_in, int32_t *acceleration, int32_t *deceleration) {
2✔
30
    // If no acceleration was given, leave values unchanged.
31
    if (accel_in == mp_const_none) {
2✔
32
        return;
2✔
33
    }
34

35
    // If single value is given for acceleration, use it for deceleration too.
36
    if (!pb_obj_is_array(accel_in)) {
2✔
37
        *acceleration = pb_obj_get_int(accel_in);
2✔
38
        *deceleration = *acceleration;
2✔
39
        return;
2✔
40
    }
41

42
    // Otherwise attempt to unpack acceleration and deceleration from tuple,
43
    // raising if something invalid was given.
44
    mp_obj_t *values;
45
    size_t n;
46
    mp_obj_get_array(accel_in, &n, &values);
×
47
    if (n != 2) {
×
48
        pb_assert(PBIO_ERROR_INVALID_ARG);
×
49
    }
50
    *acceleration = pb_obj_get_int(values[0]);
×
51
    *deceleration = pb_obj_get_int(values[1]);
×
52
}
53

54
#if PYBRICKS_PY_COMMON_CONTROL
55

56
// pybricks._common.Control class object structure
57
typedef struct _pb_type_Control_obj_t {
58
    mp_obj_base_t base;
59
    pbio_control_t *control;
60
    mp_obj_t scale;
61
    #if PYBRICKS_PY_COMMON_LOGGER
62
    mp_obj_t logger;
63
    #endif
64
} pb_type_Control_obj_t;
65

66
// pybricks._common.Control.__init__/__new__
67
mp_obj_t pb_type_Control_obj_make_new(pbio_control_t *control) {
23✔
68

69
    pb_type_Control_obj_t *self = mp_obj_malloc(pb_type_Control_obj_t, &pb_type_Control);
23✔
70

71
    self->control = control;
23✔
72

73
    #if PYBRICKS_PY_COMMON_LOGGER
74
    // Create an instance of the Logger class
75
    self->logger = common_Logger_obj_make_new(&self->control->log, PBIO_CONTROL_LOGGER_NUM_COLS);
23✔
76
    #endif
77

78
    self->scale = mp_obj_new_int(control->settings.ctl_steps_per_app_step);
23✔
79

80
    return MP_OBJ_FROM_PTR(self);
23✔
81
}
82

83
// pybricks._common.Control.limits
UNCOV
84
static mp_obj_t pb_type_Control_limits(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
×
85

UNCOV
86
    PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
×
87
        pb_type_Control_obj_t, self,
88
        PB_ARG_DEFAULT_NONE(speed),
89
        PB_ARG_DEFAULT_NONE(acceleration),
90
        PB_ARG_DEFAULT_NONE(torque));
91

92
    // Read current values.
93
    int32_t speed, acceleration, deceleration, torque;
UNCOV
94
    pbio_control_settings_get_trajectory_limits(&self->control->settings, &speed, &acceleration, &deceleration);
×
UNCOV
95
    torque = pbio_control_settings_get_actuation_limit(&self->control->settings);
×
96

97
    // If all given values are none, return current values
UNCOV
98
    if (PB_PARSE_ARGS_METHOD_ALL_NONE()) {
×
99
        mp_obj_t ret[] = {
×
100
            mp_obj_new_int(speed),
×
101
            make_acceleration_return_value(acceleration, deceleration),
×
102
            mp_obj_new_int(torque),
×
103
        };
104
        return mp_obj_new_tuple(MP_ARRAY_SIZE(ret), ret);
×
105
    }
106

107
    // Set user settings if given, else keep using current values.
UNCOV
108
    speed = pb_obj_get_default_abs_int(speed_in, speed);
×
UNCOV
109
    torque = pb_obj_get_default_abs_int(torque_in, torque);
×
UNCOV
110
    unpack_acceleration_value(acceleration_in, &acceleration, &deceleration);
×
111

112
    // Set new values.
UNCOV
113
    pb_assert(pbio_control_settings_set_trajectory_limits(&self->control->settings, speed, acceleration, deceleration));
×
UNCOV
114
    pb_assert(pbio_control_settings_set_actuation_limit(&self->control->settings, torque));
×
115

UNCOV
116
    return mp_const_none;
×
117
}
118
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Control_limits_obj, 1, pb_type_Control_limits);
119

120
// pybricks._common.Control.pid
121
static mp_obj_t pb_type_Control_pid(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
×
122

123
    PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
×
124
        pb_type_Control_obj_t, self,
125
        PB_ARG_DEFAULT_NONE(kp),
126
        PB_ARG_DEFAULT_NONE(ki),
127
        PB_ARG_DEFAULT_NONE(kd),
128
        PB_ARG_DEFAULT_NONE(integral_deadzone),
129
        PB_ARG_DEFAULT_NONE(integral_rate));
130

131
    // Read current values
132
    int32_t kp, ki, kd;
133
    int32_t integral_change_max, integral_deadzone;
134
    pbio_control_settings_get_pid(&self->control->settings, &kp, &ki, &kd, &integral_deadzone, &integral_change_max);
×
135

136
    // If all given values are none, return current values
137
    if (PB_PARSE_ARGS_METHOD_ALL_NONE()) {
×
138
        mp_obj_t ret[5];
139
        ret[0] = mp_obj_new_int(kp);
×
140
        ret[1] = mp_obj_new_int(ki);
×
141
        ret[2] = mp_obj_new_int(kd);
×
142
        ret[3] = mp_obj_new_int(integral_deadzone);
×
143
        ret[4] = mp_obj_new_int(integral_change_max);
×
144
        return mp_obj_new_tuple(5, ret);
×
145
    }
146

147
    // Set user settings
148
    kp = pb_obj_get_default_abs_int(kp_in, kp);
×
149
    ki = pb_obj_get_default_abs_int(ki_in, ki);
×
150
    kd = pb_obj_get_default_abs_int(kd_in, kd);
×
151
    integral_change_max = pb_obj_get_default_abs_int(integral_rate_in, integral_change_max);
×
152
    integral_deadzone = pb_obj_get_default_abs_int(integral_deadzone_in, integral_deadzone);
×
153

154
    pb_assert(pbio_control_settings_set_pid(&self->control->settings, kp, ki, kd, integral_deadzone, integral_change_max));
×
155

156
    return mp_const_none;
×
157
}
158
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Control_pid_obj, 1, pb_type_Control_pid);
159

160
// pybricks._common.Control.target_tolerances
161
static mp_obj_t pb_type_Control_target_tolerances(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
×
162

163
    PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
×
164
        pb_type_Control_obj_t, self,
165
        PB_ARG_DEFAULT_NONE(speed),
166
        PB_ARG_DEFAULT_NONE(position));
167

168
    // Read current values
169
    int32_t speed, position;
170
    pbio_control_settings_get_target_tolerances(&self->control->settings, &speed, &position);
×
171

172
    // If all given values are none, return current values
173
    if (PB_PARSE_ARGS_METHOD_ALL_NONE()) {
×
174
        mp_obj_t ret[2];
175
        ret[0] = mp_obj_new_int(speed);
×
176
        ret[1] = mp_obj_new_int(position);
×
177
        return mp_obj_new_tuple(2, ret);
×
178
    }
179

180
    // Set user settings
181
    speed = pb_obj_get_default_abs_int(speed_in, speed);
×
182
    position = pb_obj_get_default_abs_int(position_in, position);
×
183

184
    pb_assert(pbio_control_settings_set_target_tolerances(&self->control->settings, speed, position));
×
185

186
    return mp_const_none;
×
187
}
188
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Control_target_tolerances_obj, 1, pb_type_Control_target_tolerances);
189

190
// pybricks._common.Control.stall_tolerances
191
static mp_obj_t pb_type_Control_stall_tolerances(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
×
192

193
    PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
×
194
        pb_type_Control_obj_t, self,
195
        PB_ARG_DEFAULT_NONE(speed),
196
        PB_ARG_DEFAULT_NONE(time));
197

198
    // Read current values
199
    int32_t speed;
200
    uint32_t time;
201
    pbio_control_settings_get_stall_tolerances(&self->control->settings, &speed, &time);
×
202

203
    // If all given values are none, return current values
204
    if (PB_PARSE_ARGS_METHOD_ALL_NONE()) {
×
205
        mp_obj_t ret[2];
206
        ret[0] = mp_obj_new_int(speed);
×
207
        ret[1] = mp_obj_new_int_from_uint(time);
×
208
        return mp_obj_new_tuple(2, ret);
×
209
    }
210

211
    // Set user settings
212
    speed = pb_obj_get_default_abs_int(speed_in, speed);
×
213
    time = pb_obj_get_default_abs_int(time_in, time);
×
214

215
    pb_assert(pbio_control_settings_set_stall_tolerances(&self->control->settings, speed, time));
×
216

217
    return mp_const_none;
×
218
}
219
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_Control_stall_tolerances_obj, 1, pb_type_Control_stall_tolerances);
220

221
// pybricks._common.Control.trajectory
222
static mp_obj_t pb_type_Control_trajectory(mp_obj_t self_in) {
×
223
    pb_type_Control_obj_t *self = MP_OBJ_TO_PTR(self_in);
×
224
    pbio_trajectory_t *trj = &self->control->trajectory;
×
225

226
    mp_obj_t parms[13];
227

228
    if (pbio_control_is_active(self->control)) {
×
229
        parms[0] = mp_obj_new_int(0);
×
230
        parms[1] = mp_obj_new_int(trj->t1 / 10);
×
231
        parms[2] = mp_obj_new_int(trj->t2 / 10);
×
232
        parms[3] = mp_obj_new_int(trj->t3 / 10);
×
233
        parms[4] = mp_obj_new_int(0);
×
234
        parms[5] = mp_obj_new_int(trj->th1 / 1000);
×
235
        parms[6] = mp_obj_new_int(trj->th2 / 1000);
×
236
        parms[7] = mp_obj_new_int(trj->th3 / 1000);
×
237
        parms[8] = mp_obj_new_int(trj->w0 / 10);
×
238
        parms[9] = mp_obj_new_int(trj->w1 / 10);
×
239
        parms[10] = mp_obj_new_int(trj->w3 / 10);
×
240
        parms[11] = mp_obj_new_int(trj->a0);
×
241
        parms[12] = mp_obj_new_int(trj->a2);
×
242
        return mp_obj_new_tuple(13, parms);
×
243
    }
UNCOV
244
    return mp_const_none;
×
245
}
246
MP_DEFINE_CONST_FUN_OBJ_1(pb_type_Control_trajectory_obj, pb_type_Control_trajectory);
247

248
// DELETEME: This method should not be used in V3.2 or later. It may be removed
249
// in a future version
250
// pybricks._common.Control.done
251
static mp_obj_t pb_type_Control_done(mp_obj_t self_in) {
×
252
    pb_type_Control_obj_t *self = MP_OBJ_TO_PTR(self_in);
×
253
    return mp_obj_new_bool(pbio_control_is_done(self->control));
×
254
}
255
MP_DEFINE_CONST_FUN_OBJ_1(pb_type_Control_done_obj, pb_type_Control_done);
256

257
// DELETEME: This method should not be used in V3.2 or later. It may be removed
258
// in a future version
259
// pybricks._common.Control.load
260
static mp_obj_t pb_type_Control_load(mp_obj_t self_in) {
×
261
    pb_type_Control_obj_t *self = MP_OBJ_TO_PTR(self_in);
×
262

263
    if (!pbio_control_is_active(self->control)) {
×
264
        return mp_obj_new_int(0);
×
265
    }
266

267
    // Read currently applied PID feedback torque and return as mNm.
268
    return mp_obj_new_int(self->control->pid_average / 1000);
×
269
}
270
MP_DEFINE_CONST_FUN_OBJ_1(pb_type_Control_load_obj, pb_type_Control_load);
271

272
// DELETEME: This method should not be used in V3.2 or later. It may be removed
273
// in a future version
274
// pybricks._common.Control.stalled
275
static mp_obj_t pb_type_Control_stalled(mp_obj_t self_in) {
×
276
    pb_type_Control_obj_t *self = MP_OBJ_TO_PTR(self_in);
×
277
    uint32_t stall_duration;
278
    return mp_obj_new_bool(pbio_control_is_stalled(self->control, &stall_duration));
×
279
}
280
MP_DEFINE_CONST_FUN_OBJ_1(pb_type_Control_stalled_obj, pb_type_Control_stalled);
281

282
static const pb_attr_dict_entry_t pb_type_Control_attr_dict[] = {
283
    PB_DEFINE_CONST_ATTR_RO(MP_QSTR_scale, pb_type_Control_obj_t, scale),
284
    #if PYBRICKS_PY_COMMON_LOGGER
285
    PB_DEFINE_CONST_ATTR_RO(MP_QSTR_log, pb_type_Control_obj_t, logger),
286
    #endif // PYBRICKS_PY_COMMON_LOGGER
287
    PB_ATTR_DICT_SENTINEL
288
};
289

290
// dir(pybricks.common.Control)
291
static const mp_rom_map_elem_t pb_type_Control_locals_dict_table[] = {
292
    { MP_ROM_QSTR(MP_QSTR_limits), MP_ROM_PTR(&pb_type_Control_limits_obj) },
293
    { MP_ROM_QSTR(MP_QSTR_pid), MP_ROM_PTR(&pb_type_Control_pid_obj) },
294
    { MP_ROM_QSTR(MP_QSTR_target_tolerances), MP_ROM_PTR(&pb_type_Control_target_tolerances_obj) },
295
    { MP_ROM_QSTR(MP_QSTR_stall_tolerances), MP_ROM_PTR(&pb_type_Control_stall_tolerances_obj) },
296
    { MP_ROM_QSTR(MP_QSTR_trajectory), MP_ROM_PTR(&pb_type_Control_trajectory_obj) },
297
    { MP_ROM_QSTR(MP_QSTR_done), MP_ROM_PTR(&pb_type_Control_done_obj) },
298
    { MP_ROM_QSTR(MP_QSTR_load), MP_ROM_PTR(&pb_type_Control_load_obj) },
299
    { MP_ROM_QSTR(MP_QSTR_stalled), MP_ROM_PTR(&pb_type_Control_stalled_obj) },
300
};
301
static MP_DEFINE_CONST_DICT(pb_type_Control_locals_dict, pb_type_Control_locals_dict_table);
302

303
// type(pybricks.common.Control)
304
MP_DEFINE_CONST_OBJ_TYPE(pb_type_Control,
305
    MP_QSTR_Control,
306
    MP_TYPE_FLAG_NONE,
307
    attr, pb_attribute_handler,
308
    protocol, pb_type_Control_attr_dict,
309
    locals_dict, &pb_type_Control_locals_dict);
310

311
#endif // PYBRICKS_PY_COMMON_CONTROL
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