Skip to content

Commit a2c2e62

Browse files
committed
pbio/drv/usb: Restore subscribe message.
We dropped this when we converted to serial because the DTR serves a similar purpose and is more reliable, especially when it comes to determining disconnecting, which subscribe could only do explicitly, not implicitly if the host closed. But DTR is immediately asserted by most hosts, which is not ideal for the initial handshake, as the hub begins sending events right away. This restores subscribe so we get the best of both: subscribe gates the event flood but DTR takes care of automatically deasserting it on the hub side if it becomes disconnected.
1 parent 50bddc1 commit a2c2e62

4 files changed

Lines changed: 88 additions & 18 deletions

File tree

lib/pbio/drv/usb/usb.c

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,25 @@ void pbdrv_usb_set_host_connection_changed_callback(pbio_util_void_callback_t ca
3939
}
4040

4141
/**
42-
* Whether the host has opened the serial port (DTR asserted). This is the USB
43-
* analog of a BLE host subscribing to notifications.
42+
* Whether the host has opened the serial port (DTR asserted). This detects
43+
* disconnection gracefully, even if the host abruptly goes away (e.g. the
44+
* browser tab is closed) without unsubscribing.
4445
*/
4546
static bool pbdrv_usb_dtr;
4647

48+
/**
49+
* Whether the host is subscribed to our outgoing event messages. This is the
50+
* USB analog of a BLE host subscribing to notifications.
51+
*
52+
* Unlike DTR, this is asserted explicitly by the host application rather than
53+
* automatically by the OS when the port is opened. It gates the initial event
54+
* flood so that we don't bombard the host with events the moment DTR is
55+
* (possibly automatically) asserted.
56+
*/
57+
static bool pbdrv_usb_subscribed;
58+
4759
bool pbdrv_usb_connection_is_active(void) {
48-
return pbdrv_usb_is_ready() && pbdrv_usb_dtr;
60+
return pbdrv_usb_is_ready() && pbdrv_usb_dtr && pbdrv_usb_subscribed;
4961
}
5062

5163
//
@@ -226,10 +238,35 @@ void pbdrv_usb_on_dtr_changed(bool dtr) {
226238
pbdrv_usb_rx_frame_len = 0;
227239
pbdrv_usb_rx_overflow = false;
228240

229-
if (dtr) {
230-
// Host just opened the port. Send the current status right away, like
231-
// the first notification after a BLE host subscribes. Device info is
232-
// not pushed; the host reads it on demand via read requests.
241+
if (!dtr) {
242+
// Host closed the port. The subscription implicitly falls with DTR. In
243+
// practice the host rarely unsubscribes explicitly; it just lets DTR
244+
// drop, which we detect even if the host abruptly goes away.
245+
pbdrv_usb_subscribed = false;
246+
}
247+
248+
if (pbdrv_usb_host_connection_changed_callback) {
249+
pbdrv_usb_host_connection_changed_callback();
250+
}
251+
252+
pbio_os_request_poll();
253+
}
254+
255+
/**
256+
* Sets whether the host is subscribed to event notifications and notifies
257+
* listeners of the connection state change.
258+
*/
259+
static void pbdrv_usb_set_subscribed(bool subscribed) {
260+
if (subscribed == pbdrv_usb_subscribed) {
261+
return;
262+
}
263+
264+
pbdrv_usb_subscribed = subscribed;
265+
266+
if (subscribed) {
267+
// Host just subscribed. Send the current status right away, like the
268+
// first notification after a BLE host subscribes. Device info is not
269+
// pushed; the host reads it on demand via read requests.
233270
pbdrv_usb_status_data_pending = true;
234271
}
235272

@@ -444,7 +481,11 @@ static void pbdrv_usb_handle_data_in(void) {
444481

445482
// The first byte is the host-to-hub message type and the rest is
446483
// its payload.
447-
if (msg_size >= 2 && msg[0] == PBIO_PYBRICKS_OUT_EP_MSG_COMMAND && pbdrv_usb_receive_handler) {
484+
if (msg_size >= 2 && msg[0] == PBIO_PYBRICKS_OUT_EP_MSG_SUBSCRIBE) {
485+
// Subscribe or unsubscribe to event notifications. The payload
486+
// is a single byte: 1 to subscribe, 0 to unsubscribe.
487+
pbdrv_usb_set_subscribed(msg[1]);
488+
} else if (msg_size >= 2 && msg[0] == PBIO_PYBRICKS_OUT_EP_MSG_COMMAND && pbdrv_usb_receive_handler) {
448489
// Compute the response synchronously and queue it immediately.
449490
pbio_set_uint32_le(&pbdrv_usb_command_response_buf[1],
450491
pbdrv_usb_receive_handler(&msg[1], msg_size - 1));
@@ -475,6 +516,7 @@ static void pbdrv_usb_handle_data_in(void) {
475516

476517
static void pbdrv_usb_reset_state(void) {
477518
pbdrv_usb_on_dtr_changed(false);
519+
pbdrv_usb_subscribed = false;
478520
pbdrv_usb_command_response_pending = false;
479521
pbdrv_usb_read_reply_pending = false;
480522
pbdrv_usb_status_data_pending = false;

lib/pbio/drv/usb/usb_simulation.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ static pbio_error_t pbdrv_usb_test_process_thread(pbio_os_state_t *state, void *
9797

9898
PBIO_OS_ASYNC_BEGIN(state);
9999

100+
// Fake a subscribe message so the common driver starts sending events,
101+
// just as a real host would after opening the port. It is COBS-encoded
102+
// like real host traffic.
103+
static const uint8_t subscribe_msg[] = {
104+
PBIO_PYBRICKS_OUT_EP_MSG_SUBSCRIBE, 1,
105+
};
106+
usb_in_size = pbdrv_usb_cobs_encode(subscribe_msg, sizeof(subscribe_msg), usb_in_buf);
107+
100108
#ifdef PBDRV_CONFIG_RUN_ON_CI
101109
// CI and MicroPython test suite have lots of problems with stdin. It is
102110
// only needed for the REPL and interactive input, so don't bother on CI.
@@ -130,7 +138,8 @@ void pbdrv_usb_init_device(void) {
130138
static pbio_os_process_t pbdrv_usb_test_process;
131139
pbio_os_process_start(&pbdrv_usb_test_process, pbdrv_usb_test_process_thread, NULL);
132140

133-
// No physical port to open, so report the connection as active right away.
141+
// No physical port to open, so assert DTR right away. The process thread
142+
// also fakes a subscribe message, so the connection becomes active.
134143
pbdrv_usb_on_dtr_changed(true);
135144
}
136145

lib/pbio/drv/usb/usb_simulation_pico.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ uint32_t pbdrv_usb_get_data_and_start_receive(uint8_t *data) {
110110
static pbio_error_t pbdrv_usb_test_process_thread(pbio_os_state_t *state, void *context) {
111111
PBIO_OS_ASYNC_BEGIN(state);
112112

113+
// Fake a subscribe message so the common driver starts sending events,
114+
// just as a real host would after opening the port. It is COBS-encoded
115+
// like real host traffic.
116+
static const uint8_t subscribe_msg[] = {
117+
PBIO_PYBRICKS_OUT_EP_MSG_SUBSCRIBE, 1,
118+
};
119+
pbdrv_usb_simulation_pico_in_size = pbdrv_usb_cobs_encode(
120+
subscribe_msg, sizeof(subscribe_msg), pbdrv_usb_simulation_pico_in_buf);
121+
113122
for (;;) {
114123
static size_t available;
115124
PBIO_OS_AWAIT_UNTIL(state, pbdrv_usb_simulation_pico_in_size == 0 &&
@@ -160,8 +169,8 @@ void pbdrv_usb_init_device(void) {
160169
static pbio_os_process_t pbdrv_usb_test_process;
161170
pbio_os_process_start(&pbdrv_usb_test_process, pbdrv_usb_test_process_thread, NULL);
162171

163-
// No physical port to detect, so report the host connection as active
164-
// right away (USB analog of a BLE host subscribing).
172+
// No physical port to detect, so assert DTR right away. The process thread
173+
// also fakes a subscribe message, so the connection becomes active.
165174
pbdrv_usb_on_dtr_changed(true);
166175
}
167176

lib/pbio/include/pbio/protocol.h

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ typedef enum {
513513
* error code, analogous to a BLE write response.
514514
*/
515515
PBIO_PYBRICKS_IN_EP_MSG_RESPONSE = 1,
516-
/** Analog to BLE notification. Emitted while a host is connected. */
516+
/** Analog to BLE notification. Only emitted if subscribed. */
517517
PBIO_PYBRICKS_IN_EP_MSG_EVENT = 2,
518518
/**
519519
* Reply to a ::PBIO_PYBRICKS_OUT_EP_MSG_READ. The payload is
@@ -532,19 +532,29 @@ typedef enum {
532532
*/
533533
typedef enum {
534534
/**
535-
* A characteristic read request. The payload is
536-
* `[service, char_id_lo, char_id_hi]`, where service is one of the
537-
* ::PBIO_PYBRICKS_USB_INTERFACE_READ_CHARACTERISTIC_GATT values. The hub
538-
* replies with a ::PBIO_PYBRICKS_IN_EP_MSG_READ_REPLY. This is the serial
539-
* analog of a BLE host reading a characteristic by UUID.
535+
* Subscribe to events. The payload is a single byte: 1 to subscribe, 0 to
536+
* unsubscribe. The hub will only send events if the host is subscribed.
537+
*
538+
* Analog of BLE Client Characteristic Configuration Descriptor (CCCD).
540539
*/
541-
PBIO_PYBRICKS_OUT_EP_MSG_READ = 1,
540+
PBIO_PYBRICKS_OUT_EP_MSG_SUBSCRIBE = 1,
542541
/**
543542
* A Pybricks command (see ::pbio_pybricks_command_t), carried in the
544543
* remaining payload bytes. The hub replies with a
545544
* ::PBIO_PYBRICKS_IN_EP_MSG_RESPONSE.
545+
*
546+
* Analog of BLE Client Characteristic Write with response.
546547
*/
547548
PBIO_PYBRICKS_OUT_EP_MSG_COMMAND = 2,
549+
/**
550+
* A characteristic read request. The payload is
551+
* `[service, char_id_lo, char_id_hi]`, where service is one of the
552+
* ::PBIO_PYBRICKS_USB_INTERFACE_READ_CHARACTERISTIC_GATT values. The hub
553+
* replies with a ::PBIO_PYBRICKS_IN_EP_MSG_READ_REPLY.
554+
*
555+
* Analog of a BLE host reading a characteristic by UUID.
556+
*/
557+
PBIO_PYBRICKS_OUT_EP_MSG_READ = 3,
548558
} pbio_pybricks_usb_out_ep_msg_t;
549559

550560
#endif // _PBIO_PROTOCOL_H_

0 commit comments

Comments
 (0)