add timeout; finish up for PR

This commit is contained in:
Dan Halbert 2021-02-17 23:24:11 -05:00
parent c26de0136a
commit ed49c02feb
9 changed files with 119 additions and 40 deletions

View File

@ -99,14 +99,22 @@ const mp_stream_p_t *mp_get_stream_raise(mp_obj_t self_in, int flags) {
STATIC mp_obj_t stream_read_generic(size_t n_args, const mp_obj_t *args, byte flags) { STATIC mp_obj_t stream_read_generic(size_t n_args, const mp_obj_t *args, byte flags) {
// What to do if sz < -1? Python docs don't specify this case. // What to do if sz < -1? Python docs don't specify this case.
// CPython does a readall, but here we silently let negatives through, // CPython does a readall, let's do the same.
// and they will cause a MemoryError.
mp_int_t sz; mp_int_t sz;
if (n_args == 1 || args[1] == mp_const_none || ((sz = mp_obj_get_int(args[1])) == -1)) {
return stream_readall(args[0]);
}
const mp_stream_p_t *stream_p = mp_get_stream(args[0]); const mp_stream_p_t *stream_p = mp_get_stream(args[0]);
if (stream_p->pyserial_read_compatibility) {
// Pyserial defaults to sz=1 if not specified.
if (n_args == 1) {
sz = 1;
} else {
// Pyserial treats negative size as 0.
sz = MAX(0, mp_obj_get_int(args[1]));
}
} else {
if (n_args == 1 || args[1] == mp_const_none || (sz = mp_obj_get_int(args[1])) <= -1) {
return stream_readall(args[0]);
}
}
#if MICROPY_PY_BUILTINS_STR_UNICODE #if MICROPY_PY_BUILTINS_STR_UNICODE
if (stream_p->is_text) { if (stream_p->is_text) {
@ -284,7 +292,7 @@ STATIC mp_obj_t stream_readinto(size_t n_args, const mp_obj_t *args) {
// https://docs.python.org/3/library/socket.html#socket.socket.recv_into // https://docs.python.org/3/library/socket.html#socket.socket.recv_into
mp_uint_t len = bufinfo.len; mp_uint_t len = bufinfo.len;
if (n_args > 2) { if (n_args > 2) {
if (mp_get_stream(args[0])->pyserial_compatibility) { if (mp_get_stream(args[0])->pyserial_readinto_compatibility) {
mp_raise_ValueError(translate("length argument not allowed for this type")); mp_raise_ValueError(translate("length argument not allowed for this type"));
} }
len = mp_obj_get_int(args[2]); 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) {
mp_uint_t out_sz = mp_stream_read_exactly(args[0], bufinfo.buf, len, &error); mp_uint_t out_sz = mp_stream_read_exactly(args[0], bufinfo.buf, len, &error);
if (error != 0) { if (error != 0) {
if (mp_is_nonblocking_error(error)) { if (mp_is_nonblocking_error(error)) {
return mp_const_none; // pyserial readinto never returns None, just 0.
return mp_get_stream(args[0])->pyserial_dont_return_none_compatibility
? MP_OBJ_NEW_SMALL_INT(0)
: mp_const_none;
} }
mp_raise_OSError(error); mp_raise_OSError(error);
} else { } else {
@ -323,7 +334,10 @@ STATIC mp_obj_t stream_readall(mp_obj_t self_in) {
// If we read nothing, return None, just like read(). // If we read nothing, return None, just like read().
// Otherwise, return data read so far. // Otherwise, return data read so far.
if (total_size == 0) { if (total_size == 0) {
return mp_const_none; // pyserial read() never returns None, just b''.
return stream_p->pyserial_dont_return_none_compatibility
? mp_const_empty_bytes
: mp_const_none;
} }
break; break;
} }

View File

@ -72,7 +72,9 @@ typedef struct _mp_stream_p_t {
mp_uint_t (*write)(mp_obj_t obj, const void *buf, mp_uint_t size, int *errcode); mp_uint_t (*write)(mp_obj_t obj, const void *buf, mp_uint_t size, int *errcode);
mp_uint_t (*ioctl)(mp_obj_t obj, mp_uint_t request, uintptr_t arg, int *errcode); mp_uint_t (*ioctl)(mp_obj_t obj, mp_uint_t request, uintptr_t arg, int *errcode);
mp_uint_t is_text : 1; // default is bytes, set this for text stream mp_uint_t is_text : 1; // default is bytes, set this for text stream
bool pyserial_compatibility: 1; // adjust API to match pyserial more closely bool pyserial_readinto_compatibility: 1; // Disallow size parameter in readinto()
bool pyserial_read_compatibility: 1; // Disallow omitting read(size) size parameter
bool pyserial_dont_return_none_compatibility: 1; // Don't return None for read() or readinto()
} mp_stream_p_t; } mp_stream_p_t;
MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_read_obj); MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_stream_read_obj);

View File

@ -231,8 +231,8 @@ STATIC const mp_stream_p_t characteristic_buffer_stream_p = {
.write = bleio_characteristic_buffer_write, .write = bleio_characteristic_buffer_write,
.ioctl = bleio_characteristic_buffer_ioctl, .ioctl = bleio_characteristic_buffer_ioctl,
.is_text = false, .is_text = false,
// Match PySerial when possible, such as disallowing optional length argument for .readinto() // Disallow readinto() size parameter.
.pyserial_compatibility = true, .pyserial_readinto_compatibility = true,
}; };

View File

@ -415,8 +415,8 @@ STATIC const mp_stream_p_t uart_stream_p = {
.write = busio_uart_write, .write = busio_uart_write,
.ioctl = busio_uart_ioctl, .ioctl = busio_uart_ioctl,
.is_text = false, .is_text = false,
// Match PySerial when possible, such as disallowing optional length argument for .readinto() // Disallow optional length argument for .readinto()
.pyserial_compatibility = true, .pyserial_readinto_compatibility = true,
}; };
const mp_obj_type_t busio_uart_type = { const mp_obj_type_t busio_uart_type = {

View File

@ -41,36 +41,34 @@
//| def __init__(self) -> None: //| def __init__(self) -> None:
//| """You cannot create an instance of `usb_cdc.Serial`. //| """You cannot create an instance of `usb_cdc.Serial`.
//| //|
//| Serial objects are constructed for every corresponding entry in the USB //| Serial objects are pre-constructed for each CDC device in the USB
//| descriptor and added to the ``usb_cdc.ports`` tuple.""" //| descriptor and added to the ``usb_cdc.ports`` tuple."""
//| ... //| ...
//| //|
// These are standard stream methods. Code is in py/stream.c. //| def read(self, size: int = 1) -> bytes:
// //| """Read at most ``size`` bytes. If ``size`` exceeds the internal buffer size
//| def read(self, nbytes: Optional[int] = None) -> Optional[bytes]: //| only the bytes in the buffer will be read. If `timeout` is > 0 or ``None``,
//| """Read characters. If ``nbytes`` is specified then read at most that many //| and fewer than ``size`` bytes are available, keep waiting until the timeout
//| bytes. Otherwise, read everything that arrives until the connection //| expires or ``size`` bytes are available.
//| times out. Providing the number of bytes expected is highly recommended
//| because it will be faster.
//| //|
//| :return: Data read //| :return: Data read
//| :rtype: bytes or None""" //| :rtype: bytes"""
//| ... //| ...
//| //|
//| def readinto(self, buf: WriteableBuffer, nbytes: Optional[int] = None) -> Optional[bytes]: //| def readinto(self, buf: WriteableBuffer) -> bytes:
//| """Read bytes into the ``buf``. If ``nbytes`` is specified then read at most //| """Read bytes into the ``buf``. If ``nbytes`` is specified then read at most
//| that many bytes. Otherwise, read at most ``len(buf)`` bytes. //| that many bytes, subject to `timeout`. Otherwise, read at most ``len(buf)`` bytes.
//| //|
//| :return: number of bytes read and stored into ``buf`` //| :return: number of bytes read and stored into ``buf``
//| :rtype: bytes or None""" //| :rtype: bytes"""
//| ... //| ...
//| //|
//| def write(self, buf: ReadableBuffer) -> Optional[int]: //| def write(self, buf: ReadableBuffer) -> int:
//| """Write as many bytes as possible from the buffer of bytes. //| """Write as many bytes as possible from the buffer of bytes.
//| //|
//| :return: the number of bytes written //| :return: the number of bytes written
//| :rtype: int or None""" //| :rtype: int"""
//| ... //| ...
//| //|
//| def flush(self) -> None: //| def flush(self) -> None:
@ -79,7 +77,7 @@
//| //|
// These three methods are used by the shared stream methods. // These three methods are used by the shared stream methods.
STATIC mp_uint_t usb_cdc_serial_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { STATIC mp_uint_t usb_cdc_serial_read_stream(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) {
usb_cdc_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); usb_cdc_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
byte *buf = buf_in; byte *buf = buf_in;
@ -91,16 +89,16 @@ STATIC mp_uint_t usb_cdc_serial_read(mp_obj_t self_in, void *buf_in, mp_uint_t s
return common_hal_usb_cdc_serial_read(self, buf, size, errcode); return common_hal_usb_cdc_serial_read(self, buf, size, errcode);
} }
STATIC mp_uint_t usb_cdc_serial_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) { STATIC mp_uint_t usb_cdc_serial_write_stream(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) {
usb_cdc_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); usb_cdc_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
const byte *buf = buf_in; const byte *buf = buf_in;
return common_hal_usb_cdc_serial_write(self, buf, size, errcode); return common_hal_usb_cdc_serial_write(self, buf, size, errcode);
} }
STATIC mp_uint_t usb_cdc_serial_ioctl(mp_obj_t self_in, mp_uint_t request, mp_uint_t arg, int *errcode) { STATIC mp_uint_t usb_cdc_serial_ioctl_stream(mp_obj_t self_in, mp_uint_t request, mp_uint_t arg, int *errcode) {
usb_cdc_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); usb_cdc_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_uint_t ret; mp_uint_t ret = 0;
switch (request) { switch (request) {
case MP_IOCTL_POLL: { case MP_IOCTL_POLL: {
mp_uint_t flags = arg; mp_uint_t flags = arg;
@ -134,7 +132,7 @@ STATIC mp_obj_t usb_cdc_serial_get_connected(mp_obj_t self_in) {
} }
MP_DEFINE_CONST_FUN_OBJ_1(usb_cdc_serial_get_connected_obj, usb_cdc_serial_get_connected); MP_DEFINE_CONST_FUN_OBJ_1(usb_cdc_serial_get_connected_obj, usb_cdc_serial_get_connected);
const mp_obj_property_t usb_cdc_serial__connected_obj = { const mp_obj_property_t usb_cdc_serial_connected_obj = {
.base.type = &mp_type_property, .base.type = &mp_type_property,
.proxy = {(mp_obj_t)&usb_cdc_serial_get_connected_obj, .proxy = {(mp_obj_t)&usb_cdc_serial_get_connected_obj,
(mp_obj_t)&mp_const_none_obj, (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) {
} }
MP_DEFINE_CONST_FUN_OBJ_1(usb_cdc_serial_reset_output_buffer_obj, usb_cdc_serial_reset_output_buffer); MP_DEFINE_CONST_FUN_OBJ_1(usb_cdc_serial_reset_output_buffer_obj, usb_cdc_serial_reset_output_buffer);
//| timeout: Optional[float]
//| """The initial value of `timeout` is ``None``. If ``None``, wait indefinitely to satisfy
//| the conditions of a read operation. If 0, do not wait. If > 0, wait only ``timeout`` seconds."""
//|
STATIC mp_obj_t usb_cdc_serial_get_timeout(mp_obj_t self_in) {
usb_cdc_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_float_t timeout = common_hal_usb_cdc_serial_get_timeout(self);
return (timeout < 0.0f) ? mp_const_none : mp_obj_new_float(self->timeout);
}
MP_DEFINE_CONST_FUN_OBJ_1(usb_cdc_serial_get_timeout_obj, usb_cdc_serial_get_timeout);
STATIC mp_obj_t usb_cdc_serial_set_timeout(mp_obj_t self_in, mp_obj_t timeout_in) {
usb_cdc_serial_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_usb_cdc_serial_set_timeout(self,
timeout_in == mp_const_none ? -1.0f : mp_obj_get_float(timeout_in));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(usb_cdc_serial_set_timeout_obj, usb_cdc_serial_set_timeout);
const mp_obj_property_t usb_cdc_serial_timeout_obj = {
.base.type = &mp_type_property,
.proxy = {(mp_obj_t)&usb_cdc_serial_get_timeout_obj,
(mp_obj_t)&usb_cdc_serial_set_timeout_obj,
(mp_obj_t)&mp_const_none_obj},
};
STATIC const mp_rom_map_elem_t usb_cdc_serial_locals_dict_table[] = { STATIC const mp_rom_map_elem_t usb_cdc_serial_locals_dict_table[] = {
// Standard stream methods. // Standard stream methods.
@ -210,9 +234,10 @@ STATIC const mp_rom_map_elem_t usb_cdc_serial_locals_dict_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR_out_waiting), MP_ROM_PTR(&usb_cdc_serial_out_waiting_obj) }, { MP_OBJ_NEW_QSTR(MP_QSTR_out_waiting), MP_ROM_PTR(&usb_cdc_serial_out_waiting_obj) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_reset_input_buffer), MP_ROM_PTR(&usb_cdc_serial_reset_input_buffer_obj) }, { MP_OBJ_NEW_QSTR(MP_QSTR_reset_input_buffer), MP_ROM_PTR(&usb_cdc_serial_reset_input_buffer_obj) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_reset_output_buffer), MP_ROM_PTR(&usb_cdc_serial_reset_output_buffer_obj) }, { MP_OBJ_NEW_QSTR(MP_QSTR_reset_output_buffer), MP_ROM_PTR(&usb_cdc_serial_reset_output_buffer_obj) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_timeout), MP_ROM_PTR(&usb_cdc_serial_timeout_obj) },
// Not in pyserial protocol. // Not in pyserial protocol.
{ MP_OBJ_NEW_QSTR(MP_QSTR_connected), MP_ROM_PTR(&usb_cdc_serial_get_connected_obj) }, { MP_OBJ_NEW_QSTR(MP_QSTR_connected), MP_ROM_PTR(&usb_cdc_serial_connected_obj) },
@ -221,10 +246,13 @@ STATIC MP_DEFINE_CONST_DICT(usb_cdc_serial_locals_dict, usb_cdc_serial_locals_di
STATIC const mp_stream_p_t usb_cdc_serial_stream_p = { STATIC const mp_stream_p_t usb_cdc_serial_stream_p = {
MP_PROTO_IMPLEMENT(MP_QSTR_protocol_stream) MP_PROTO_IMPLEMENT(MP_QSTR_protocol_stream)
.read = usb_cdc_serial_read, .read = usb_cdc_serial_read_stream,
.write = usb_cdc_serial_write, .write = usb_cdc_serial_write_stream,
.ioctl = usb_cdc_serial_ioctl, .ioctl = usb_cdc_serial_ioctl_stream,
.is_text = false, .is_text = false,
.pyserial_read_compatibility = true,
.pyserial_readinto_compatibility = true,
.pyserial_dont_return_none_compatibility = true,
}; };
const mp_obj_type_t usb_cdc_serial_type = { const mp_obj_type_t usb_cdc_serial_type = {

View File

@ -44,4 +44,7 @@ extern uint32_t common_hal_usb_cdc_serial_flush(usb_cdc_serial_obj_t *self);
extern bool common_hal_usb_cdc_serial_get_connected(usb_cdc_serial_obj_t *self); extern bool common_hal_usb_cdc_serial_get_connected(usb_cdc_serial_obj_t *self);
extern mp_float_t common_hal_usb_cdc_serial_get_timeout(usb_cdc_serial_obj_t *self);
extern void common_hal_usb_cdc_serial_set_timeout(usb_cdc_serial_obj_t *self, mp_float_t timeout);
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_USB_CDC_SERIAL_H #endif // MICROPY_INCLUDED_SHARED_BINDINGS_USB_CDC_SERIAL_H

View File

@ -24,10 +24,32 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#include "lib/utils/interrupt_char.h"
#include "shared-module/usb_cdc/Serial.h" #include "shared-module/usb_cdc/Serial.h"
#include "supervisor/shared/tick.h"
#include "tusb.h" #include "tusb.h"
size_t common_hal_usb_cdc_serial_read(usb_cdc_serial_obj_t *self, uint8_t *data, size_t len, int *errcode) { size_t common_hal_usb_cdc_serial_read(usb_cdc_serial_obj_t *self, uint8_t *data, size_t len, int *errcode) {
if (self->timeout < 0.0f) {
while (tud_cdc_n_available(self->idx) < len) {
RUN_BACKGROUND_TASKS;
if (mp_hal_is_interrupted()) {
return 0;
}
}
} else if (self->timeout > 0.0f) {
uint64_t timeout_ms = self->timeout * 1000;
uint64_t start_ticks = supervisor_ticks_ms64();
while (tud_cdc_n_available(self->idx) < len &&
supervisor_ticks_ms64() - start_ticks <= timeout_ms) {
RUN_BACKGROUND_TASKS;
if (mp_hal_is_interrupted()) {
return 0;
}
}
}
// Timeout of 0.0f falls through to here with no waiting or unnecessary calculation.
return tud_cdc_n_read(self->idx, data, len); return tud_cdc_n_read(self->idx, data, len);
} }
@ -61,3 +83,11 @@ uint32_t common_hal_usb_cdc_serial_flush(usb_cdc_serial_obj_t *self) {
bool common_hal_usb_cdc_serial_get_connected(usb_cdc_serial_obj_t *self) { bool common_hal_usb_cdc_serial_get_connected(usb_cdc_serial_obj_t *self) {
return tud_cdc_n_connected(self->idx); return tud_cdc_n_connected(self->idx);
} }
mp_float_t common_hal_usb_cdc_serial_get_timeout(usb_cdc_serial_obj_t *self) {
return self->timeout;
}
void common_hal_usb_cdc_serial_set_timeout(usb_cdc_serial_obj_t *self, mp_float_t timeout) {
self->timeout = timeout;
}

View File

@ -31,8 +31,8 @@
typedef struct { typedef struct {
mp_obj_base_t base; mp_obj_base_t base;
// Which CDC device? mp_float_t timeout; // if negative, wait forever.
uint8_t idx; uint8_t idx; // which CDC device?
} usb_cdc_serial_obj_t; } usb_cdc_serial_obj_t;
#endif // SHARED_MODULE_USB_CDC_SERIAL_H #endif // SHARED_MODULE_USB_CDC_SERIAL_H

View File

@ -38,11 +38,13 @@
#error CFG_TUD_CDC must be exactly 2 #error CFG_TUD_CDC must be exactly 2
#endif #endif
static const usb_cdc_serial_obj_t serial_objs[CFG_TUD_CDC] = { static usb_cdc_serial_obj_t serial_objs[CFG_TUD_CDC] = {
{ .base.type = &usb_cdc_serial_type, { .base.type = &usb_cdc_serial_type,
.timeout = -1.0f,
.idx = 0, .idx = 0,
}, { }, {
.base.type = &usb_cdc_serial_type, .base.type = &usb_cdc_serial_type,
.timeout = -1.0f,
.idx = 1, .idx = 1,
} }
}; };