3434
3535// MTU is assumed to be 23, not the actual negotiated MTU.
3636// A overhead of 3 yields a max message size of 20 (=23-3)
37- #define LWP3_MAX_MESSAGE_SIZE 20
37+ #define LWP3_MAX_MESSAGE_SIZE (23 - LWP3_HEADER_SIZE)
3838
3939enum {
4040 REMOTE_PORT_LEFT_BUTTONS = 0 ,
@@ -88,8 +88,22 @@ static pbdrv_bluetooth_peripheral_char_t pb_lwp3device_char = {
8888typedef struct {
8989 pbio_task_t task ;
9090 #if PYBRICKS_PY_IODEVICES
91- uint8_t buffer [LWP3_MAX_MESSAGE_SIZE ];
92- bool notification_received ;
91+ /**
92+ * Buffer of incoming notifications.
93+ */
94+ uint8_t * noti_data ;
95+ /**
96+ * Maximum number of stored notifications.
97+ */
98+ uint8_t noti_num ;
99+ /**
100+ * Index to read (the oldest data)
101+ */
102+ uint8_t noti_idx_read ;
103+ /**
104+ * Index to write (next free slot)
105+ */
106+ uint8_t noti_idx_write ;
93107 #endif // PYBRICKS_PY_IODEVICES
94108 uint8_t left [3 ];
95109 uint8_t right [3 ];
@@ -102,25 +116,13 @@ typedef struct {
102116} pb_lwp3device_t ;
103117
104118static pb_lwp3device_t pb_lwp3device_singleton ;
119+ void * pb_lwp3device_root_pointer = & pb_lwp3device_singleton ;
120+ MP_REGISTER_ROOT_POINTER (void * pb_lwp3device_root_pointer );
105121
106- // Handles LEGO Wireless protocol messages from the LWP3 Device .
107- static pbio_pybricks_error_t handle_notification (pbdrv_bluetooth_connection_t connection , const uint8_t * value , uint32_t size ) {
122+ // Handles LEGO Wireless protocol messages from the Powered Up Remote .
123+ static pbio_pybricks_error_t handle_remote_notification (pbdrv_bluetooth_connection_t connection , const uint8_t * value , uint32_t size ) {
108124 pb_lwp3device_t * lwp3device = & pb_lwp3device_singleton ;
109125
110- #if PYBRICKS_PY_IODEVICES
111- // Each message overwrites the previous received messages
112- // Messages will be lost if they are not read fast enough
113- memcpy (lwp3device -> buffer , & value [0 ], (size < LWP3_MAX_MESSAGE_SIZE ) ? size : LWP3_MAX_MESSAGE_SIZE );
114- lwp3device -> notification_received = true;
115-
116- if (lwp3device -> hub_kind != LWP3_HUB_KIND_HANDSET ) {
117- // This is not a handset, so we don't care about button state.
118- return PBIO_PYBRICKS_ERROR_OK ;
119- }
120- #endif // PYBRICKS_PY_IODEVICES
121-
122- // The LWP3 class is mostly just used for the remote, so do the work
123- // to parse the button state here.
124126 if (value [0 ] == 5 && value [2 ] == LWP3_MSG_TYPE_HW_NET_CMDS && value [3 ] == LWP3_HW_NET_CMD_CONNECTION_REQ ) {
125127 // This message is meant for something else, but contains the center button state
126128 lwp3device -> center = value [4 ];
@@ -186,7 +188,7 @@ static void pb_lwp3device_assert_connected(void) {
186188 }
187189}
188190
189- static void pb_lwp3device_connect (const char * name , mp_int_t timeout , lwp3_hub_kind_t hub_kind , bool pair ) {
191+ static void pb_lwp3device_connect (const char * name , mp_int_t timeout , lwp3_hub_kind_t hub_kind , pbdrv_bluetooth_receive_handler_t notification_handler , bool pair ) {
190192 pb_lwp3device_t * lwp3device = & pb_lwp3device_singleton ;
191193
192194 // REVISIT: for now, we only allow a single connection to a LWP3 device.
@@ -201,7 +203,9 @@ static void pb_lwp3device_connect(const char *name, mp_int_t timeout, lwp3_hub_k
201203
202204 // needed to ensure that no buttons are "pressed" after reconnecting since
203205 // we are using static memory
204- memset (lwp3device , 0 , sizeof (* lwp3device ));
206+ memset (& lwp3device -> left , 0 , sizeof (lwp3device -> left ));
207+ memset (& lwp3device -> right , 0 , sizeof (lwp3device -> left ));
208+ lwp3device -> center = 0 ;
205209
206210 // Hub kind and name are set to filter advertisements and responses.
207211 lwp3device -> hub_kind = hub_kind ;
@@ -218,7 +222,7 @@ static void pb_lwp3device_connect(const char *name, mp_int_t timeout, lwp3_hub_k
218222 pbdrv_bluetooth_peripheral_scan_and_connect (& lwp3device -> task ,
219223 lwp3_advertisement_matches ,
220224 lwp3_advertisement_response_matches ,
221- handle_notification ,
225+ notification_handler ,
222226 options );
223227 pb_module_tools_pbio_task_do_blocking (& lwp3device -> task , timeout );
224228
@@ -396,7 +400,7 @@ static mp_obj_t pb_type_pupdevices_Remote_make_new(const mp_obj_type_t *type, si
396400
397401 const char * name = name_in == mp_const_none ? NULL : mp_obj_str_get_str (name_in );
398402 mp_int_t timeout = timeout_in == mp_const_none ? -1 : pb_obj_get_positive_int (timeout_in );
399- pb_lwp3device_connect (name , timeout , LWP3_HUB_KIND_HANDSET , false);
403+ pb_lwp3device_connect (name , timeout , LWP3_HUB_KIND_HANDSET , handle_remote_notification , false);
400404 pb_lwp3device_configure_remote ();
401405
402406 self -> buttons = pb_type_Keypad_obj_new (pb_type_remote_button_pressed );
@@ -480,22 +484,44 @@ MP_DEFINE_CONST_OBJ_TYPE(pb_type_pupdevices_Remote,
480484
481485#if PYBRICKS_PY_IODEVICES
482486
487+ static pbio_pybricks_error_t handle_lwp3_generic_notification (pbdrv_bluetooth_connection_t connection , const uint8_t * value , uint32_t size ) {
488+ pb_lwp3device_t * self = & pb_lwp3device_singleton ;
489+
490+ if (!self -> noti_data || !self -> noti_num ) {
491+ // Allocated data not ready, but no error.
492+ return PBIO_PYBRICKS_ERROR_OK ;
493+ }
494+
495+ memcpy (& self -> noti_data [self -> noti_idx_write * LWP3_MAX_MESSAGE_SIZE ], & value [0 ], (size < LWP3_MAX_MESSAGE_SIZE ) ? size : LWP3_MAX_MESSAGE_SIZE );
496+ self -> noti_idx_write = (self -> noti_idx_write + 1 ) % self -> noti_num ;
497+ return PBIO_PYBRICKS_ERROR_OK ;
498+ }
499+
483500static mp_obj_t pb_type_iodevices_LWP3Device_make_new (const mp_obj_type_t * type , size_t n_args , size_t n_kw , const mp_obj_t * args ) {
484501 PB_PARSE_ARGS_CLASS (n_args , n_kw , args ,
485502 PB_ARG_REQUIRED (hub_kind ),
486503 PB_ARG_DEFAULT_NONE (name ),
487504 PB_ARG_DEFAULT_INT (timeout , 10000 ),
488- PB_ARG_DEFAULT_FALSE (pair ));
505+ PB_ARG_DEFAULT_FALSE (pair ),
506+ PB_ARG_DEFAULT_INT (num_notifications , 8 ));
489507
490- pb_type_pupdevices_Remote_obj_t * self = mp_obj_malloc (pb_type_pupdevices_Remote_obj_t , type );
508+ mp_obj_base_t * obj = mp_obj_malloc (mp_obj_base_t , type );
509+ pb_lwp3device_t * self = & pb_lwp3device_singleton ;
491510
492511 const char * name = name_in == mp_const_none ? NULL : mp_obj_str_get_str (name_in );
493512 mp_int_t timeout = timeout_in == mp_const_none ? -1 : pb_obj_get_positive_int (timeout_in );
494513 uint8_t hub_kind = pb_obj_get_positive_int (hub_kind_in );
495514 bool pair = mp_obj_is_true (pair_in );
496- pb_lwp3device_connect (name , timeout , hub_kind , pair );
497515
498- return MP_OBJ_FROM_PTR (self );
516+ self -> noti_num = mp_obj_get_int (num_notifications_in );
517+ self -> noti_num = self -> noti_num ? self -> noti_num : 1 ;
518+ self -> noti_data = m_malloc0 (LWP3_MAX_MESSAGE_SIZE * self -> noti_num );
519+ self -> noti_idx_read = 0 ;
520+ self -> noti_idx_write = 0 ;
521+
522+ pb_lwp3device_connect (name , timeout , hub_kind , handle_lwp3_generic_notification , pair );
523+
524+ return MP_OBJ_FROM_PTR (obj );
499525}
500526
501527static mp_obj_t lwp3device_write (mp_obj_t self_in , mp_obj_t buf_in ) {
@@ -528,27 +554,31 @@ static mp_obj_t lwp3device_write(mp_obj_t self_in, mp_obj_t buf_in) {
528554static MP_DEFINE_CONST_FUN_OBJ_2 (lwp3device_write_obj , lwp3device_write ) ;
529555
530556static mp_obj_t lwp3device_read (mp_obj_t self_in ) {
531- pb_lwp3device_t * lwp3device = & pb_lwp3device_singleton ;
557+ pb_lwp3device_t * self = & pb_lwp3device_singleton ;
532558
533- // wait until a notification is received
534- for (;;) {
535- pb_lwp3device_assert_connected ();
536-
537- if (lwp3device -> notification_received ) {
538- lwp3device -> notification_received = false;
539- break ;
540- }
559+ pb_lwp3device_assert_connected ();
541560
542- MICROPY_EVENT_POLL_HOOK
561+ if (self -> noti_idx_read == self -> noti_idx_write || !self -> noti_num || !self -> noti_data ) {
562+ return mp_const_none ;
543563 }
544564
545- size_t len = lwp3device -> buffer [0 ];
565+ // Update index before returning, else bad values would not be cleared.
566+ uint8_t index = self -> noti_idx_read ;
567+ self -> noti_idx_read = (self -> noti_idx_read + 1 ) % self -> noti_num ;
546568
569+ // First byte is the size.
570+ uint8_t len = self -> noti_data [index * LWP3_MAX_MESSAGE_SIZE ];
547571 if (len < LWP3_HEADER_SIZE || len > LWP3_MAX_MESSAGE_SIZE ) {
548- mp_raise_msg (& mp_type_RuntimeError , MP_ERROR_TEXT ("bad data" ));
572+ // This is rare but it can happen sometimes. It is better to just
573+ // ignore it rather than raise and crash the user application.
574+ return mp_const_none ;
549575 }
550576
551- return mp_obj_new_bytes (lwp3device -> buffer , len );
577+ // Allocation of the return object may drive the runloop and process
578+ // new incoming messages, so copy data atomically before that happens.
579+ uint8_t message [LWP3_MAX_MESSAGE_SIZE ];
580+ memcpy (message , & self -> noti_data [index * LWP3_MAX_MESSAGE_SIZE ], len );
581+ return mp_obj_new_bytes (message , len );
552582}
553583static MP_DEFINE_CONST_FUN_OBJ_1 (lwp3device_read_obj , lwp3device_read ) ;
554584
0 commit comments