Skip to content

Commit d6479db

Browse files
committed
pybricks.iodevices.XboxController: Update for non-singletons.
Follow the recent updates for peripherals, preparing for multiple peripherals, and fix use after disconnect.
1 parent 279aba9 commit d6479db

1 file changed

Lines changed: 111 additions & 68 deletions

File tree

pybricks/iodevices/pb_type_iodevices_xbox_controller.c

Lines changed: 111 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
/**
3939
* The main HID Characteristic.
4040
*/
41-
static pbdrv_bluetooth_peripheral_char_t pb_xbox_char_hid_report = {
41+
static pbdrv_bluetooth_peripheral_char_t pb_type_xbox_char_hid_report = {
4242
.handle = 0, // Will be set during discovery.
4343
// Even with the property filter, there are still 3 matches for this
4444
// characteristic on the Elite Series 2 controller. For now limit discovery
@@ -54,7 +54,7 @@ static pbdrv_bluetooth_peripheral_char_t pb_xbox_char_hid_report = {
5454
/**
5555
* Unused characteristic that needs to be read for controller to become active.
5656
*/
57-
static pbdrv_bluetooth_peripheral_char_t pb_xbox_char_hid_map = {
57+
static pbdrv_bluetooth_peripheral_char_t pb_type_xbox_char_hid_map = {
5858
.uuid16 = 0x2a4b,
5959
.request_notification = false,
6060
};
@@ -75,17 +75,40 @@ typedef struct __attribute__((packed)) {
7575
uint8_t paddles;
7676
} xbox_input_map_t;
7777

78-
typedef struct {
79-
xbox_input_map_t state;
80-
} pb_xbox_t;
81-
82-
static pb_xbox_t pb_xbox_singleton;
78+
typedef struct _pb_type_xbox_obj_t {
79+
mp_obj_base_t base;
80+
/**
81+
* The peripheral instance associated with this MicroPython object.
82+
*/
83+
pbdrv_bluetooth_peripheral_t *peripheral;
84+
/**
85+
* Buttons object on the Xbox Controller instance.
86+
*/
87+
mp_obj_t buttons;
88+
/**
89+
* Threshold below which joystick reports zero to avoid drift.
90+
*/
91+
mp_int_t joystick_deadzone;
92+
/**
93+
* State of awaitable used for connecting and writing.
94+
*/
95+
pb_type_async_t *iter;
96+
/**
97+
* Button and joystick state (populated by notification handler).
98+
*/
99+
xbox_input_map_t input_map;
100+
} pb_type_xbox_obj_t;
83101

84102
// Handles LEGO Wireless protocol messages from the XBOX Device.
85103
static void handle_notification(void *user, const uint8_t *value, uint32_t size) {
86-
pb_xbox_t *xbox = &pb_xbox_singleton;
104+
105+
pb_type_xbox_obj_t *self = user;
106+
if (!self) {
107+
return;
108+
}
109+
87110
if (size <= sizeof(xbox_input_map_t)) {
88-
memcpy(&xbox->state, &value[0], size);
111+
memcpy(&self->input_map, &value[0], size);
89112
}
90113
}
91114

@@ -159,27 +182,20 @@ static pbdrv_bluetooth_ad_match_result_flags_t xbox_advertisement_response_match
159182
return flags;
160183
}
161184

162-
static void pb_xbox_assert_connected(void) {
185+
static xbox_input_map_t *pb_type_xbox_get_input(mp_obj_t self_in) {
186+
163187
if (!pbdrv_bluetooth_is_connected(PBDRV_BLUETOOTH_CONNECTION_PERIPHERAL)) {
164188
mp_raise_OSError(MP_ENODEV);
165189
}
166-
}
167190

168-
typedef struct _pb_type_xbox_obj_t {
169-
mp_obj_base_t base;
170-
mp_obj_t buttons;
171-
mp_int_t joystick_deadzone;
172-
pb_type_async_t *iter;
173-
} pb_type_xbox_obj_t;
191+
pb_type_xbox_obj_t *self = MP_OBJ_TO_PTR(self_in);
174192

175-
static xbox_input_map_t *pb_xbox_get_buttons(void) {
176-
xbox_input_map_t *buttons = &pb_xbox_singleton.state;
177-
pb_xbox_assert_connected();
178-
return buttons;
193+
return &self->input_map;
179194
}
180195

181-
static mp_obj_t pb_xbox_button_pressed(mp_obj_t self_in) {
182-
xbox_input_map_t *buttons = pb_xbox_get_buttons();
196+
static mp_obj_t pb_type_xbox_button_pressed(mp_obj_t self_in) {
197+
198+
xbox_input_map_t *buttons = pb_type_xbox_get_input(self_in);
183199

184200
// At most 16 simultaneous button presses, plus up to two dpad directions.
185201
mp_obj_t items[16 + 2];
@@ -276,6 +292,11 @@ static pbio_error_t xbox_connect_thread(pbio_os_state_t *state, mp_obj_t parent_
276292

277293
PBIO_OS_ASYNC_BEGIN(state);
278294

295+
pb_type_xbox_obj_t *self = MP_OBJ_TO_PTR(parent_obj);
296+
297+
// Get available peripheral instance.
298+
pb_assert(pbdrv_bluetooth_peripheral_get_available(&self->peripheral, self));
299+
279300
// Connect with bonding enabled. On some computers, the pairing step will
280301
// fail if the hub is still connected to Pybricks Code. Since it is unclear
281302
// which computer will have this problem, recommend to disconnect the hub
@@ -296,24 +317,24 @@ static pbio_error_t xbox_connect_thread(pbio_os_state_t *state, mp_obj_t parent_
296317
// catch the case where user might not have done this at least once.
297318
// Connecting takes about a second longer this way, but we can provide
298319
// better error messages.
299-
pb_assert(pbdrv_bluetooth_peripheral_discover_characteristic(&pb_xbox_char_hid_map));
320+
pb_assert(pbdrv_bluetooth_peripheral_discover_characteristic(&pb_type_xbox_char_hid_map));
300321
PBIO_OS_AWAIT(state, &unused, err = pbdrv_bluetooth_await_peripheral_command(&unused, NULL));
301322
if (err != PBIO_SUCCESS) {
302323
goto disconnect;
303324
}
304-
pb_assert(pbdrv_bluetooth_peripheral_read_characteristic(&pb_xbox_char_hid_map));
325+
pb_assert(pbdrv_bluetooth_peripheral_read_characteristic(&pb_type_xbox_char_hid_map));
305326
PBIO_OS_AWAIT(state, &unused, err = pbdrv_bluetooth_await_peripheral_command(&unused, NULL));
306327
if (err != PBIO_SUCCESS) {
307328
goto disconnect;
308329
}
309330

310331
// This is the main characteristic that notifies us of button state.
311-
pb_assert(pbdrv_bluetooth_peripheral_discover_characteristic(&pb_xbox_char_hid_report));
332+
pb_assert(pbdrv_bluetooth_peripheral_discover_characteristic(&pb_type_xbox_char_hid_report));
312333
PBIO_OS_AWAIT(state, &unused, err = pbdrv_bluetooth_await_peripheral_command(&unused, NULL));
313334
if (err != PBIO_SUCCESS) {
314335
goto disconnect;
315336
}
316-
pb_assert(pbdrv_bluetooth_peripheral_read_characteristic(&pb_xbox_char_hid_report));
337+
pb_assert(pbdrv_bluetooth_peripheral_read_characteristic(&pb_type_xbox_char_hid_report));
317338
PBIO_OS_AWAIT(state, &unused, err = pbdrv_bluetooth_await_peripheral_command(&unused, NULL));
318339
if (err != PBIO_SUCCESS) {
319340
goto disconnect;
@@ -360,6 +381,24 @@ static mp_obj_t pb_type_xbox_await_operation(mp_obj_t self_in) {
360381
return pb_type_async_wait_or_await(&config, &self->iter, true);
361382
}
362383

384+
static mp_obj_t pb_type_xbox_close(mp_obj_t self_in) {
385+
pb_type_xbox_obj_t *self = MP_OBJ_TO_PTR(self_in);
386+
// Disables notification handler from accessing allocated memory.
387+
pbdrv_bluetooth_peripheral_release(self->peripheral, self);
388+
return mp_const_none;
389+
}
390+
MP_DEFINE_CONST_FUN_OBJ_1(pb_type_xbox_close_obj, pb_type_xbox_close);
391+
392+
static mp_obj_t pb_type_xbox_disconnect(mp_obj_t self_in) {
393+
// Needed to release claim on allocated data so we can make a new
394+
// connection later.
395+
pb_type_xbox_close(self_in);
396+
pb_assert(pbdrv_bluetooth_peripheral_disconnect());
397+
return pb_type_xbox_await_operation(self_in);
398+
}
399+
static MP_DEFINE_CONST_FUN_OBJ_1(pb_type_xbox_disconnect_obj, pb_type_xbox_disconnect);
400+
401+
363402
static mp_obj_t pb_type_xbox_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
364403

365404
PB_PARSE_ARGS_CLASS(n_args, n_kw, args,
@@ -373,17 +412,17 @@ static mp_obj_t pb_type_xbox_make_new(const mp_obj_type_t *type, size_t n_args,
373412

374413
pb_module_tools_assert_blocking();
375414

376-
pb_type_xbox_obj_t *self = mp_obj_malloc(pb_type_xbox_obj_t, type);
415+
pb_type_xbox_obj_t *self = mp_obj_malloc_with_finaliser(pb_type_xbox_obj_t, type);
377416
self->joystick_deadzone = pb_obj_get_pct(joystick_deadzone_in);
378417
self->iter = NULL;
379418

380-
pb_xbox_t *xbox = &pb_xbox_singleton;
381-
self->buttons = pb_type_Keypad_obj_new(MP_OBJ_FROM_PTR(self), pb_xbox_button_pressed);
419+
self->buttons = pb_type_Keypad_obj_new(MP_OBJ_FROM_PTR(self), pb_type_xbox_button_pressed);
382420

383-
// needed to ensure that no buttons are "pressed" after reconnecting since
384-
// we are using static memory
385-
memset(&xbox->state, 0, sizeof(xbox_input_map_t));
386-
xbox->state.x = xbox->state.y = xbox->state.z = xbox->state.rz = INT16_MAX;
421+
// needed to ensure that no buttons are "pressed" since we are using
422+
// allocated memory
423+
xbox_input_map_t *input = &self->input_map;
424+
memset(input, 0, sizeof(xbox_input_map_t));
425+
input->x = input->y = input->z = input->rz = INT16_MAX;
387426

388427
// Xbox Controller requires pairing.
389428
scan_config.options = PBDRV_BLUETOOTH_PERIPHERAL_OPTIONS_PAIR;
@@ -403,16 +442,18 @@ static mp_obj_t pb_type_xbox_make_new(const mp_obj_type_t *type, size_t n_args,
403442
return MP_OBJ_FROM_PTR(self);
404443
}
405444

406-
static mp_obj_t pb_xbox_name(size_t n_args, const mp_obj_t *args) {
407-
pb_xbox_assert_connected();
445+
static mp_obj_t pb_type_xbox_name(size_t n_args, const mp_obj_t *args) {
446+
// Asserts connection.
447+
pb_type_xbox_get_input(args[0]);
448+
408449
const char *name = pbdrv_bluetooth_peripheral_get_name();
409450
return mp_obj_new_str(name, strlen(name));
410451
}
411-
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pb_xbox_name_obj, 1, 2, pb_xbox_name);
452+
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pb_type_xbox_name_obj, 1, 2, pb_type_xbox_name);
412453

413-
static mp_obj_t pb_xbox_state(mp_obj_t self_in) {
454+
static mp_obj_t pb_type_xbox_state(mp_obj_t self_in) {
414455

415-
xbox_input_map_t *buttons = pb_xbox_get_buttons();
456+
xbox_input_map_t *buttons = pb_type_xbox_get_input(self_in);
416457

417458
mp_obj_t state[] = {
418459
mp_obj_new_int(buttons->x - INT16_MAX),
@@ -430,21 +471,21 @@ static mp_obj_t pb_xbox_state(mp_obj_t self_in) {
430471
};
431472
return mp_obj_new_tuple(MP_ARRAY_SIZE(state), state);
432473
}
433-
static MP_DEFINE_CONST_FUN_OBJ_1(pb_xbox_state_obj, pb_xbox_state);
474+
static MP_DEFINE_CONST_FUN_OBJ_1(pb_type_xbox_state_obj, pb_type_xbox_state);
434475

435-
static mp_obj_t pb_xbox_dpad(mp_obj_t self_in) {
436-
xbox_input_map_t *buttons = pb_xbox_get_buttons();
476+
static mp_obj_t pb_type_xbox_dpad(mp_obj_t self_in) {
477+
xbox_input_map_t *buttons = pb_type_xbox_get_input(self_in);
437478
return mp_obj_new_int(buttons->dpad);
438479
}
439-
static MP_DEFINE_CONST_FUN_OBJ_1(pb_xbox_dpad_obj, pb_xbox_dpad);
480+
static MP_DEFINE_CONST_FUN_OBJ_1(pb_type_xbox_dpad_obj, pb_type_xbox_dpad);
440481

441-
static mp_obj_t pb_xbox_profile(mp_obj_t self_in) {
442-
xbox_input_map_t *buttons = pb_xbox_get_buttons();
482+
static mp_obj_t pb_type_xbox_profile(mp_obj_t self_in) {
483+
xbox_input_map_t *buttons = pb_type_xbox_get_input(self_in);
443484
return mp_obj_new_int(buttons->profile);
444485
}
445-
static MP_DEFINE_CONST_FUN_OBJ_1(pb_xbox_profile_obj, pb_xbox_profile);
486+
static MP_DEFINE_CONST_FUN_OBJ_1(pb_type_xbox_profile_obj, pb_type_xbox_profile);
446487

447-
static mp_obj_t pb_xbox_joystick(mp_obj_t self_in, uint16_t x_raw, uint16_t y_raw) {
488+
static mp_obj_t pb_type_xbox_joystick(mp_obj_t self_in, uint16_t x_raw, uint16_t y_raw) {
448489
pb_type_xbox_obj_t *self = MP_OBJ_TO_PTR(self_in);
449490

450491
mp_int_t x = (x_raw - INT16_MAX) * 100 / INT16_MAX;
@@ -464,27 +505,27 @@ static mp_obj_t pb_xbox_joystick(mp_obj_t self_in, uint16_t x_raw, uint16_t y_ra
464505
return mp_obj_new_tuple(MP_ARRAY_SIZE(directions), directions);
465506
}
466507

467-
static mp_obj_t pb_xbox_joystick_left(mp_obj_t self_in) {
468-
xbox_input_map_t *buttons = pb_xbox_get_buttons();
469-
return pb_xbox_joystick(self_in, buttons->x, buttons->y);
508+
static mp_obj_t pb_type_xbox_joystick_left(mp_obj_t self_in) {
509+
xbox_input_map_t *buttons = pb_type_xbox_get_input(self_in);
510+
return pb_type_xbox_joystick(self_in, buttons->x, buttons->y);
470511
}
471-
static MP_DEFINE_CONST_FUN_OBJ_1(pb_xbox_joystick_left_obj, pb_xbox_joystick_left);
512+
static MP_DEFINE_CONST_FUN_OBJ_1(pb_type_xbox_joystick_left_obj, pb_type_xbox_joystick_left);
472513

473-
static mp_obj_t pb_xbox_joystick_right(mp_obj_t self_in) {
474-
xbox_input_map_t *buttons = pb_xbox_get_buttons();
475-
return pb_xbox_joystick(self_in, buttons->z, buttons->rz);
514+
static mp_obj_t pb_type_xbox_joystick_right(mp_obj_t self_in) {
515+
xbox_input_map_t *buttons = pb_type_xbox_get_input(self_in);
516+
return pb_type_xbox_joystick(self_in, buttons->z, buttons->rz);
476517
}
477-
static MP_DEFINE_CONST_FUN_OBJ_1(pb_xbox_joystick_right_obj, pb_xbox_joystick_right);
518+
static MP_DEFINE_CONST_FUN_OBJ_1(pb_type_xbox_joystick_right_obj, pb_type_xbox_joystick_right);
478519

479-
static mp_obj_t pb_xbox_triggers(mp_obj_t self_in) {
480-
xbox_input_map_t *buttons = pb_xbox_get_buttons();
520+
static mp_obj_t pb_type_xbox_triggers(mp_obj_t self_in) {
521+
xbox_input_map_t *buttons = pb_type_xbox_get_input(self_in);
481522
mp_obj_t tiggers[] = {
482523
mp_obj_new_int(buttons->left_trigger * 100 / 1023),
483524
mp_obj_new_int(buttons->right_trigger * 100 / 1023),
484525
};
485526
return mp_obj_new_tuple(MP_ARRAY_SIZE(tiggers), tiggers);
486527
}
487-
static MP_DEFINE_CONST_FUN_OBJ_1(pb_xbox_triggers_obj, pb_xbox_triggers);
528+
static MP_DEFINE_CONST_FUN_OBJ_1(pb_type_xbox_triggers_obj, pb_type_xbox_triggers);
488529

489530
typedef struct {
490531
uint8_t activation_flags;
@@ -497,7 +538,7 @@ typedef struct {
497538
uint8_t repetitions;
498539
} __attribute__((packed)) xbox_rumble_command_t;
499540

500-
static mp_obj_t pb_xbox_rumble(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
541+
static mp_obj_t pb_type_xbox_rumble(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
501542
PB_PARSE_ARGS_METHOD(n_args, pos_args, kw_args,
502543
pb_type_xbox_obj_t, self,
503544
PB_ARG_DEFAULT_INT(power, 100),
@@ -564,17 +605,19 @@ static mp_obj_t pb_xbox_rumble(size_t n_args, const mp_obj_t *pos_args, mp_map_t
564605
pbdrv_bluetooth_peripheral_write_characteristic(handle, (const uint8_t *)&command, sizeof(command));
565606
return pb_type_xbox_await_operation(MP_OBJ_TO_PTR(self));
566607
}
567-
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_xbox_rumble_obj, 1, pb_xbox_rumble);
608+
static MP_DEFINE_CONST_FUN_OBJ_KW(pb_type_xbox_rumble_obj, 1, pb_type_xbox_rumble);
568609

569610
static const mp_rom_map_elem_t pb_type_xbox_locals_dict_table[] = {
570-
{ MP_ROM_QSTR(MP_QSTR_name), MP_ROM_PTR(&pb_xbox_name_obj) },
571-
{ MP_ROM_QSTR(MP_QSTR_state), MP_ROM_PTR(&pb_xbox_state_obj) },
572-
{ MP_ROM_QSTR(MP_QSTR_dpad), MP_ROM_PTR(&pb_xbox_dpad_obj) },
573-
{ MP_ROM_QSTR(MP_QSTR_profile), MP_ROM_PTR(&pb_xbox_profile_obj) },
574-
{ MP_ROM_QSTR(MP_QSTR_joystick_left), MP_ROM_PTR(&pb_xbox_joystick_left_obj) },
575-
{ MP_ROM_QSTR(MP_QSTR_joystick_right), MP_ROM_PTR(&pb_xbox_joystick_right_obj) },
576-
{ MP_ROM_QSTR(MP_QSTR_triggers), MP_ROM_PTR(&pb_xbox_triggers_obj) },
577-
{ MP_ROM_QSTR(MP_QSTR_rumble), MP_ROM_PTR(&pb_xbox_rumble_obj) },
611+
{ MP_ROM_QSTR(MP_QSTR_name), MP_ROM_PTR(&pb_type_xbox_name_obj) },
612+
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&pb_type_xbox_close_obj) },
613+
{ MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&pb_type_xbox_disconnect_obj) },
614+
{ MP_ROM_QSTR(MP_QSTR_state), MP_ROM_PTR(&pb_type_xbox_state_obj) },
615+
{ MP_ROM_QSTR(MP_QSTR_dpad), MP_ROM_PTR(&pb_type_xbox_dpad_obj) },
616+
{ MP_ROM_QSTR(MP_QSTR_profile), MP_ROM_PTR(&pb_type_xbox_profile_obj) },
617+
{ MP_ROM_QSTR(MP_QSTR_joystick_left), MP_ROM_PTR(&pb_type_xbox_joystick_left_obj) },
618+
{ MP_ROM_QSTR(MP_QSTR_joystick_right), MP_ROM_PTR(&pb_type_xbox_joystick_right_obj) },
619+
{ MP_ROM_QSTR(MP_QSTR_triggers), MP_ROM_PTR(&pb_type_xbox_triggers_obj) },
620+
{ MP_ROM_QSTR(MP_QSTR_rumble), MP_ROM_PTR(&pb_type_xbox_rumble_obj) },
578621
};
579622
static MP_DEFINE_CONST_DICT(pb_type_xbox_locals_dict, pb_type_xbox_locals_dict_table);
580623

0 commit comments

Comments
 (0)