From 28cfd8a5135a1e420b56d693cef20fc1a4721081 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Sat, 19 Jan 2019 19:45:35 -0500 Subject: [PATCH] CharacteristicBuffer: make it be a stream class; add locking --- ports/nrf/Makefile | 1 + ports/nrf/common-hal/bleio/Characteristic.c | 2 +- .../common-hal/bleio/CharacteristicBuffer.c | 80 +++++++-- .../common-hal/bleio/CharacteristicBuffer.h | 5 +- ports/nrf/common-hal/bleio/Device.c | 2 + ports/nrf/common-hal/busio/UART.c | 39 +---- ports/nrf/sd_mutex.c | 56 ++++++ ports/nrf/sd_mutex.h | 46 +++++ py/ringbuf.h | 32 +++- py/stream.c | 2 +- shared-bindings/bleio/CharacteristicBuffer.c | 161 ++++++++++++++++-- shared-bindings/bleio/CharacteristicBuffer.h | 9 +- shared-bindings/busio/UART.c | 4 +- 13 files changed, 372 insertions(+), 67 deletions(-) create mode 100644 ports/nrf/sd_mutex.c create mode 100644 ports/nrf/sd_mutex.h diff --git a/ports/nrf/Makefile b/ports/nrf/Makefile index 2bc5084754..1b4aef0504 100755 --- a/ports/nrf/Makefile +++ b/ports/nrf/Makefile @@ -142,6 +142,7 @@ SRC_C += \ peripherals/nrf/$(MCU_CHIP)/pins.c \ peripherals/nrf/$(MCU_CHIP)/power.c \ peripherals/nrf/timers.c \ + sd_mutex.c \ supervisor/shared/memory.c diff --git a/ports/nrf/common-hal/bleio/Characteristic.c b/ports/nrf/common-hal/bleio/Characteristic.c index 0bade8d015..1db564549c 100644 --- a/ports/nrf/common-hal/bleio/Characteristic.c +++ b/ports/nrf/common-hal/bleio/Characteristic.c @@ -36,9 +36,9 @@ #include "common-hal/bleio/Characteristic.h" #include "shared-module/bleio/Characteristic.h" -// TODO - should these be per object?? ***** STATIC volatile bleio_characteristic_obj_t *m_read_characteristic; STATIC volatile uint8_t m_tx_in_progress; +// Serialize gattc writes that send a response. This might be done per object? STATIC nrf_mutex_t *m_write_mutex; STATIC uint16_t get_cccd(bleio_characteristic_obj_t *characteristic) { diff --git a/ports/nrf/common-hal/bleio/CharacteristicBuffer.c b/ports/nrf/common-hal/bleio/CharacteristicBuffer.c index 4bd3e147ff..42e45abdfb 100644 --- a/ports/nrf/common-hal/bleio/CharacteristicBuffer.c +++ b/ports/nrf/common-hal/bleio/CharacteristicBuffer.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2018 Artur Pacholec + * Copyright (c) 2019 Dan Halbert for Adafruit Industries * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,9 +29,13 @@ #include "ble_drv.h" #include "ble_gatts.h" -#include "nrf_soc.h" +#include "sd_mutex.h" +#include "lib/utils/interrupt_char.h" #include "py/runtime.h" +#include "py/stream.h" + +#include "tick.h" #include "common-hal/bleio/__init__.h" #include "common-hal/bleio/CharacteristicBuffer.h" @@ -43,10 +47,13 @@ STATIC void characteristic_buffer_on_ble_evt(ble_evt_t *ble_evt, void *param) { ble_gatts_evt_write_t *evt_write = &ble_evt->evt.gatts_evt.params.write; // Event handle must match the handle for my characteristic. if (evt_write->handle == self->characteristic->handle) { - // Push all the data onto the ring buffer. + // Push all the data onto the ring buffer, but wait for any reads to finish. + sd_mutex_acquire_wait_no_vm(&self->ringbuf_mutex); for (size_t i = 0; i < evt_write->len; i++) { ringbuf_put(&self->ringbuf, evt_write->data[i]); } + // Don't check for errors: we're in an event handler. + sd_mutex_release(&self->ringbuf_mutex); break; } } @@ -54,22 +61,75 @@ STATIC void characteristic_buffer_on_ble_evt(ble_evt_t *ble_evt, void *param) { } -// Assumes that buffer_size has been validated before call. -void common_hal_bleio_characteristic_buffer_construct(bleio_characteristic_buffer_obj_t *self, bleio_characteristic_obj_t *characteristic, size_t buffer_size) { +// Assumes that timeout and buffer_size have been validated before call. +void common_hal_bleio_characteristic_buffer_construct(bleio_characteristic_buffer_obj_t *self, + bleio_characteristic_obj_t *characteristic, + mp_float_t timeout, + size_t buffer_size) { self->characteristic = characteristic; + self->timeout_ms = timeout * 1000; // This is a macro. - ringbuf_alloc(&self->ringbuf, buffer_size); + // true means long-lived, so it won't be moved. + ringbuf_alloc(&self->ringbuf, buffer_size, true); + sd_mutex_new(&self->ringbuf_mutex); ble_drv_add_event_handler(characteristic_buffer_on_ble_evt, self); } -// Returns a uint8_t byte value, or -1 if no data is available. -int common_hal_bleio_characteristic_buffer_read(bleio_characteristic_buffer_obj_t *self) { - return ringbuf_get(&self->ringbuf); +int common_hal_bleio_characteristic_buffer_read(bleio_characteristic_buffer_obj_t *self, uint8_t *data, size_t len, int *errcode) { + uint64_t start_ticks = ticks_ms; + + // Wait for all bytes received or timeout + while ( (ringbuf_count(&self->ringbuf) < len) && (ticks_ms - start_ticks < self->timeout_ms) ) { +#ifdef MICROPY_VM_HOOK_LOOP + MICROPY_VM_HOOK_LOOP ; + // Allow user to break out of a timeout with a KeyboardInterrupt. + if ( mp_hal_is_interrupted() ) { + return 0; + } +#endif + } + + // Copy received data. Lock out writes while copying. + sd_mutex_acquire_wait(&self->ringbuf_mutex); + + size_t rx_bytes = MIN(ringbuf_count(&self->ringbuf), len); + for ( size_t i = 0; i < rx_bytes; i++ ) { + data[i] = ringbuf_get(&self->ringbuf); + } + + // Writes now OK. + sd_mutex_release_check(&self->ringbuf_mutex); + + return rx_bytes; +} + +uint32_t common_hal_bleio_characteristic_buffer_rx_characters_available(bleio_characteristic_buffer_obj_t *self) { + return ringbuf_count(&self->ringbuf); +} + +void common_hal_bleio_characteristic_buffer_clear_rx_buffer(bleio_characteristic_buffer_obj_t *self) { + // prevent conflict with uart irq + sd_mutex_acquire_wait(&self->ringbuf_mutex); + ringbuf_clear(&self->ringbuf); + sd_mutex_release_check(&self->ringbuf_mutex); +} + +bool common_hal_bleio_characteristic_buffer_deinited(bleio_characteristic_buffer_obj_t *self) { + return self->characteristic == NULL; } void common_hal_bleio_characteristic_buffer_deinit(bleio_characteristic_buffer_obj_t *self) { - ble_drv_remove_event_handler(characteristic_buffer_on_ble_evt, self); + if (!common_hal_bleio_characteristic_buffer_deinited(self)) { + ble_drv_remove_event_handler(characteristic_buffer_on_ble_evt, self); + } +} + +bool common_hal_bleio_characteristic_buffer_connected(bleio_characteristic_buffer_obj_t *self) { + return self->characteristic != NULL && + self->characteristic->service != NULL && + self->characteristic->service->device != NULL && + common_hal_bleio_device_get_conn_handle(self->characteristic->service->device) != BLE_CONN_HANDLE_INVALID; } diff --git a/ports/nrf/common-hal/bleio/CharacteristicBuffer.h b/ports/nrf/common-hal/bleio/CharacteristicBuffer.h index f9ff7dd66d..f5c7151547 100644 --- a/ports/nrf/common-hal/bleio/CharacteristicBuffer.h +++ b/ports/nrf/common-hal/bleio/CharacteristicBuffer.h @@ -27,15 +27,18 @@ #ifndef MICROPY_INCLUDED_COMMON_HAL_BLEIO_CHARACTERISTICBUFFER_H #define MICROPY_INCLUDED_COMMON_HAL_BLEIO_CHARACTERISTICBUFFER_H -#include "py/ringbuf.h" +#include "nrf_soc.h" +#include "py/ringbuf.h" #include "shared-bindings/bleio/Characteristic.h" typedef struct { mp_obj_base_t base; bleio_characteristic_obj_t *characteristic; + uint32_t timeout_ms; // Ring buffer storing consecutive incoming values. ringbuf_t ringbuf; + nrf_mutex_t ringbuf_mutex; } bleio_characteristic_buffer_obj_t; #endif // MICROPY_INCLUDED_COMMON_HAL_BLEIO_CHARACTERISTICBUFFER_H diff --git a/ports/nrf/common-hal/bleio/Device.c b/ports/nrf/common-hal/bleio/Device.c index 50c738abee..5f625829e4 100644 --- a/ports/nrf/common-hal/bleio/Device.c +++ b/ports/nrf/common-hal/bleio/Device.c @@ -262,11 +262,13 @@ STATIC bool discover_services(bleio_device_obj_t *device, uint16_t start_handle) mp_raise_OSError_msg(translate("Failed to discover services")); } + // Serialize discovery. err_code = sd_mutex_acquire(m_discovery_mutex); if (err_code != NRF_SUCCESS) { mp_raise_OSError_msg(translate("Failed to acquire mutex")); } + // Wait for someone else to release m_discovery_mutex. while (sd_mutex_acquire(m_discovery_mutex) == NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN) { #ifdef MICROPY_VM_HOOK_LOOP MICROPY_VM_HOOK_LOOP diff --git a/ports/nrf/common-hal/busio/UART.c b/ports/nrf/common-hal/busio/UART.c index 912956369c..43868f5065 100644 --- a/ports/nrf/common-hal/busio/UART.c +++ b/ports/nrf/common-hal/busio/UART.c @@ -52,33 +52,6 @@ static uint32_t get_nrf_baud (uint32_t baudrate); -static uint16_t ringbuf_count(ringbuf_t *r) -{ - volatile int count = r->iput - r->iget; - if ( count < 0 ) { - count += r->size; - } - - return (uint16_t) count; -} - -static void ringbuf_clear(ringbuf_t *r) -{ - r->iput = r->iget = 0; -} - -// will overwrite old data -static void ringbuf_put_n(ringbuf_t* r, uint8_t* buf, uint8_t bufsize) -{ - for(uint8_t i=0; i < bufsize; i++) { - if ( ringbuf_put(r, buf[i]) < 0 ) { - // if full overwrite old data - (void) ringbuf_get(r); - ringbuf_put(r, buf[i]); - } - } -} - static void uart_callback_irq (const nrfx_uarte_event_t * event, void * context) { busio_uart_obj_t* self = (busio_uart_obj_t*) context; @@ -145,16 +118,20 @@ void common_hal_busio_uart_construct (busio_uart_obj_t *self, // Init buffer for rx if ( rx != mp_const_none ) { - self->rbuf.buf = (uint8_t *) gc_alloc(receiver_buffer_size, false, false); + // Initially allocate the UART's buffer in the long-lived part of the + // heap. UARTs are generally long-lived objects, but the "make long- + // lived" machinery is incapable of moving internal pointers like + // self->buffer, so do it manually. (However, as long as internal + // pointers like this are NOT moved, allocating the buffer + // in the long-lived pool is not strictly necessary) + // (This is a macro.) + ringbuf_alloc(&self->rbuf, receiver_buffer_size, true); if ( !self->rbuf.buf ) { nrfx_uarte_uninit(&self->uarte); mp_raise_msg(&mp_type_MemoryError, translate("Failed to allocate RX buffer")); } - self->rbuf.size = receiver_buffer_size; - self->rbuf.iget = self->rbuf.iput = 0; - self->rx_pin_number = rx->number; claim_pin(rx); } diff --git a/ports/nrf/sd_mutex.c b/ports/nrf/sd_mutex.c new file mode 100644 index 0000000000..7682ffa623 --- /dev/null +++ b/ports/nrf/sd_mutex.c @@ -0,0 +1,56 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Dan Halbert for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/mpconfig.h" +#include "py/runtime.h" +#include "nrf_soc.h" + +void sd_mutex_acquire_check(nrf_mutex_t* p_mutex) { + uint32_t err_code = sd_mutex_acquire(p_mutex); + if (err_code != NRF_SUCCESS) { + mp_raise_OSError_msg_varg(translate("Failed to acquire mutex, err 0x%04x"), err_code); + } +} + +void sd_mutex_acquire_wait(nrf_mutex_t* p_mutex) { + while (sd_mutex_acquire(p_mutex) == NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN) { +#ifdef MICROPY_VM_HOOK_LOOP + MICROPY_VM_HOOK_LOOP +#endif + } +} + +void sd_mutex_acquire_wait_no_vm(nrf_mutex_t* p_mutex) { + while (sd_mutex_acquire(p_mutex) == NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN) { + } +} + +void sd_mutex_release_check(nrf_mutex_t* p_mutex) { + uint32_t err_code = sd_mutex_release(p_mutex); + if (err_code != NRF_SUCCESS) { + mp_raise_OSError_msg_varg(translate("Failed to release mutex, err 0x%04x"), err_code); + } +} diff --git a/ports/nrf/sd_mutex.h b/ports/nrf/sd_mutex.h new file mode 100644 index 0000000000..ca46917205 --- /dev/null +++ b/ports/nrf/sd_mutex.h @@ -0,0 +1,46 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Dan Halbert for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_NRF_SD_MUTEX_H +#define MICROPY_INCLUDED_NRF_SD_MUTEX_H + +#include "nrf_soc.h" + +// Helpers for common usage of nrf_mutex. + +// Try to acquire a mutex right now. Raise exception if we can't get it. +void sd_mutex_acquire_check(nrf_mutex_t* p_mutex); + +// Wait for a mutex to become available. Run VM background tasks while waiting. +void sd_mutex_acquire_wait(nrf_mutex_t* p_mutex); + +// Wait for a mutex to become available.. Block VM while waiting. +void sd_mutex_acquire_wait_no_vm(nrf_mutex_t* p_mutex); + +// Release a mutex, and raise exception on error. +void sd_mutex_release_check(nrf_mutex_t* p_mutex); + +#endif // MICROPY_INCLUDED_NRF_SD_MUTEX_H diff --git a/py/ringbuf.h b/py/ringbuf.h index c62e20e1db..5f82cc0968 100644 --- a/py/ringbuf.h +++ b/py/ringbuf.h @@ -26,6 +26,8 @@ #ifndef MICROPY_INCLUDED_PY_RINGBUF_H #define MICROPY_INCLUDED_PY_RINGBUF_H +#include "py/gc.h" + #include typedef struct _ringbuf_t { @@ -40,9 +42,9 @@ typedef struct _ringbuf_t { // ringbuf_t buf = {buf_array, sizeof(buf_array)}; // Dynamic initialization. This creates root pointer! -#define ringbuf_alloc(r, sz) \ +#define ringbuf_alloc(r, sz, long_lived) \ { \ - (r)->buf = m_new(uint8_t, sz); \ + (r)->buf = gc_alloc(sz, false, long_lived); \ (r)->size = sz; \ (r)->iget = (r)->iput = 0; \ } @@ -71,4 +73,30 @@ static inline int ringbuf_put(ringbuf_t *r, uint8_t v) { return 0; } +static inline uint16_t ringbuf_count(ringbuf_t *r) +{ + volatile int count = r->iput - r->iget; + if ( count < 0 ) { + count += r->size; + } + + return (uint16_t) count; +} + +static inline void ringbuf_clear(ringbuf_t *r) +{ + r->iput = r->iget = 0; +} + +// will overwrite old data +static inline void ringbuf_put_n(ringbuf_t* r, uint8_t* buf, uint8_t bufsize) +{ + for(uint8_t i=0; i < bufsize; i++) { + if ( ringbuf_put(r, buf[i]) < 0 ) { + // if full overwrite old data + (void) ringbuf_get(r); + ringbuf_put(r, buf[i]); + } + } +} #endif // MICROPY_INCLUDED_PY_RINGBUF_H diff --git a/py/stream.c b/py/stream.c index aca9b84607..9d8be445c5 100644 --- a/py/stream.c +++ b/py/stream.c @@ -103,7 +103,7 @@ STATIC mp_obj_t stream_read_generic(size_t n_args, const mp_obj_t *args, byte fl // CPython does a readall, but here we silently let negatives through, // and they will cause a MemoryError. mp_int_t sz; - if (n_args == 1 || ((sz = mp_obj_get_int(args[1])) == -1)) { + if (n_args == 1 || args[1] == mp_const_none || ((sz = mp_obj_get_int(args[1])) == -1)) { return stream_readall(args[0]); } diff --git a/shared-bindings/bleio/CharacteristicBuffer.c b/shared-bindings/bleio/CharacteristicBuffer.c index 2bb4448c4a..32629ca193 100644 --- a/shared-bindings/bleio/CharacteristicBuffer.c +++ b/shared-bindings/bleio/CharacteristicBuffer.c @@ -24,10 +24,21 @@ * THE SOFTWARE. */ +#include "py/mperrno.h" +#include "py/ioctl.h" #include "py/objproperty.h" #include "py/runtime.h" +#include "py/stream.h" + #include "shared-bindings/bleio/CharacteristicBuffer.h" #include "shared-bindings/bleio/UUID.h" +#include "shared-bindings/util.h" + +STATIC void raise_error_if_not_connected(bleio_characteristic_buffer_obj_t *self) { + if (!common_hal_bleio_characteristic_buffer_connected(self)) { + mp_raise_ValueError(translate("Not connected")); + } +} //| .. currentmodule:: bleio //| @@ -36,27 +47,34 @@ //| //| Accumulates a Characteristic's incoming values in a FIFO buffer. //| -//| .. class:: CharacteristicBuffer(Characteristic, buffer_size=0) +//| .. class:: CharacteristicBuffer(Characteristic, *, timeout=1, buffer_size=64) //| //| Create a new Characteristic object identified by the specified UUID. //| //| :param bleio.Characteristic characteristic: The characteristic to monitor +//| :param int timeout: the timeout in seconds to wait for the first character and between subsequent characters.//| //| :param int buffer_size: Size of ring buffer that stores incoming data coming from client. //| Must be >= 1. //| STATIC mp_obj_t bleio_characteristic_buffer_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_characteristic, ARG_buffer_size, }; + enum { ARG_characteristic, ARG_timeout, ARG_buffer_size, }; static const mp_arg_t allowed_args[] = { { MP_QSTR_characteristic, MP_ARG_REQUIRED | MP_ARG_OBJ }, - { MP_QSTR_buffer_size, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(1)} }, + { MP_QSTR_buffer_size, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 64} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); const mp_obj_t characteristic = args[ARG_characteristic].u_obj; - const int buffer_size = args[ARG_buffer_size].u_int; + mp_float_t timeout = mp_obj_get_float(args[ARG_timeout].u_obj); + if (timeout < 0.0f) { + mp_raise_ValueError(translate("timeout must be >= 0.0")); + } + + const int buffer_size = args[ARG_buffer_size].u_int; if (buffer_size < 1) { mp_raise_ValueError(translate("buffer_size must be >= 1")); } @@ -69,30 +87,117 @@ STATIC mp_obj_t bleio_characteristic_buffer_make_new(const mp_obj_type_t *type, self->base.type = &bleio_characteristic_buffer_type; self->characteristic = MP_OBJ_TO_PTR(characteristic); - common_hal_bleio_characteristic_buffer_construct(self, self->characteristic, buffer_size); + common_hal_bleio_characteristic_buffer_construct(self, self->characteristic, timeout, buffer_size); return MP_OBJ_FROM_PTR(self); } - -//| .. method:: read() +// These are standard stream methods. Code is in py/stream.c. +// +//| .. method:: read(nbytes=None) +//| +//| Read characters. If ``nbytes`` is specified then read at most that many +//| bytes. Otherwise, read everything that arrives until the connection +//| times out. Providing the number of bytes expected is highly recommended +//| because it will be faster. +//| +//| :return: Data read +//| :rtype: bytes or None +//| +//| .. method:: readinto(buf) +//| +//| Read bytes into the ``buf``. Read at most ``len(buf)`` bytes. +//| +//| :return: number of bytes read and stored into ``buf`` +//| :rtype: int or None (on a non-blocking error) +//| +//| .. method:: readline() +//| +//| Read a line, ending in a newline character. +//| +//| :return: the line read +//| :rtype: int or None //| -//| Read a single byte from the buffer. If no character is available, return None. -STATIC mp_obj_t bleio_characteristic_buffer_read(mp_obj_t self_in) { - bleio_characteristic_buffer_obj_t *self = MP_OBJ_TO_PTR(self_in); - int byte = common_hal_bleio_characteristic_buffer_read(self); - if (byte == -1) { - return mp_const_none; +// These three methods are used by the shared stream methods. +STATIC mp_uint_t bleio_characteristic_buffer_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { + bleio_characteristic_buffer_obj_t *self = MP_OBJ_TO_PTR(self_in); + raise_error_if_deinited(common_hal_bleio_characteristic_buffer_deinited(self)); + raise_error_if_not_connected(self); + byte *buf = buf_in; + + // make sure we want at least 1 char + if (size == 0) { + return 0; } - return MP_OBJ_NEW_SMALL_INT(byte); + return common_hal_bleio_characteristic_buffer_read(self, buf, size, errcode); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(bleio_characteristic_buffer_read_obj, bleio_characteristic_buffer_read); + +STATIC mp_uint_t bleio_characteristic_buffer_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) { + mp_raise_NotImplementedError(translate("CharacteristicBuffer writing not provided")); + return 0; +} + +STATIC mp_uint_t bleio_characteristic_buffer_ioctl(mp_obj_t self_in, mp_uint_t request, mp_uint_t arg, int *errcode) { + bleio_characteristic_buffer_obj_t *self = MP_OBJ_TO_PTR(self_in); + raise_error_if_deinited(common_hal_bleio_characteristic_buffer_deinited(self)); + raise_error_if_not_connected(self); + if (!common_hal_bleio_characteristic_buffer_connected(self)) { + mp_raise_ValueError(translate("Not connected.")); + } + mp_uint_t ret; + if (request == MP_IOCTL_POLL) { + mp_uint_t flags = arg; + ret = 0; + if ((flags & MP_IOCTL_POLL_RD) && common_hal_bleio_characteristic_buffer_rx_characters_available(self) > 0) { + ret |= MP_IOCTL_POLL_RD; + } +// No writing provided. +// if ((flags & MP_IOCTL_POLL_WR) && common_hal_busio_uart_ready_to_tx(self)) { +// ret |= MP_IOCTL_POLL_WR; +// } + } else { + *errcode = MP_EINVAL; + ret = MP_STREAM_ERROR; + } + return ret; +} + +//| .. attribute:: in_waiting +//| +//| The number of bytes in the input buffer, available to be read +//| +STATIC mp_obj_t bleio_characteristic_buffer_obj_get_in_waiting(mp_obj_t self_in) { + bleio_characteristic_buffer_obj_t *self = MP_OBJ_TO_PTR(self_in); + raise_error_if_deinited(common_hal_bleio_characteristic_buffer_deinited(self)); + return MP_OBJ_NEW_SMALL_INT(common_hal_bleio_characteristic_buffer_rx_characters_available(self)); +} +MP_DEFINE_CONST_FUN_OBJ_1(bleio_characteristic_buffer_get_in_waiting_obj, bleio_characteristic_buffer_obj_get_in_waiting); + +const mp_obj_property_t bleio_characteristic_buffer_in_waiting_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&bleio_characteristic_buffer_get_in_waiting_obj, + (mp_obj_t)&mp_const_none_obj, + (mp_obj_t)&mp_const_none_obj}, +}; + +//| .. method:: reset_input_buffer() +//| +//| Discard any unread characters in the input buffer. +//| +STATIC mp_obj_t bleio_characteristic_buffer_obj_reset_input_buffer(mp_obj_t self_in) { + bleio_characteristic_buffer_obj_t *self = MP_OBJ_TO_PTR(self_in); + raise_error_if_deinited(common_hal_bleio_characteristic_buffer_deinited(self)); + common_hal_bleio_characteristic_buffer_clear_rx_buffer(self); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(bleio_characteristic_buffer_reset_input_buffer_obj, bleio_characteristic_buffer_obj_reset_input_buffer); //| .. method:: deinit() //| //| Disable permanently. +//| STATIC mp_obj_t bleio_characteristic_buffer_deinit(mp_obj_t self_in) { bleio_characteristic_buffer_obj_t *self = MP_OBJ_TO_PTR(self_in); common_hal_bleio_characteristic_buffer_deinit(self); @@ -101,15 +206,39 @@ STATIC mp_obj_t bleio_characteristic_buffer_deinit(mp_obj_t self_in) { STATIC MP_DEFINE_CONST_FUN_OBJ_1(bleio_characteristic_buffer_deinit_obj, bleio_characteristic_buffer_deinit); STATIC const mp_rom_map_elem_t bleio_characteristic_buffer_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&bleio_characteristic_buffer_read_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&bleio_characteristic_buffer_deinit_obj) }, + + // Standard stream methods. + { MP_OBJ_NEW_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, + // CharacteristicBuffer is currently read-only. + // { MP_OBJ_NEW_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + + { MP_OBJ_NEW_QSTR(MP_QSTR_reset_input_buffer), MP_ROM_PTR(&bleio_characteristic_buffer_reset_input_buffer_obj) }, + // Properties + { MP_ROM_QSTR(MP_QSTR_in_waiting), MP_ROM_PTR(&bleio_characteristic_buffer_in_waiting_obj) }, + }; STATIC MP_DEFINE_CONST_DICT(bleio_characteristic_buffer_locals_dict, bleio_characteristic_buffer_locals_dict_table); +STATIC const mp_stream_p_t characteristic_buffer_stream_p = { + .read = bleio_characteristic_buffer_read, + .write = bleio_characteristic_buffer_write, + .ioctl = bleio_characteristic_buffer_ioctl, + .is_text = false, + // Match PySerial when possible, such as disallowing optional length argument for .readinto() + .pyserial_compatibility = true, +}; + + const mp_obj_type_t bleio_characteristic_buffer_type = { { &mp_type_type }, .name = MP_QSTR_CharacteristicBuffer, .make_new = bleio_characteristic_buffer_make_new, + .getiter = mp_identity_getiter, + .iternext = mp_stream_unbuffered_iter, + .protocol = &characteristic_buffer_stream_p, .locals_dict = (mp_obj_dict_t*)&bleio_characteristic_buffer_locals_dict }; diff --git a/shared-bindings/bleio/CharacteristicBuffer.h b/shared-bindings/bleio/CharacteristicBuffer.h index c5d7580da3..f25017c19e 100644 --- a/shared-bindings/bleio/CharacteristicBuffer.h +++ b/shared-bindings/bleio/CharacteristicBuffer.h @@ -31,9 +31,12 @@ extern const mp_obj_type_t bleio_characteristic_buffer_type; -extern void common_hal_bleio_characteristic_buffer_construct(bleio_characteristic_buffer_obj_t *self, bleio_characteristic_obj_t *characteristic, size_t buffer_size); -// Returns a uint8_t byte value, or -1 if no data is available. -int common_hal_bleio_characteristic_buffer_read(bleio_characteristic_buffer_obj_t *self); +extern void common_hal_bleio_characteristic_buffer_construct(bleio_characteristic_buffer_obj_t *self, bleio_characteristic_obj_t *characteristic, mp_float_t timeout, size_t buffer_size); +int common_hal_bleio_characteristic_buffer_read(bleio_characteristic_buffer_obj_t *self, uint8_t *data, size_t len, int *errcode); +uint32_t common_hal_bleio_characteristic_buffer_rx_characters_available(bleio_characteristic_buffer_obj_t *self); +void common_hal_bleio_characteristic_buffer_clear_rx_buffer(bleio_characteristic_buffer_obj_t *self); +bool common_hal_bleio_characteristic_buffer_deinited(bleio_characteristic_buffer_obj_t *self); int common_hal_bleio_characteristic_buffer_deinit(bleio_characteristic_buffer_obj_t *self); +bool common_hal_bleio_characteristic_buffer_connected(bleio_characteristic_buffer_obj_t *self); #endif // MICROPY_INCLUDED_SHARED_BINDINGS_BLEIO_CHARACTERISTICBUFFER_H diff --git a/shared-bindings/busio/UART.c b/shared-bindings/busio/UART.c index cd0ca3f360..85bf498a48 100644 --- a/shared-bindings/busio/UART.c +++ b/shared-bindings/busio/UART.c @@ -62,7 +62,7 @@ //| //| *New in CircuitPython 4.0:* ``timeout`` has incompatibly changed units from milliseconds to seconds. //| The new upper limit on ``timeout`` is meant to catch mistaken use of milliseconds. - +//| typedef struct { mp_obj_base_t base; } busio_uart_parity_obj_t; @@ -172,7 +172,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(busio_uart___exit___obj, 4, 4, busio_ //| Read bytes into the ``buf``. Read at most ``len(buf)`` bytes. //| //| :return: number of bytes read and stored into ``buf`` -//| :rtype: bytes or None +//| :rtype: int or None (on a non-blocking error) //| //| *New in CircuitPython 4.0:* No length parameter is permitted.