Skip to content

Commit ed49c02

Browse files
committed
add timeout; finish up for PR
1 parent c26de01 commit ed49c02

File tree

9 files changed

+119
-40
lines changed

9 files changed

+119
-40
lines changed

py/stream.c

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,22 @@ const mp_stream_p_t *mp_get_stream_raise(mp_obj_t self_in, int flags) {
9999

100100
STATIC mp_obj_t stream_read_generic(size_t n_args, const mp_obj_t *args, byte flags) {
101101
// What to do if sz < -1? Python docs don't specify this case.
102-
// CPython does a readall, but here we silently let negatives through,
103-
// and they will cause a MemoryError.
102+
// CPython does a readall, let's do the same.
104103
mp_int_t sz;
105-
if (n_args == 1 || args[1] == mp_const_none || ((sz = mp_obj_get_int(args[1])) == -1)) {
106-
return stream_readall(args[0]);
107-
}
108-
109104
const mp_stream_p_t *stream_p = mp_get_stream(args[0]);
105+
if (stream_p->pyserial_read_compatibility) {
106+
// Pyserial defaults to sz=1 if not specified.
107+
if (n_args == 1) {
108+
sz = 1;
109+
} else {
110+
// Pyserial treats negative size as 0.
111+
sz = MAX(0, mp_obj_get_int(args[1]));
112+
}
113+
} else {
114+
if (n_args == 1 || args[1] == mp_const_none || (sz = mp_obj_get_int(args[1])) <= -1) {
115+
return stream_readall(args[0]);
116+
}
117+
}
110118

111119
#if MICROPY_PY_BUILTINS_STR_UNICODE
112120
if (stream_p->is_text) {
@@ -284,7 +292,7 @@ STATIC mp_obj_t stream_readinto(size_t n_args, const mp_obj_t *args) {
284292
// https://docs.python.org/3/library/socket.html#socket.socket.recv_into
285293
mp_uint_t len = bufinfo.len;
286294
if (n_args > 2) {
287-
if (mp_get_stream(args[0])->pyserial_compatibility) {
295+
if (mp_get_stream(args[0])->pyserial_readinto_compatibility) {
288296
mp_raise_ValueError(translate("length argument not allowed for this type"));
289297
}
290298
len = mp_obj_get_int(args[2]);
@@ -297,7 +305,10 @@ STATIC mp_obj_t stream_readinto(size_t n_args, const mp_obj_t *args) {
297305
mp_uint_t out_sz = mp_stream_read_exactly(args[0], bufinfo.buf, len, &error);
298306
if (error != 0) {
299307
if (mp_is_nonblocking_error(error)) {
300-
return mp_const_none;
308+
// pyserial readinto never returns None, just 0.
309+
return mp_get_stream(args[0])->pyserial_dont_return_none_compatibility
310+
? MP_OBJ_NEW_SMALL_INT(0)
311+
: mp_const_none;
301312
}
302313
mp_raise_OSError(error);
303314
} else {
@@ -323,7 +334,10 @@ STATIC mp_obj_t stream_readall(mp_obj_t self_in) {
323334
// If we read nothing, return None, just like read().
324335
// Otherwise, return data read so far.
325336
if (total_size == 0) {
326-
return mp_const_none;
337+
// pyserial read() never returns None, just b''.
338+
return stream_p->pyserial_dont_return_none_compatibility
339+
? mp_const_empty_bytes
340+
: mp_const_none;
327341
}
328342
break;
329343
}

py/stream.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ typedef struct _mp_stream_p_t {
7272
mp_uint_t (*write)(mp_obj_t obj, const void *buf, mp_uint_t size, int *errcode);
7373
mp_uint_t (*ioctl)(mp_obj_t obj, mp_uint_t request, uintptr_t arg, int *errcode);
7474
mp_uint_t is_text : 1; // default is bytes, set this for text stream
75-
bool pyserial_compatibility: 1; // adjust API to match pyserial more closely
75+
bool pyserial_readinto_compatibility: 1; // Disallow size parameter in readinto()
76+
bool pyserial_read_compatibility: 1; // Disallow omitting read(size) size parameter
77+
bool pyserial_dont_return_none_compatibility: 1; // Don't return None for read() or readinto()
7678
} mp_stream_p_t;
7779

7880
MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_read_obj);

shared-bindings/_bleio/CharacteristicBuffer.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,8 @@ STATIC const mp_stream_p_t characteristic_buffer_stream_p = {
231231
.write = bleio_characteristic_buffer_write,
232232
.ioctl = bleio_characteristic_buffer_ioctl,
233233
.is_text = false,
234-
// Match PySerial when possible, such as disallowing optional length argument for .readinto()
235-
.pyserial_compatibility = true,
234+
// Disallow readinto() size parameter.
235+
.pyserial_readinto_compatibility = true,
236236
};
237237

238238

shared-bindings/busio/UART.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,8 @@ STATIC const mp_stream_p_t uart_stream_p = {
415415
.write = busio_uart_write,
416416
.ioctl = busio_uart_ioctl,
417417
.is_text = false,
418-
// Match PySerial when possible, such as disallowing optional length argument for .readinto()
419-
.pyserial_compatibility = true,
418+
// Disallow optional length argument for .readinto()
419+
.pyserial_readinto_compatibility = true,
420420
};
421421

422422
const mp_obj_type_t busio_uart_type = {

shared-bindings/usb_cdc/Serial.c

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,36 +41,34 @@
4141
//| def __init__(self) -> None:
4242
//| """You cannot create an instance of `usb_cdc.Serial`.
4343
//|
44-
//| Serial objects are constructed for every corresponding entry in the USB
44+
//| Serial objects are pre-constructed for each CDC device in the USB
4545
//| descriptor and added to the ``usb_cdc.ports`` tuple."""
4646
//| ...
4747
//|
4848

49-
// These are standard stream methods. Code is in py/stream.c.
50-
//
51-
//| def read(self, nbytes: Optional[int] = None) -> Optional[bytes]:
52-
//| """Read characters. If ``nbytes`` is specified then read at most that many
53-
//| bytes. Otherwise, read everything that arrives until the connection
54-
//| times out. Providing the number of bytes expected is highly recommended
55-
//| because it will be faster.
49+
//| def read(self, size: int = 1) -> bytes:
50+
//| """Read at most ``size`` bytes. If ``size`` exceeds the internal buffer size
51+
//| only the bytes in the buffer will be read. If `timeout` is > 0 or ``None``,
52+
//| and fewer than ``size`` bytes are available, keep waiting until the timeout
53+
//| expires or ``size`` bytes are available.
5654
//|
5755
//| :return: Data read
58-
//| :rtype: bytes or None"""
56+
//| :rtype: bytes"""
5957
//| ...
6058
//|
61-
//| def readinto(self, buf: WriteableBuffer, nbytes: Optional[int] = None) -> Optional[bytes]:
59+
//| def readinto(self, buf: WriteableBuffer) -> bytes:
6260
//| """Read bytes into the ``buf``. If ``nbytes`` is specified then read at most
63-
//| that many bytes. Otherwise, read at most ``len(buf)`` bytes.
61+
//| that many bytes, subject to `timeout`. Otherwise, read at most ``len(buf)`` bytes.
6462
//|
6563
//| :return: number of bytes read and stored into ``buf``
66-
//| :rtype: bytes or None"""
64+
//| :rtype: bytes"""
6765
//| ...
6866
//|
69-
//| def write(self, buf: ReadableBuffer) -> Optional[int]:
67+
//| def write(self, buf: ReadableBuffer) -> int:
7068
//| """Write as many bytes as possible from the buffer of bytes.
7169
//|
7270
//| :return: the number of bytes written
73-
//| :rtype: int or None"""
71+
//| :rtype: int"""
7472
//| ...
7573
//|
7674
//| def flush(self) -> None:
@@ -79,7 +77,7 @@
7977
//|
8078

8179
// These three methods are used by the shared stream methods.
82-
STATIC mp_uint_t usb_cdc_serial_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) {
80+
STATIC mp_uint_t usb_cdc_serial_read_stream(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) {
8381
usb_cdc_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
8482
byte *buf = buf_in;
8583

@@ -91,16 +89,16 @@ STATIC mp_uint_t usb_cdc_serial_read(mp_obj_t self_in, void *buf_in, mp_uint_t s
9189
return common_hal_usb_cdc_serial_read(self, buf, size, errcode);
9290
}
9391

94-
STATIC mp_uint_t usb_cdc_serial_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) {
92+
STATIC mp_uint_t usb_cdc_serial_write_stream(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) {
9593
usb_cdc_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
9694
const byte *buf = buf_in;
9795

9896
return common_hal_usb_cdc_serial_write(self, buf, size, errcode);
9997
}
10098

101-
STATIC mp_uint_t usb_cdc_serial_ioctl(mp_obj_t self_in, mp_uint_t request, mp_uint_t arg, int *errcode) {
99+
STATIC mp_uint_t usb_cdc_serial_ioctl_stream(mp_obj_t self_in, mp_uint_t request, mp_uint_t arg, int *errcode) {
102100
usb_cdc_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
103-
mp_uint_t ret;
101+
mp_uint_t ret = 0;
104102
switch (request) {
105103
case MP_IOCTL_POLL: {
106104
mp_uint_t flags = arg;
@@ -134,7 +132,7 @@ STATIC mp_obj_t usb_cdc_serial_get_connected(mp_obj_t self_in) {
134132
}
135133
MP_DEFINE_CONST_FUN_OBJ_1(usb_cdc_serial_get_connected_obj, usb_cdc_serial_get_connected);
136134

137-
const mp_obj_property_t usb_cdc_serial__connected_obj = {
135+
const mp_obj_property_t usb_cdc_serial_connected_obj = {
138136
.base.type = &mp_type_property,
139137
.proxy = {(mp_obj_t)&usb_cdc_serial_get_connected_obj,
140138
(mp_obj_t)&mp_const_none_obj,
@@ -195,6 +193,32 @@ STATIC mp_obj_t usb_cdc_serial_reset_output_buffer(mp_obj_t self_in) {
195193
}
196194
MP_DEFINE_CONST_FUN_OBJ_1(usb_cdc_serial_reset_output_buffer_obj, usb_cdc_serial_reset_output_buffer);
197195

196+
//| timeout: Optional[float]
197+
//| """The initial value of `timeout` is ``None``. If ``None``, wait indefinitely to satisfy
198+
//| the conditions of a read operation. If 0, do not wait. If > 0, wait only ``timeout`` seconds."""
199+
//|
200+
STATIC mp_obj_t usb_cdc_serial_get_timeout(mp_obj_t self_in) {
201+
usb_cdc_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
202+
mp_float_t timeout = common_hal_usb_cdc_serial_get_timeout(self);
203+
return (timeout < 0.0f) ? mp_const_none : mp_obj_new_float(self->timeout);
204+
}
205+
MP_DEFINE_CONST_FUN_OBJ_1(usb_cdc_serial_get_timeout_obj, usb_cdc_serial_get_timeout);
206+
207+
STATIC mp_obj_t usb_cdc_serial_set_timeout(mp_obj_t self_in, mp_obj_t timeout_in) {
208+
usb_cdc_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
209+
common_hal_usb_cdc_serial_set_timeout(self,
210+
timeout_in == mp_const_none ? -1.0f : mp_obj_get_float(timeout_in));
211+
return mp_const_none;
212+
}
213+
MP_DEFINE_CONST_FUN_OBJ_2(usb_cdc_serial_set_timeout_obj, usb_cdc_serial_set_timeout);
214+
215+
const mp_obj_property_t usb_cdc_serial_timeout_obj = {
216+
.base.type = &mp_type_property,
217+
.proxy = {(mp_obj_t)&usb_cdc_serial_get_timeout_obj,
218+
(mp_obj_t)&usb_cdc_serial_set_timeout_obj,
219+
(mp_obj_t)&mp_const_none_obj},
220+
};
221+
198222

199223
STATIC const mp_rom_map_elem_t usb_cdc_serial_locals_dict_table[] = {
200224
// Standard stream methods.
@@ -210,9 +234,10 @@ STATIC const mp_rom_map_elem_t usb_cdc_serial_locals_dict_table[] = {
210234
{ MP_OBJ_NEW_QSTR(MP_QSTR_out_waiting), MP_ROM_PTR(&usb_cdc_serial_out_waiting_obj) },
211235
{ MP_OBJ_NEW_QSTR(MP_QSTR_reset_input_buffer), MP_ROM_PTR(&usb_cdc_serial_reset_input_buffer_obj) },
212236
{ MP_OBJ_NEW_QSTR(MP_QSTR_reset_output_buffer), MP_ROM_PTR(&usb_cdc_serial_reset_output_buffer_obj) },
237+
{ MP_OBJ_NEW_QSTR(MP_QSTR_timeout), MP_ROM_PTR(&usb_cdc_serial_timeout_obj) },
213238

214239
// Not in pyserial protocol.
215-
{ MP_OBJ_NEW_QSTR(MP_QSTR_connected), MP_ROM_PTR(&usb_cdc_serial_get_connected_obj) },
240+
{ MP_OBJ_NEW_QSTR(MP_QSTR_connected), MP_ROM_PTR(&usb_cdc_serial_connected_obj) },
216241

217242

218243

@@ -221,10 +246,13 @@ STATIC MP_DEFINE_CONST_DICT(usb_cdc_serial_locals_dict, usb_cdc_serial_locals_di
221246

222247
STATIC const mp_stream_p_t usb_cdc_serial_stream_p = {
223248
MP_PROTO_IMPLEMENT(MP_QSTR_protocol_stream)
224-
.read = usb_cdc_serial_read,
225-
.write = usb_cdc_serial_write,
226-
.ioctl = usb_cdc_serial_ioctl,
249+
.read = usb_cdc_serial_read_stream,
250+
.write = usb_cdc_serial_write_stream,
251+
.ioctl = usb_cdc_serial_ioctl_stream,
227252
.is_text = false,
253+
.pyserial_read_compatibility = true,
254+
.pyserial_readinto_compatibility = true,
255+
.pyserial_dont_return_none_compatibility = true,
228256
};
229257

230258
const mp_obj_type_t usb_cdc_serial_type = {

shared-bindings/usb_cdc/Serial.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,7 @@ extern uint32_t common_hal_usb_cdc_serial_flush(usb_cdc_serial_obj_t *self);
4444

4545
extern bool common_hal_usb_cdc_serial_get_connected(usb_cdc_serial_obj_t *self);
4646

47+
extern mp_float_t common_hal_usb_cdc_serial_get_timeout(usb_cdc_serial_obj_t *self);
48+
extern void common_hal_usb_cdc_serial_set_timeout(usb_cdc_serial_obj_t *self, mp_float_t timeout);
49+
4750
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_USB_CDC_SERIAL_H

shared-module/usb_cdc/Serial.c

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,32 @@
2424
* THE SOFTWARE.
2525
*/
2626

27+
#include "lib/utils/interrupt_char.h"
2728
#include "shared-module/usb_cdc/Serial.h"
29+
#include "supervisor/shared/tick.h"
30+
2831
#include "tusb.h"
2932

3033
size_t common_hal_usb_cdc_serial_read(usb_cdc_serial_obj_t *self, uint8_t *data, size_t len, int *errcode) {
34+
if (self->timeout < 0.0f) {
35+
while (tud_cdc_n_available(self->idx) < len) {
36+
RUN_BACKGROUND_TASKS;
37+
if (mp_hal_is_interrupted()) {
38+
return 0;
39+
}
40+
}
41+
} else if (self->timeout > 0.0f) {
42+
uint64_t timeout_ms = self->timeout * 1000;
43+
uint64_t start_ticks = supervisor_ticks_ms64();
44+
while (tud_cdc_n_available(self->idx) < len &&
45+
supervisor_ticks_ms64() - start_ticks <= timeout_ms) {
46+
RUN_BACKGROUND_TASKS;
47+
if (mp_hal_is_interrupted()) {
48+
return 0;
49+
}
50+
}
51+
}
52+
// Timeout of 0.0f falls through to here with no waiting or unnecessary calculation.
3153
return tud_cdc_n_read(self->idx, data, len);
3254
}
3355

@@ -61,3 +83,11 @@ uint32_t common_hal_usb_cdc_serial_flush(usb_cdc_serial_obj_t *self) {
6183
bool common_hal_usb_cdc_serial_get_connected(usb_cdc_serial_obj_t *self) {
6284
return tud_cdc_n_connected(self->idx);
6385
}
86+
87+
mp_float_t common_hal_usb_cdc_serial_get_timeout(usb_cdc_serial_obj_t *self) {
88+
return self->timeout;
89+
}
90+
91+
void common_hal_usb_cdc_serial_set_timeout(usb_cdc_serial_obj_t *self, mp_float_t timeout) {
92+
self->timeout = timeout;
93+
}

shared-module/usb_cdc/Serial.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131

3232
typedef struct {
3333
mp_obj_base_t base;
34-
// Which CDC device?
35-
uint8_t idx;
34+
mp_float_t timeout; // if negative, wait forever.
35+
uint8_t idx; // which CDC device?
3636
} usb_cdc_serial_obj_t;
3737

3838
#endif // SHARED_MODULE_USB_CDC_SERIAL_H

shared-module/usb_cdc/__init__.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,13 @@
3838
#error CFG_TUD_CDC must be exactly 2
3939
#endif
4040

41-
static const usb_cdc_serial_obj_t serial_objs[CFG_TUD_CDC] = {
41+
static usb_cdc_serial_obj_t serial_objs[CFG_TUD_CDC] = {
4242
{ .base.type = &usb_cdc_serial_type,
43+
.timeout = -1.0f,
4344
.idx = 0,
4445
}, {
4546
.base.type = &usb_cdc_serial_type,
47+
.timeout = -1.0f,
4648
.idx = 1,
4749
}
4850
};

0 commit comments

Comments
 (0)