diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 6beb320527e43..8e3379252549e 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -2968,6 +2968,10 @@ msgstr "" msgid "destination buffer must be an array of type 'H' for bit_depth = 16" msgstr "" +#: shared-bindings/usb/cdc_host/__init__.c +msgid "device must be a usb.core.Device object" +msgstr "" + #: py/objdict.c msgid "dict update sequence has wrong length" msgstr "" diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index fa90481e648ea..1f2dac4c3ecee 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -785,6 +785,8 @@ SRC_SHARED_MODULE_ALL = \ usb/__init__.c \ usb/core/__init__.c \ usb/core/Device.c \ + usb/cdc_host/Serial.c \ + usb/cdc_host/__init__.c \ usb/util/__init__.c \ ustack/__init__.c \ vectorio/Circle.c \ diff --git a/shared-bindings/usb/__init__.c b/shared-bindings/usb/__init__.c index ea05229984c97..4ee289960ac57 100644 --- a/shared-bindings/usb/__init__.c +++ b/shared-bindings/usb/__init__.c @@ -11,6 +11,7 @@ #include "shared-bindings/usb/__init__.h" #include "shared-bindings/usb/core/__init__.h" #include "shared-bindings/usb/util/__init__.h" +#include "shared-bindings/usb/cdc_host/__init__.h" #include "supervisor/usb.h" //| """PyUSB-compatible USB host API @@ -23,6 +24,7 @@ static mp_rom_map_elem_t usb_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_usb) }, { MP_ROM_QSTR(MP_QSTR_core), MP_OBJ_FROM_PTR(&usb_core_module) }, { MP_ROM_QSTR(MP_QSTR_util), MP_OBJ_FROM_PTR(&usb_util_module) }, + { MP_ROM_QSTR(MP_QSTR_cdc_host), MP_OBJ_FROM_PTR(&usb_cdc_host_module) }, }; static MP_DEFINE_CONST_DICT(usb_module_globals, usb_module_globals_table); diff --git a/shared-bindings/usb/cdc_host/Serial.c b/shared-bindings/usb/cdc_host/Serial.c new file mode 100644 index 0000000000000..a018b471ed2e9 --- /dev/null +++ b/shared-bindings/usb/cdc_host/Serial.c @@ -0,0 +1,274 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: 2025 rianadon +// +// SPDX-License-Identifier: MIT + +#include "py/stream.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "py/stream.h" + +#include "shared-bindings/usb/cdc_host/Serial.h" + +#include "tusb.h" +#include "class/cdc/cdc_host.h" + +//| class Serial: +//| """Receives cdc commands over USB""" +//| +//| def __init__(self, device: Device) -> None: +//| """You cannot create an instance of `usb_cdc.Serial`. +//| The available instances are in the ``usb_cdc.serials`` tuple.""" +//| ... +//| +//| def read(self, size: int = -1) -> bytes: +//| """Read at most ``size`` bytes. If ``size`` exceeds the internal buffer size, +//| only the bytes in the buffer will be read. If ``size`` is not specified or is ``-1``, +//| read as many bytes as possible, until the timeout expires. +//| If `timeout` is > 0 or ``None``, and fewer than ``size`` bytes are available, +//| keep waiting until the timeout expires or ``size`` bytes are available. +//| +//| If no bytes are read, return ``b''``. This is unlike, say, `busio.UART.read()`, which +//| would return ``None``. +//| +//| :return: Data read +//| :rtype: bytes""" +//| ... +//| +//| def readinto(self, buf: WriteableBuffer) -> int: +//| """Read bytes into the ``buf``. Read at most ``len(buf)`` bytes. If `timeout` +//| is > 0 or ``None``, keep waiting until the timeout expires or ``len(buf)`` +//| bytes are available. +//| +//| :return: number of bytes read and stored into ``buf`` +//| :rtype: int""" +//| ... +//| +//| def readline(self, size: int = -1) -> Optional[bytes]: +//| r"""Read a line ending in a newline character ("\\n"), including the newline. +//| Return everything readable if no newline is found and ``timeout`` is 0. +//| Return ``None`` in case of error. +//| +//| This is a binary stream: the newline character "\\n" cannot be changed. +//| If the host computer transmits "\\r" it will also be included as part of the line. +//| +//| :param int size: maximum number of characters to read. ``-1`` means as many as possible. +//| :return: the line read +//| :rtype: bytes or None""" +//| ... +//| +//| def readlines(self) -> List[Optional[bytes]]: +//| """Read multiple lines as a list, using `readline()`. +//| +//| .. warning:: If ``timeout`` is ``None``, +//| `readlines()` will never return, because there is no way to indicate end of stream. +//| +//| :return: a list of the line read +//| :rtype: list""" +//| ... +//| +//| def write(self, buf: ReadableBuffer) -> int: +//| """Write as many bytes as possible from the buffer of bytes. +//| +//| :return: the number of bytes written +//| :rtype: int""" +//| ... +//| +//| def flush(self) -> None: +//| """Force out any unwritten bytes, waiting until they are written.""" +//| ... +//| + +static mp_uint_t usb_host_cdc_serial_read_stream(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + byte *buf = buf_in; + + if (size == 0) { + return 0; + } + + return common_hal_usb_host_cdc_serial_read(self, buf, size, errcode); +} + +static mp_uint_t usb_host_cdc_serial_write_stream(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + const byte *buf = buf_in; + + return common_hal_usb_host_cdc_serial_write(self, buf, size, errcode); +} + +static mp_uint_t usb_host_cdc_serial_ioctl_stream(mp_obj_t self_in, mp_uint_t request, mp_uint_t arg, int *errcode) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_uint_t ret = 0; + switch (request) { + case MP_STREAM_POLL: { + mp_uint_t flags = arg; + ret = 0; + if ((flags & MP_STREAM_POLL_RD) && common_hal_usb_host_cdc_serial_get_in_waiting(self) > 0) { + ret |= MP_STREAM_POLL_RD; + } + if ((flags & MP_STREAM_POLL_WR) && common_hal_usb_host_cdc_serial_get_out_waiting(self) < CFG_TUH_CDC_TX_BUFSIZE) { + ret |= MP_STREAM_POLL_WR; + } + break; + } + + case MP_STREAM_FLUSH: + common_hal_usb_host_cdc_serial_flush(self); + ret = 0; + break; + + default: + *errcode = MP_EINVAL; + ret = MP_STREAM_ERROR; + } + return ret; +} + +// connected property +//| connected: bool +//| """True if this Serial object represents a mounted CDC device +//| and the remote device is asserting DTR (Data Terminal Ready). (read-only) +//| """ +static mp_obj_t usb_host_cdc_serial_get_connected(mp_obj_t self_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(common_hal_usb_host_cdc_serial_get_connected(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_get_connected_obj, usb_host_cdc_serial_get_connected); + +MP_PROPERTY_GETTER(usb_host_cdc_serial_connected_obj, + (mp_obj_t)&usb_host_cdc_serial_get_connected_obj); + +// in_waiting property +//| in_waiting: int +//| """Returns the number of bytes waiting to be read from the +//| CDC device's input buffer. (read-only)""" +static mp_obj_t usb_host_cdc_serial_get_in_waiting(mp_obj_t self_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(common_hal_usb_host_cdc_serial_get_in_waiting(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_get_in_waiting_obj, usb_host_cdc_serial_get_in_waiting); + +MP_PROPERTY_GETTER(usb_host_cdc_serial_in_waiting_obj, + (mp_obj_t)&usb_host_cdc_serial_get_in_waiting_obj); + +// out_waiting property +//| out_waiting: int +//| """Returns the number of bytes waiting to be written to the +//| CDC device's output buffer. (read-only)""" +static mp_obj_t usb_host_cdc_serial_get_out_waiting(mp_obj_t self_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(common_hal_usb_host_cdc_serial_get_out_waiting(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_get_out_waiting_obj, usb_host_cdc_serial_get_out_waiting); + +MP_PROPERTY_GETTER(usb_host_cdc_serial_out_waiting_obj, + (mp_obj_t)&usb_host_cdc_serial_get_out_waiting_obj); + +// reset_input_buffer method +//| def reset_input_buffer(self) -> None: +//| """Clears any unread bytes from the input buffer.""" +//| ... +static mp_obj_t usb_host_cdc_serial_reset_input_buffer(mp_obj_t self_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_usb_host_cdc_serial_reset_input_buffer(self); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_reset_input_buffer_obj, usb_host_cdc_serial_reset_input_buffer); + +// reset_output_buffer method +//| def reset_output_buffer(self) -> None: +//| """Clears any unwritten bytes from the output buffer.""" +//| ... +static mp_obj_t usb_host_cdc_serial_reset_output_buffer(mp_obj_t self_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_usb_host_cdc_serial_reset_output_buffer(self); + return mp_const_none; // Standard method returns None +} +MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_reset_output_buffer_obj, usb_host_cdc_serial_reset_output_buffer); + +// timeout property +//| timeout: Optional[float] +//| """The read timeout value in seconds. `None means wait indefinitely.//| 0 means non-blocking. Positive value is the timeout in seconds.""" +static mp_obj_t usb_host_cdc_serial_get_timeout(mp_obj_t self_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_float_t timeout = common_hal_usb_host_cdc_serial_get_timeout(self); + return (timeout < 0.0f) ? mp_const_none : mp_obj_new_float(timeout); +} +MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_get_timeout_obj, usb_host_cdc_serial_get_timeout); + +static mp_obj_t usb_host_cdc_serial_set_timeout(mp_obj_t self_in, mp_obj_t timeout_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_usb_host_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_host_cdc_serial_set_timeout_obj, usb_host_cdc_serial_set_timeout); + +MP_PROPERTY_GETSET(usb_host_cdc_serial_timeout_obj, + (mp_obj_t)&usb_host_cdc_serial_get_timeout_obj, + (mp_obj_t)&usb_host_cdc_serial_set_timeout_obj); + +// write_timeout property +//| write_timeout: Optional[float] +//| """The write timeout value in seconds. `None means wait indefinitely.//| 0 means non-blocking. Positive value is the timeout in seconds.""" +static mp_obj_t usb_host_cdc_serial_get_write_timeout(mp_obj_t self_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_float_t write_timeout = common_hal_usb_host_cdc_serial_get_write_timeout(self); + return (write_timeout < 0.0f) ? mp_const_none : mp_obj_new_float(write_timeout); +} +MP_DEFINE_CONST_FUN_OBJ_1(usb_host_cdc_serial_get_write_timeout_obj, usb_host_cdc_serial_get_write_timeout); + +static mp_obj_t usb_host_cdc_serial_set_write_timeout(mp_obj_t self_in, mp_obj_t write_timeout_in) { + usb_cdc_host_serial_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_usb_host_cdc_serial_set_write_timeout(self, + write_timeout_in == mp_const_none ? -1.0f : mp_obj_get_float(write_timeout_in)); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(usb_host_cdc_serial_set_write_timeout_obj, usb_host_cdc_serial_set_write_timeout); + +MP_PROPERTY_GETSET(usb_host_cdc_serial_write_timeout_obj, + (mp_obj_t)&usb_host_cdc_serial_get_write_timeout_obj, + (mp_obj_t)&usb_host_cdc_serial_set_write_timeout_obj); + + +static const mp_rom_map_elem_t usb_host_cdc_serial_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj)}, + { MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj)}, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + + { MP_ROM_QSTR(MP_QSTR_in_waiting), MP_ROM_PTR(&usb_host_cdc_serial_in_waiting_obj) }, + { MP_ROM_QSTR(MP_QSTR_out_waiting), MP_ROM_PTR(&usb_host_cdc_serial_out_waiting_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset_input_buffer), MP_ROM_PTR(&usb_host_cdc_serial_reset_input_buffer_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset_output_buffer), MP_ROM_PTR(&usb_host_cdc_serial_reset_output_buffer_obj) }, + { MP_ROM_QSTR(MP_QSTR_timeout), MP_ROM_PTR(&usb_host_cdc_serial_timeout_obj) }, + { MP_ROM_QSTR(MP_QSTR_write_timeout), MP_ROM_PTR(&usb_host_cdc_serial_write_timeout_obj) }, + + { MP_ROM_QSTR(MP_QSTR_connected), MP_ROM_PTR(&usb_host_cdc_serial_connected_obj) }, + + // TODO: Add baudrate, data_bits, parity, stop_bits properties/methods. +}; +static MP_DEFINE_CONST_DICT(usb_host_cdc_serial_locals_dict, usb_host_cdc_serial_locals_dict_table); + +static const mp_stream_p_t usb_host_cdc_serial_stream_p = { + .read = usb_host_cdc_serial_read_stream, + .write = usb_host_cdc_serial_write_stream, + .ioctl = usb_host_cdc_serial_ioctl_stream, + .is_text = false, + .pyserial_read_compatibility = true, + .pyserial_readinto_compatibility = true, + .pyserial_dont_return_none_compatibility = true, +}; + +MP_DEFINE_CONST_OBJ_TYPE( + usb_cdc_host_serial_type, + MP_QSTR_Serial, + MP_TYPE_FLAG_ITER_IS_ITERNEXT | MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS, + locals_dict, &usb_host_cdc_serial_locals_dict, + iter, mp_stream_unbuffered_iter, + protocol, &usb_host_cdc_serial_stream_p + ); diff --git a/shared-bindings/usb/cdc_host/Serial.h b/shared-bindings/usb/cdc_host/Serial.h new file mode 100644 index 0000000000000..ac41e6190b854 --- /dev/null +++ b/shared-bindings/usb/cdc_host/Serial.h @@ -0,0 +1,30 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: 2025 rianadon +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/usb/cdc_host/Serial.h" + +extern const mp_obj_type_t usb_cdc_host_serial_type; + +size_t common_hal_usb_host_cdc_serial_read(usb_cdc_host_serial_obj_t *self, uint8_t *data, size_t len, int *errcode); +size_t common_hal_usb_host_cdc_serial_write(usb_cdc_host_serial_obj_t *self, const uint8_t *data, size_t len, int *errcode); + +uint32_t common_hal_usb_host_cdc_serial_get_in_waiting(usb_cdc_host_serial_obj_t *self); +uint32_t common_hal_usb_host_cdc_serial_get_out_waiting(usb_cdc_host_serial_obj_t *self); + +void common_hal_usb_host_cdc_serial_reset_input_buffer(usb_cdc_host_serial_obj_t *self); +uint32_t common_hal_usb_host_cdc_serial_reset_output_buffer(usb_cdc_host_serial_obj_t *self); + +uint32_t common_hal_usb_host_cdc_serial_flush(usb_cdc_host_serial_obj_t *self); + +bool common_hal_usb_host_cdc_serial_get_connected(usb_cdc_host_serial_obj_t *self); + +mp_float_t common_hal_usb_host_cdc_serial_get_timeout(usb_cdc_host_serial_obj_t *self); +void common_hal_usb_host_cdc_serial_set_timeout(usb_cdc_host_serial_obj_t *self, mp_float_t timeout); + +mp_float_t common_hal_usb_host_cdc_serial_get_write_timeout(usb_cdc_host_serial_obj_t *self); +void common_hal_usb_host_cdc_serial_set_write_timeout(usb_cdc_host_serial_obj_t *self, mp_float_t write_timeout); diff --git a/shared-bindings/usb/cdc_host/__init__.c b/shared-bindings/usb/cdc_host/__init__.c new file mode 100644 index 0000000000000..3bcdb9ebd0260 --- /dev/null +++ b/shared-bindings/usb/cdc_host/__init__.c @@ -0,0 +1,57 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: 2025 rianadon +// +// SPDX-License-Identifier: MIT + +#include + +#include "py/obj.h" +#include "py/objmodule.h" +#include "py/runtime.h" + +#include "shared-bindings/usb/cdc_host/Serial.h" +#include "shared-bindings/usb/core/Device.h" + +#include "tusb.h" +#include "class/cdc/cdc_host.h" + +static mp_obj_t usb_cdc_host_find(mp_obj_t device_in, mp_obj_t interface_in) { + if (!mp_obj_is_type(device_in, &usb_core_device_type)) { + mp_raise_TypeError(MP_ERROR_TEXT("device must be a usb.core.Device object")); + } + + usb_core_device_obj_t *device_obj = MP_OBJ_TO_PTR(device_in); + uint8_t daddr = device_obj->device_address; + + mp_int_t interface_num = mp_obj_get_int(interface_in); + uint8_t cdc_idx = tuh_cdc_itf_get_index(daddr, (uint8_t)interface_num); + + if (cdc_idx == TUSB_INDEX_INVALID_8) { + return mp_const_none; + } + + usb_cdc_host_serial_obj_t *serial_obj = mp_obj_malloc(usb_cdc_host_serial_obj_t, &usb_cdc_host_serial_type); + serial_obj->idx = cdc_idx; + serial_obj->timeout = -1.0f; + serial_obj->write_timeout = -1.0f; + + return MP_OBJ_FROM_PTR(serial_obj); +} + +static MP_DEFINE_CONST_FUN_OBJ_2(usb_cdc_host_find_obj, usb_cdc_host_find); + +static const mp_rom_map_elem_t usb_cdc_host_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_usb_dot_cdc_host) }, + { MP_ROM_QSTR(MP_QSTR_Serial), MP_ROM_PTR(&usb_cdc_host_serial_type) }, + { MP_ROM_QSTR(MP_QSTR_find), MP_ROM_PTR(&usb_cdc_host_find_obj) }, +}; + +static MP_DEFINE_CONST_DICT(usb_cdc_host_module_globals, usb_cdc_host_module_globals_table); + +const mp_obj_module_t usb_cdc_host_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&usb_cdc_host_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_usb_dot_cdc_host, usb_cdc_host_module); diff --git a/shared-bindings/usb/cdc_host/__init__.h b/shared-bindings/usb/cdc_host/__init__.h new file mode 100644 index 0000000000000..260aec4bace2d --- /dev/null +++ b/shared-bindings/usb/cdc_host/__init__.h @@ -0,0 +1,11 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: 2025 rianadon +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "shared-module/usb/cdc_host/__init__.h" + +extern const mp_obj_module_t usb_cdc_host_module; diff --git a/shared-module/usb/cdc_host/Serial.c b/shared-module/usb/cdc_host/Serial.c new file mode 100644 index 0000000000000..089cc86d3e7c9 --- /dev/null +++ b/shared-module/usb/cdc_host/Serial.c @@ -0,0 +1,119 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: 2025 rianadon +// +// SPDX-License-Identifier: MIT + +#include "shared/runtime/interrupt_char.h" +#include "shared-bindings/usb/cdc_host/Serial.h" +#include "shared-module/usb/cdc_host/Serial.h" +#include "supervisor/shared/tick.h" + +#include "tusb.h" +#include "class/cdc/cdc_host.h" +#include "py/stream.h" + +size_t common_hal_usb_host_cdc_serial_read(usb_cdc_host_serial_obj_t *self, uint8_t *data, size_t len, int *errcode) { + if (!tuh_cdc_mounted(self->idx)) { + *errcode = ENODEV; + return 0; + } + + size_t total_read = tuh_cdc_read(self->idx, data, len); + *errcode = 0; + return total_read; +} + +size_t common_hal_usb_host_cdc_serial_write(usb_cdc_host_serial_obj_t *self, const uint8_t *data, size_t len, int *errcode) { + if (!tuh_cdc_mounted(self->idx)) { + *errcode = ENODEV; + return 0; + } + + size_t total_written = tuh_cdc_write(self->idx, data, len); + *errcode = 0; // Success + return total_written; +} + + +uint32_t common_hal_usb_host_cdc_serial_get_in_waiting(usb_cdc_host_serial_obj_t *self) { + if (!tuh_cdc_mounted(self->idx)) { + return 0; + } + return tuh_cdc_read_available(self->idx); +} + +uint32_t common_hal_usb_host_cdc_serial_get_out_waiting(usb_cdc_host_serial_obj_t *self) { + if (!tuh_cdc_mounted(self->idx)) { + return 0; + } + uint32_t available_space = tuh_cdc_write_available(self->idx); + if (available_space > CFG_TUH_CDC_TX_BUFSIZE) { + return 0; + } + return CFG_TUH_CDC_TX_BUFSIZE - available_space; +} + +void common_hal_usb_host_cdc_serial_reset_input_buffer(usb_cdc_host_serial_obj_t *self) { + if (tuh_cdc_mounted(self->idx)) { + tuh_cdc_read_clear(self->idx); + } +} + +uint32_t common_hal_usb_host_cdc_serial_reset_output_buffer(usb_cdc_host_serial_obj_t *self) { + uint32_t bytes_cleared = 0; + if (tuh_cdc_mounted(self->idx)) { + bytes_cleared = common_hal_usb_host_cdc_serial_get_out_waiting(self); + tuh_cdc_write_clear(self->idx); + } + return bytes_cleared; +} + +uint32_t common_hal_usb_host_cdc_serial_flush(usb_cdc_host_serial_obj_t *self) { + if (!tuh_cdc_mounted(self->idx)) { + return 0; + } + + uint64_t start_ticks = supervisor_ticks_ms64(); + uint64_t timeout_ticks = (self->write_timeout < 0) ? 0 : float_to_uint64(self->write_timeout * 1000); + + uint32_t initial_waiting = common_hal_usb_host_cdc_serial_get_out_waiting(self); + + while (common_hal_usb_host_cdc_serial_get_out_waiting(self) > 0) { + tuh_cdc_write_flush(self->idx); + + if (!(self->write_timeout < 0 || self->write_timeout > 0)) { + return initial_waiting - common_hal_usb_host_cdc_serial_get_out_waiting(self); + } + + if (self->write_timeout > 0) { + if (supervisor_ticks_ms64() - start_ticks >= timeout_ticks) { + return initial_waiting - common_hal_usb_host_cdc_serial_get_out_waiting(self); + } + } + + RUN_BACKGROUND_TASKS; + } + + return initial_waiting; +} + +bool common_hal_usb_host_cdc_serial_get_connected(usb_cdc_host_serial_obj_t *self) { + return tuh_cdc_mounted(self->idx) && tuh_cdc_connected(self->idx); +} + +mp_float_t common_hal_usb_host_cdc_serial_get_timeout(usb_cdc_host_serial_obj_t *self) { + return self->timeout; +} + +void common_hal_usb_host_cdc_serial_set_timeout(usb_cdc_host_serial_obj_t *self, mp_float_t timeout) { + self->timeout = timeout; +} + +mp_float_t common_hal_usb_host_cdc_serial_get_write_timeout(usb_cdc_host_serial_obj_t *self) { + return self->write_timeout; +} + +void common_hal_usb_host_cdc_serial_set_write_timeout(usb_cdc_host_serial_obj_t *self, mp_float_t write_timeout) { + self->write_timeout = write_timeout; +} diff --git a/shared-module/usb/cdc_host/Serial.h b/shared-module/usb/cdc_host/Serial.h new file mode 100644 index 0000000000000..2e6c6272ef6e6 --- /dev/null +++ b/shared-module/usb/cdc_host/Serial.h @@ -0,0 +1,16 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: 2025 rianadon +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + mp_float_t timeout; // Read timeout (s). <0 means block forever + mp_float_t write_timeout; // Write timeout (s). <0 means block forever + uint8_t idx; // TinyUSB CDC interface index +} usb_cdc_host_serial_obj_t; diff --git a/shared-module/usb/cdc_host/__init__.c b/shared-module/usb/cdc_host/__init__.c new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/shared-module/usb/cdc_host/__init__.h b/shared-module/usb/cdc_host/__init__.h new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/supervisor/shared/usb/tusb_config.h b/supervisor/shared/usb/tusb_config.h index ce39925f45b22..de9eca5501604 100644 --- a/supervisor/shared/usb/tusb_config.h +++ b/supervisor/shared/usb/tusb_config.h @@ -185,7 +185,7 @@ extern "C" { // 2 hubs so we can support "7 port" hubs which have two internal hubs. #define CFG_TUH_HUB 2 -#define CFG_TUH_CDC 0 +#define CFG_TUH_CDC 2 #define CFG_TUH_MSC 0 #define CFG_TUH_VENDOR 0 #define CFG_TUH_API_EDPT_XFER 1 diff --git a/supervisor/supervisor.mk b/supervisor/supervisor.mk index 0ecc6ea3acfe6..3eb1f3388b31d 100644 --- a/supervisor/supervisor.mk +++ b/supervisor/supervisor.mk @@ -185,6 +185,7 @@ ifeq ($(CIRCUITPY_TINYUSB),1) SRC_SUPERVISOR += \ lib/tinyusb/src/host/hub.c \ lib/tinyusb/src/host/usbh.c \ + lib/tinyusb/src/class/cdc/cdc_host.c \ endif