CharacteristicBuffer: make it be a stream class; add locking
This commit is contained in:
parent
7a33e588d4
commit
28cfd8a513
@ -142,6 +142,7 @@ SRC_C += \
|
|||||||
peripherals/nrf/$(MCU_CHIP)/pins.c \
|
peripherals/nrf/$(MCU_CHIP)/pins.c \
|
||||||
peripherals/nrf/$(MCU_CHIP)/power.c \
|
peripherals/nrf/$(MCU_CHIP)/power.c \
|
||||||
peripherals/nrf/timers.c \
|
peripherals/nrf/timers.c \
|
||||||
|
sd_mutex.c \
|
||||||
supervisor/shared/memory.c
|
supervisor/shared/memory.c
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,9 +36,9 @@
|
|||||||
#include "common-hal/bleio/Characteristic.h"
|
#include "common-hal/bleio/Characteristic.h"
|
||||||
#include "shared-module/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 bleio_characteristic_obj_t *m_read_characteristic;
|
||||||
STATIC volatile uint8_t m_tx_in_progress;
|
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 nrf_mutex_t *m_write_mutex;
|
||||||
|
|
||||||
STATIC uint16_t get_cccd(bleio_characteristic_obj_t *characteristic) {
|
STATIC uint16_t get_cccd(bleio_characteristic_obj_t *characteristic) {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* The MIT License (MIT)
|
* 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
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -29,9 +29,13 @@
|
|||||||
|
|
||||||
#include "ble_drv.h"
|
#include "ble_drv.h"
|
||||||
#include "ble_gatts.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/runtime.h"
|
||||||
|
#include "py/stream.h"
|
||||||
|
|
||||||
|
#include "tick.h"
|
||||||
|
|
||||||
#include "common-hal/bleio/__init__.h"
|
#include "common-hal/bleio/__init__.h"
|
||||||
#include "common-hal/bleio/CharacteristicBuffer.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;
|
ble_gatts_evt_write_t *evt_write = &ble_evt->evt.gatts_evt.params.write;
|
||||||
// Event handle must match the handle for my characteristic.
|
// Event handle must match the handle for my characteristic.
|
||||||
if (evt_write->handle == self->characteristic->handle) {
|
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++) {
|
for (size_t i = 0; i < evt_write->len; i++) {
|
||||||
ringbuf_put(&self->ringbuf, evt_write->data[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;
|
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.
|
// 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, size_t buffer_size) {
|
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->characteristic = characteristic;
|
||||||
|
self->timeout_ms = timeout * 1000;
|
||||||
// This is a macro.
|
// 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);
|
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, uint8_t *data, size_t len, int *errcode) {
|
||||||
int common_hal_bleio_characteristic_buffer_read(bleio_characteristic_buffer_obj_t *self) {
|
uint64_t start_ticks = ticks_ms;
|
||||||
return ringbuf_get(&self->ringbuf);
|
|
||||||
|
// 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) {
|
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;
|
||||||
}
|
}
|
||||||
|
@ -27,15 +27,18 @@
|
|||||||
#ifndef MICROPY_INCLUDED_COMMON_HAL_BLEIO_CHARACTERISTICBUFFER_H
|
#ifndef MICROPY_INCLUDED_COMMON_HAL_BLEIO_CHARACTERISTICBUFFER_H
|
||||||
#define 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"
|
#include "shared-bindings/bleio/Characteristic.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
mp_obj_base_t base;
|
mp_obj_base_t base;
|
||||||
bleio_characteristic_obj_t *characteristic;
|
bleio_characteristic_obj_t *characteristic;
|
||||||
|
uint32_t timeout_ms;
|
||||||
// Ring buffer storing consecutive incoming values.
|
// Ring buffer storing consecutive incoming values.
|
||||||
ringbuf_t ringbuf;
|
ringbuf_t ringbuf;
|
||||||
|
nrf_mutex_t ringbuf_mutex;
|
||||||
} bleio_characteristic_buffer_obj_t;
|
} bleio_characteristic_buffer_obj_t;
|
||||||
|
|
||||||
#endif // MICROPY_INCLUDED_COMMON_HAL_BLEIO_CHARACTERISTICBUFFER_H
|
#endif // MICROPY_INCLUDED_COMMON_HAL_BLEIO_CHARACTERISTICBUFFER_H
|
||||||
|
@ -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"));
|
mp_raise_OSError_msg(translate("Failed to discover services"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Serialize discovery.
|
||||||
err_code = sd_mutex_acquire(m_discovery_mutex);
|
err_code = sd_mutex_acquire(m_discovery_mutex);
|
||||||
if (err_code != NRF_SUCCESS) {
|
if (err_code != NRF_SUCCESS) {
|
||||||
mp_raise_OSError_msg(translate("Failed to acquire mutex"));
|
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) {
|
while (sd_mutex_acquire(m_discovery_mutex) == NRF_ERROR_SOC_MUTEX_ALREADY_TAKEN) {
|
||||||
#ifdef MICROPY_VM_HOOK_LOOP
|
#ifdef MICROPY_VM_HOOK_LOOP
|
||||||
MICROPY_VM_HOOK_LOOP
|
MICROPY_VM_HOOK_LOOP
|
||||||
|
@ -52,33 +52,6 @@
|
|||||||
|
|
||||||
static uint32_t get_nrf_baud (uint32_t baudrate);
|
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) {
|
static void uart_callback_irq (const nrfx_uarte_event_t * event, void * context) {
|
||||||
busio_uart_obj_t* self = (busio_uart_obj_t*) 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
|
// Init buffer for rx
|
||||||
if ( rx != mp_const_none ) {
|
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 ) {
|
if ( !self->rbuf.buf ) {
|
||||||
nrfx_uarte_uninit(&self->uarte);
|
nrfx_uarte_uninit(&self->uarte);
|
||||||
mp_raise_msg(&mp_type_MemoryError, translate("Failed to allocate RX buffer"));
|
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;
|
self->rx_pin_number = rx->number;
|
||||||
claim_pin(rx);
|
claim_pin(rx);
|
||||||
}
|
}
|
||||||
|
56
ports/nrf/sd_mutex.c
Normal file
56
ports/nrf/sd_mutex.c
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
46
ports/nrf/sd_mutex.h
Normal file
46
ports/nrf/sd_mutex.h
Normal file
@ -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
|
32
py/ringbuf.h
32
py/ringbuf.h
@ -26,6 +26,8 @@
|
|||||||
#ifndef MICROPY_INCLUDED_PY_RINGBUF_H
|
#ifndef MICROPY_INCLUDED_PY_RINGBUF_H
|
||||||
#define MICROPY_INCLUDED_PY_RINGBUF_H
|
#define MICROPY_INCLUDED_PY_RINGBUF_H
|
||||||
|
|
||||||
|
#include "py/gc.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
typedef struct _ringbuf_t {
|
typedef struct _ringbuf_t {
|
||||||
@ -40,9 +42,9 @@ typedef struct _ringbuf_t {
|
|||||||
// ringbuf_t buf = {buf_array, sizeof(buf_array)};
|
// ringbuf_t buf = {buf_array, sizeof(buf_array)};
|
||||||
|
|
||||||
// Dynamic initialization. This creates root pointer!
|
// 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)->size = sz; \
|
||||||
(r)->iget = (r)->iput = 0; \
|
(r)->iget = (r)->iput = 0; \
|
||||||
}
|
}
|
||||||
@ -71,4 +73,30 @@ static inline int ringbuf_put(ringbuf_t *r, uint8_t v) {
|
|||||||
return 0;
|
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
|
#endif // MICROPY_INCLUDED_PY_RINGBUF_H
|
||||||
|
@ -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,
|
// CPython does a readall, but here we silently let negatives through,
|
||||||
// and they will cause a MemoryError.
|
// and they will cause a MemoryError.
|
||||||
mp_int_t sz;
|
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]);
|
return stream_readall(args[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,10 +24,21 @@
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "py/mperrno.h"
|
||||||
|
#include "py/ioctl.h"
|
||||||
#include "py/objproperty.h"
|
#include "py/objproperty.h"
|
||||||
#include "py/runtime.h"
|
#include "py/runtime.h"
|
||||||
|
#include "py/stream.h"
|
||||||
|
|
||||||
#include "shared-bindings/bleio/CharacteristicBuffer.h"
|
#include "shared-bindings/bleio/CharacteristicBuffer.h"
|
||||||
#include "shared-bindings/bleio/UUID.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
|
//| .. currentmodule:: bleio
|
||||||
//|
|
//|
|
||||||
@ -36,27 +47,34 @@
|
|||||||
//|
|
//|
|
||||||
//| Accumulates a Characteristic's incoming values in a FIFO buffer.
|
//| 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.
|
//| Create a new Characteristic object identified by the specified UUID.
|
||||||
//|
|
//|
|
||||||
//| :param bleio.Characteristic characteristic: The characteristic to monitor
|
//| :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.
|
//| :param int buffer_size: Size of ring buffer that stores incoming data coming from client.
|
||||||
//| Must be >= 1.
|
//| 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) {
|
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[] = {
|
static const mp_arg_t allowed_args[] = {
|
||||||
{ MP_QSTR_characteristic, MP_ARG_REQUIRED | MP_ARG_OBJ },
|
{ 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_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);
|
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 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) {
|
if (buffer_size < 1) {
|
||||||
mp_raise_ValueError(translate("buffer_size must be >= 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->base.type = &bleio_characteristic_buffer_type;
|
||||||
self->characteristic = MP_OBJ_TO_PTR(characteristic);
|
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);
|
return MP_OBJ_FROM_PTR(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These are standard stream methods. Code is in py/stream.c.
|
||||||
//| .. method:: read()
|
//
|
||||||
|
//| .. 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);
|
// These three methods are used by the shared stream methods.
|
||||||
if (byte == -1) {
|
STATIC mp_uint_t bleio_characteristic_buffer_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) {
|
||||||
return mp_const_none;
|
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()
|
//| .. method:: deinit()
|
||||||
//|
|
//|
|
||||||
//| Disable permanently.
|
//| Disable permanently.
|
||||||
|
//|
|
||||||
STATIC mp_obj_t bleio_characteristic_buffer_deinit(mp_obj_t self_in) {
|
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);
|
bleio_characteristic_buffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||||
common_hal_bleio_characteristic_buffer_deinit(self);
|
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 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[] = {
|
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) },
|
{ 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 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 = {
|
const mp_obj_type_t bleio_characteristic_buffer_type = {
|
||||||
{ &mp_type_type },
|
{ &mp_type_type },
|
||||||
.name = MP_QSTR_CharacteristicBuffer,
|
.name = MP_QSTR_CharacteristicBuffer,
|
||||||
.make_new = bleio_characteristic_buffer_make_new,
|
.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
|
.locals_dict = (mp_obj_dict_t*)&bleio_characteristic_buffer_locals_dict
|
||||||
};
|
};
|
||||||
|
@ -31,9 +31,12 @@
|
|||||||
|
|
||||||
extern const mp_obj_type_t bleio_characteristic_buffer_type;
|
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);
|
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);
|
||||||
// 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, uint8_t *data, size_t len, int *errcode);
|
||||||
int common_hal_bleio_characteristic_buffer_read(bleio_characteristic_buffer_obj_t *self);
|
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);
|
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
|
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_BLEIO_CHARACTERISTICBUFFER_H
|
||||||
|
@ -62,7 +62,7 @@
|
|||||||
//|
|
//|
|
||||||
//| *New in CircuitPython 4.0:* ``timeout`` has incompatibly changed units from milliseconds to seconds.
|
//| *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.
|
//| The new upper limit on ``timeout`` is meant to catch mistaken use of milliseconds.
|
||||||
|
//|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
mp_obj_base_t base;
|
mp_obj_base_t base;
|
||||||
} busio_uart_parity_obj_t;
|
} 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.
|
//| Read bytes into the ``buf``. 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: int or None (on a non-blocking error)
|
||||||
//|
|
//|
|
||||||
//| *New in CircuitPython 4.0:* No length parameter is permitted.
|
//| *New in CircuitPython 4.0:* No length parameter is permitted.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user