Add PacketBuffer and MTU negotiation support.

PacketBuffer facilitates packet oriented BLE protocols such as BLE
MIDI and the Apple Media Service.

This also adds PHY, MTU and connection event extension negotiation
to speed up data transfer when possible.
This commit is contained in:
Scott Shawcroft 2019-12-06 12:27:46 -08:00
parent 776c9b011c
commit 82fb761c0f
No known key found for this signature in database
GPG Key ID: 9349BC7E64B1921E
14 changed files with 726 additions and 23 deletions

View File

@ -46,7 +46,7 @@ nrf_nvic_state_t nrf_nvic_state = { 0 };
volatile sd_flash_operation_status_t sd_flash_operation_status;
__attribute__((aligned(4)))
static uint8_t m_ble_evt_buf[sizeof(ble_evt_t) + (BLE_GATT_ATT_MTU_DEFAULT)];
static uint8_t m_ble_evt_buf[sizeof(ble_evt_t) + (BLE_GATTS_VAR_ATTR_LEN_MAX)];
void ble_drv_reset() {
// Linked list items will be gc'd.

View File

@ -16,7 +16,13 @@ MEMORY
FLASH_FATFS (r) : ORIGIN = ${CIRCUITPY_INTERNAL_FLASH_FILESYSTEM_START_ADDR}, LENGTH = ${CIRCUITPY_INTERNAL_FLASH_FILESYSTEM_SIZE}
FLASH_BOOTLOADER (rx) : ORIGIN = ${BOOTLOADER_START_ADDR}, LENGTH = ${BOOTLOADER_SIZE}
FLASH_BOOTLOADER_SETTINGS (r) : ORIGIN = ${BOOTLOADER_SETTINGS_START_ADDR}, LENGTH = ${BOOTLOADER_SETTINGS_SIZE}
RAM (xrw) : ORIGIN = 0x20004000, LENGTH = 0x03C000 /* 240 KiB */
/* 0x2000000 - RAM:ORIGIN is reserved for Softdevice */
/* SoftDevice 6.1.0 takes 0x7b78 bytes (30.86 kb) minimum with high ATT MTU. */
/* To measure the minimum required amount of memory for given configuration, set this number
high enough to work and then check the mutation of the value done by sd_ble_enable. */
RAM (xrw) : ORIGIN = 0x20000000 + 32K, LENGTH = 256K - 32K
}
/* produce a link error if there is not this amount of RAM for these sections */

View File

@ -67,7 +67,7 @@ STATIC void softdevice_assert_handler(uint32_t id, uint32_t pc, uint32_t info) {
reset_into_safe_mode(NORDIC_SOFT_DEVICE_ASSERT);
}
bleio_connection_internal_t connections[BLEIO_TOTAL_CONNECTION_COUNT];
bleio_connection_internal_t bleio_connections[BLEIO_TOTAL_CONNECTION_COUNT];
// Linker script provided ram start.
extern uint32_t _ram_start;
@ -133,6 +133,15 @@ STATIC uint32_t ble_stack_enable(void) {
return err_code;
}
// Set ATT_MTU so that the maximum MTU we can negotiate is up to the full characteristic size.
memset(&ble_conf, 0, sizeof(ble_conf));
ble_conf.conn_cfg.conn_cfg_tag = BLE_CONN_CFG_TAG_CUSTOM;
ble_conf.conn_cfg.params.gatt_conn_cfg.att_mtu = BLE_GATTS_VAR_ATTR_LEN_MAX;
err_code = sd_ble_cfg_set(BLE_CONN_CFG_GATT, &ble_conf, app_ram_start);
if (err_code != NRF_SUCCESS) {
return err_code;
}
// Triple the GATT Server attribute size to accomodate both the CircuitPython built-in service
// and anything the user does.
memset(&ble_conf, 0, sizeof(ble_conf));
@ -142,7 +151,14 @@ STATIC uint32_t ble_stack_enable(void) {
return err_code;
}
// TODO set ATT_MTU so that the maximum MTU we can negotiate is higher than the default.
// Increase the number of vendor UUIDs supported. Apple uses a complete random number per
// service and characteristic.
memset(&ble_conf, 0, sizeof(ble_conf));
ble_conf.common_cfg.vs_uuid_cfg.vs_uuid_count = 32; // Defaults to 10.
err_code = sd_ble_cfg_set(BLE_COMMON_CFG_VS_UUID, &ble_conf, app_ram_start);
if (err_code != NRF_SUCCESS) {
return err_code;
}
// This sets app_ram_start to the minimum value needed for the settings set above.
err_code = sd_ble_enable(&app_ram_start);
@ -150,6 +166,14 @@ STATIC uint32_t ble_stack_enable(void) {
return err_code;
}
// Turn on connection event extension so we can transmit for a longer period of time as needed.
ble_opt_t opt;
opt.common_opt.conn_evt_ext.enable = true;
err_code = sd_ble_opt_set(BLE_COMMON_OPT_CONN_EVT_EXT, &opt);
if (err_code != NRF_SUCCESS) {
return err_code;
}
ble_gap_conn_params_t gap_conn_params = {
.min_conn_interval = BLE_MIN_CONN_INTERVAL,
.max_conn_interval = BLE_MAX_CONN_INTERVAL,
@ -177,7 +201,7 @@ STATIC bool adapter_on_ble_evt(ble_evt_t *ble_evt, void *self_in) {
// total connection limit.
bleio_connection_internal_t *connection;
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
connection = &connections[i];
connection = &bleio_connections[i];
if (connection->conn_handle == BLE_CONN_HANDLE_INVALID) {
break;
}
@ -189,6 +213,7 @@ STATIC bool adapter_on_ble_evt(ble_evt_t *ble_evt, void *self_in) {
connection->conn_handle = ble_evt->evt.gap_evt.conn_handle;
connection->connection_obj = mp_const_none;
connection->pair_status = PAIR_NOT_PAIRED;
connection->mtu = 0;
ble_drv_add_event_handler_entry(&connection->handler_entry, connection_on_ble_evt, connection);
self->connection_objs = NULL;
@ -216,7 +241,7 @@ STATIC bool adapter_on_ble_evt(ble_evt_t *ble_evt, void *self_in) {
// Find the connection that was disconnected.
bleio_connection_internal_t *connection;
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
connection = &connections[i];
connection = &bleio_connections[i];
if (connection->conn_handle == ble_evt->evt.gap_evt.conn_handle) {
break;
}
@ -293,7 +318,7 @@ void common_hal_bleio_adapter_set_enabled(bleio_adapter_obj_t *self, bool enable
// Add a handler for incoming peripheral connections.
if (enabled) {
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
bleio_connection_internal_t *connection = &connections[i];
bleio_connection_internal_t *connection = &bleio_connections[i];
connection->conn_handle = BLE_CONN_HANDLE_INVALID;
}
bleio_adapter_reset_name(self);
@ -497,14 +522,25 @@ mp_obj_t common_hal_bleio_adapter_connect(bleio_adapter_obj_t *self, bleio_addre
ble_drv_remove_event_handler(connect_on_ble_evt, &event_info);
if (event_info.conn_handle == BLE_CONN_HANDLE_INVALID) {
uint16_t conn_handle = event_info.conn_handle;
if (conn_handle == BLE_CONN_HANDLE_INVALID) {
mp_raise_bleio_BluetoothError(translate("Failed to connect: timeout"));
}
// Negotiate for better PHY, larger MTU and data lengths since we are the central. These are
// nice-to-haves so ignore any errors.
ble_gap_phys_t const phys = {
.rx_phys = BLE_GAP_PHY_AUTO,
.tx_phys = BLE_GAP_PHY_AUTO,
};
sd_ble_gap_phy_update(conn_handle, &phys);
sd_ble_gattc_exchange_mtu_request(conn_handle, BLE_GATTS_VAR_ATTR_LEN_MAX);
sd_ble_gap_data_length_update(conn_handle, NULL, NULL);
// Make the connection object and return it.
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
bleio_connection_internal_t *connection = &connections[i];
if (connection->conn_handle == event_info.conn_handle) {
bleio_connection_internal_t *connection = &bleio_connections[i];
if (connection->conn_handle == conn_handle) {
return bleio_connection_new_from_internal(connection);
}
}
@ -628,7 +664,7 @@ void common_hal_bleio_adapter_stop_advertising(bleio_adapter_obj_t *self) {
bool common_hal_bleio_adapter_get_connected(bleio_adapter_obj_t *self) {
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
bleio_connection_internal_t *connection = &connections[i];
bleio_connection_internal_t *connection = &bleio_connections[i];
if (connection->conn_handle != BLE_CONN_HANDLE_INVALID) {
return true;
}
@ -643,7 +679,7 @@ mp_obj_t common_hal_bleio_adapter_get_connections(bleio_adapter_obj_t *self) {
size_t total_connected = 0;
mp_obj_t items[BLEIO_TOTAL_CONNECTION_COUNT];
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
bleio_connection_internal_t *connection = &connections[i];
bleio_connection_internal_t *connection = &bleio_connections[i];
if (connection->conn_handle != BLE_CONN_HANDLE_INVALID) {
if (connection->connection_obj == mp_const_none) {
connection->connection_obj = bleio_connection_new_from_internal(connection);
@ -658,7 +694,7 @@ mp_obj_t common_hal_bleio_adapter_get_connections(bleio_adapter_obj_t *self) {
void bleio_adapter_gc_collect(bleio_adapter_obj_t* adapter) {
gc_collect_root((void**)adapter, sizeof(bleio_adapter_obj_t) / sizeof(size_t));
gc_collect_root((void**)connections, sizeof(connections) / sizeof(size_t));
gc_collect_root((void**)bleio_connections, sizeof(bleio_connections) / sizeof(size_t));
}
void bleio_adapter_reset(bleio_adapter_obj_t* adapter) {
@ -666,7 +702,7 @@ void bleio_adapter_reset(bleio_adapter_obj_t* adapter) {
common_hal_bleio_adapter_stop_advertising(adapter);
adapter->connection_objs = NULL;
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
bleio_connection_internal_t *connection = &connections[i];
bleio_connection_internal_t *connection = &bleio_connections[i];
connection->connection_obj = mp_const_none;
}
}

View File

@ -37,7 +37,7 @@
#define BLEIO_TOTAL_CONNECTION_COUNT 2
extern bleio_connection_internal_t connections[BLEIO_TOTAL_CONNECTION_COUNT];
extern bleio_connection_internal_t bleio_connections[BLEIO_TOTAL_CONNECTION_COUNT];
typedef struct {
mp_obj_base_t base;

View File

@ -154,7 +154,7 @@ void common_hal_bleio_characteristic_set_value(bleio_characteristic_obj_t *self,
common_hal_bleio_gatts_write(self->handle, BLE_CONN_HANDLE_INVALID, bufinfo);
// Check to see if we need to notify or indicate any active connections.
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
bleio_connection_internal_t *connection = &connections[i];
bleio_connection_internal_t *connection = &bleio_connections[i];
uint16_t conn_handle = connection->conn_handle;
if (connection->conn_handle == BLE_CONN_HANDLE_INVALID) {
continue;

View File

@ -85,6 +85,7 @@ bool connection_on_ble_evt(ble_evt_t *ble_evt, void *self_in) {
switch (ble_evt->header.evt_id) {
case BLE_GAP_EVT_DISCONNECTED:
break;
case BLE_GAP_EVT_PHY_UPDATE_REQUEST: {
ble_gap_phys_t const phys = {
.rx_phys = BLE_GAP_PHY_AUTO,
@ -94,20 +95,45 @@ bool connection_on_ble_evt(ble_evt_t *ble_evt, void *self_in) {
break;
}
case BLE_GAP_EVT_PHY_UPDATE: // 0x22
case BLE_GAP_EVT_PHY_UPDATE: { // 0x22
break;
}
case BLE_GAP_EVT_DATA_LENGTH_UPDATE_REQUEST:
// SoftDevice will respond to a length update request.
sd_ble_gap_data_length_update(self->conn_handle, NULL, NULL);
break;
case BLE_GAP_EVT_DATA_LENGTH_UPDATE: // 0x24
case BLE_GAP_EVT_DATA_LENGTH_UPDATE: { // 0x24
break;
}
case BLE_GATTS_EVT_EXCHANGE_MTU_REQUEST: {
// We only handle MTU of size BLE_GATT_ATT_MTU_DEFAULT.
sd_ble_gatts_exchange_mtu_reply(self->conn_handle, BLE_GATT_ATT_MTU_DEFAULT);
ble_gatts_evt_exchange_mtu_request_t *request =
&ble_evt->evt.gatts_evt.params.exchange_mtu_request;
uint16_t new_mtu = BLE_GATTS_VAR_ATTR_LEN_MAX;
if (request->client_rx_mtu < new_mtu) {
new_mtu = request->client_rx_mtu;
}
if (new_mtu < BLE_GATT_ATT_MTU_DEFAULT) {
new_mtu = BLE_GATT_ATT_MTU_DEFAULT;
}
if (self->mtu > 0) {
new_mtu = self->mtu;
}
self->mtu = new_mtu;
sd_ble_gatts_exchange_mtu_reply(self->conn_handle, new_mtu);
break;
}
case BLE_GATTC_EVT_EXCHANGE_MTU_RSP: {
ble_gattc_evt_exchange_mtu_rsp_t *response =
&ble_evt->evt.gattc_evt.params.exchange_mtu_rsp;
self->mtu = response->server_rx_mtu;
break;
}
@ -319,8 +345,11 @@ STATIC bool discover_next_services(bleio_connection_internal_t* connection, uint
m_discovery_successful = false;
m_discovery_in_process = true;
check_nrf_error(sd_ble_gattc_primary_services_discover(connection->conn_handle,
start_handle, service_uuid));
uint32_t nrf_err = NRF_ERROR_BUSY;
while (nrf_err == NRF_ERROR_BUSY) {
nrf_err = sd_ble_gattc_primary_services_discover(connection->conn_handle, start_handle, service_uuid);
}
check_nrf_error(nrf_err);
// Wait for a discovery event.
while (m_discovery_in_process) {

View File

@ -65,6 +65,7 @@ typedef struct {
ble_drv_evt_handler_entry_t handler_entry;
ble_gap_conn_params_t conn_params;
volatile bool conn_params_updating;
uint16_t mtu;
} bleio_connection_internal_t;
typedef struct {

View File

@ -0,0 +1,331 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019-2020 Scott Shawcroft 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 <string.h>
#include <stdio.h>
#include "ble_drv.h"
#include "ble_gatts.h"
#include "nrf_nvic.h"
#include "lib/utils/interrupt_char.h"
#include "py/runtime.h"
#include "py/stream.h"
#include "tick.h"
#include "shared-bindings/_bleio/__init__.h"
#include "shared-bindings/_bleio/Connection.h"
#include "shared-bindings/_bleio/PacketBuffer.h"
#include "supervisor/shared/tick.h"
STATIC void write_to_ringbuf(bleio_packet_buffer_obj_t *self, uint8_t *data, uint16_t len) {
if (len + sizeof(uint16_t) > self->ringbuf.size) {
// This shouldn't happen.
return;
}
// Push all the data onto the ring buffer.
uint8_t is_nested_critical_region;
sd_nvic_critical_region_enter(&is_nested_critical_region);
// Make room for the new value by dropping the oldest packets first.
while (self->ringbuf.size - ringbuf_count(&self->ringbuf) < (int) (len + sizeof(uint16_t))) {
uint16_t packet_length;
ringbuf_get_n(&self->ringbuf, (uint8_t*) &packet_length, sizeof(uint16_t));
for (uint16_t i = 0; i < packet_length; i++) {
ringbuf_get(&self->ringbuf);
}
// set an overflow flag?
}
ringbuf_put_n(&self->ringbuf, (uint8_t*) &len, sizeof(uint16_t));
ringbuf_put_n(&self->ringbuf, data, len);
sd_nvic_critical_region_exit(is_nested_critical_region);
}
STATIC uint32_t queue_next_write(bleio_packet_buffer_obj_t *self) {
self->packet_queued = false;
if (self->pending_size > 0) {
uint16_t conn_handle = self->conn_handle;
uint32_t err_code;
if (self->client) {
ble_gattc_write_params_t write_params = {
.write_op = self->write_type,
.handle = self->characteristic->handle,
.p_value = self->outgoing[self->pending_index],
.len = self->pending_size,
};
err_code = sd_ble_gattc_write(conn_handle, &write_params);
} else {
uint16_t hvx_len = self->pending_size;
ble_gatts_hvx_params_t hvx_params = {
.handle = self->characteristic->handle,
.type = self->write_type,
.offset = 0,
.p_len = &hvx_len,
.p_data = self->outgoing[self->pending_index],
};
err_code = sd_ble_gatts_hvx(conn_handle, &hvx_params);
}
if (err_code != NRF_SUCCESS) {
// On error, simply skip updating the pending buffers so that the next HVC or WRITE
// complete event triggers another attempt.
return err_code;
}
self->pending_size = 0;
self->pending_index = (self->pending_index + 1) % 2;
self->packet_queued = true;
}
return NRF_SUCCESS;
}
STATIC bool packet_buffer_on_ble_client_evt(ble_evt_t *ble_evt, void *param) {
bleio_packet_buffer_obj_t *self = (bleio_packet_buffer_obj_t *) param;
uint16_t conn_handle = ble_evt->evt.gattc_evt.conn_handle;
if (conn_handle != self->conn_handle) {
return false;
}
switch (ble_evt->header.evt_id) {
case BLE_GATTC_EVT_HVX: {
// A remote service wrote to this characteristic.
ble_gattc_evt_hvx_t* evt_hvx = &ble_evt->evt.gattc_evt.params.hvx;
// Must be a notification, and event handle must match the handle for my characteristic.
if (evt_hvx->handle == self->characteristic->handle) {
write_to_ringbuf(self, evt_hvx->data, evt_hvx->len);
if (evt_hvx->type == BLE_GATT_HVX_INDICATION) {
sd_ble_gattc_hv_confirm(conn_handle, evt_hvx->handle);
}
}
break;
}
case BLE_GATTC_EVT_WRITE_CMD_TX_COMPLETE: {
queue_next_write(self);
break;
}
case BLE_GATTC_EVT_WRITE_RSP: {
queue_next_write(self);
break;
}
default:
return false;
break;
}
return true;
}
STATIC bool packet_buffer_on_ble_server_evt(ble_evt_t *ble_evt, void *param) {
bleio_packet_buffer_obj_t *self = (bleio_packet_buffer_obj_t *) param;
uint16_t conn_handle = ble_evt->evt.gatts_evt.conn_handle;
switch (ble_evt->header.evt_id) {
case BLE_GATTS_EVT_WRITE: {
// A client wrote to this server characteristic.
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) {
if (self->conn_handle == BLE_CONN_HANDLE_INVALID) {
self->conn_handle = conn_handle;
} else if (self->conn_handle != conn_handle) {
return false;
}
write_to_ringbuf(self, evt_write->data, evt_write->len);
} else if (evt_write->handle == self->characteristic->cccd_handle &&
self->conn_handle == BLE_CONN_HANDLE_INVALID) {
uint16_t cccd = *((uint16_t*) evt_write->data);
if (cccd & BLE_GATT_HVX_NOTIFICATION) {
self->conn_handle = conn_handle;
} else {
self->conn_handle = BLE_CONN_HANDLE_INVALID;
}
}
break;
}
case BLE_GATTS_EVT_HVN_TX_COMPLETE: {
queue_next_write(self);
}
default:
return false;
break;
}
return true;
}
void common_hal_bleio_packet_buffer_construct(
bleio_packet_buffer_obj_t *self, bleio_characteristic_obj_t *characteristic,
size_t buffer_size) {
self->characteristic = characteristic;
self->client = self->characteristic->service->is_remote;
bleio_characteristic_properties_t incoming = self->characteristic->props & (CHAR_PROP_WRITE_NO_RESPONSE | CHAR_PROP_WRITE);
bleio_characteristic_properties_t outgoing = self->characteristic->props & (CHAR_PROP_NOTIFY | CHAR_PROP_INDICATE);
if (self->client) {
// Swap if we're the client.
bleio_characteristic_properties_t temp = incoming;
incoming = outgoing;
outgoing = temp;
self->conn_handle = bleio_connection_get_conn_handle(MP_OBJ_TO_PTR(self->characteristic->service->connection));
}
if (incoming) {
// This is a macro.
ringbuf_alloc(&self->ringbuf, buffer_size * (sizeof(uint16_t) + characteristic->max_length), false);
if (self->ringbuf.buf == NULL) {
mp_raise_ValueError(translate("Buffer too large and unable to allocate"));
}
}
if (outgoing) {
self->packet_queued = false;
self->pending_index = 0;
self->pending_size = 0;
self->outgoing[0] = m_malloc(characteristic->max_length, false);
self->outgoing[1] = m_malloc(characteristic->max_length, false);
} else {
self->outgoing[0] = NULL;
self->outgoing[1] = NULL;
}
if (self->client) {
ble_drv_add_event_handler(packet_buffer_on_ble_client_evt, self);
if (incoming) {
// Prefer notify if both are available.
if (incoming & CHAR_PROP_NOTIFY) {
self->write_type = BLE_GATT_HVX_NOTIFICATION;
common_hal_bleio_characteristic_set_cccd(self->characteristic, true, false);
} else {
common_hal_bleio_characteristic_set_cccd(self->characteristic, false, true);
}
}
if (outgoing) {
self->write_type = BLE_GATT_OP_WRITE_REQ;
if (outgoing & CHAR_PROP_WRITE_NO_RESPONSE) {
self->write_type = BLE_GATT_OP_WRITE_CMD;
}
}
} else {
ble_drv_add_event_handler(packet_buffer_on_ble_server_evt, self);
if (outgoing) {
self->write_type = BLE_GATT_HVX_INDICATION;
if (outgoing & CHAR_PROP_NOTIFY) {
self->write_type = BLE_GATT_HVX_NOTIFICATION;
}
}
}
}
int common_hal_bleio_packet_buffer_readinto(bleio_packet_buffer_obj_t *self, uint8_t *data, size_t len) {
if (ringbuf_count(&self->ringbuf) < 2) {
return 0;
}
uint16_t packet_length;
ringbuf_get_n(&self->ringbuf, (uint8_t*) &packet_length, sizeof(uint16_t));
// Copy received data. Lock out write interrupt handler while copying.
uint8_t is_nested_critical_region;
sd_nvic_critical_region_enter(&is_nested_critical_region);
if (packet_length > len) {
// TODO: raise an exception.
packet_length = len;
}
ringbuf_get_n(&self->ringbuf, data, packet_length);
// Writes now OK.
sd_nvic_critical_region_exit(is_nested_critical_region);
return packet_length;
}
void common_hal_bleio_packet_buffer_write(bleio_packet_buffer_obj_t *self, uint8_t *data, size_t len, uint8_t* header, size_t header_len) {
if (self->outgoing[0] == NULL) {
mp_raise_bleio_BluetoothError(translate("Writes not supported on Characteristic"));
}
if (self->conn_handle == BLE_CONN_HANDLE_INVALID) {
return;
}
uint16_t packet_size = common_hal_bleio_packet_buffer_get_packet_size(self);
uint16_t max_size = packet_size - len;
while (max_size < self->pending_size && self->conn_handle != BLE_CONN_HANDLE_INVALID) {
RUN_BACKGROUND_TASKS;
}
if (self->conn_handle == BLE_CONN_HANDLE_INVALID) {
return;
}
uint8_t is_nested_critical_region;
sd_nvic_critical_region_enter(&is_nested_critical_region);
uint8_t* pending = self->outgoing[self->pending_index];
if (self->pending_size == 0) {
memcpy(pending, header, header_len);
self->pending_size += header_len;
}
memcpy(pending + self->pending_size, data, len);
self->pending_size += len;
sd_nvic_critical_region_exit(is_nested_critical_region);
// If no writes are queued then sneak in this data.
if (!self->packet_queued) {
queue_next_write(self);
}
}
uint16_t common_hal_bleio_packet_buffer_get_packet_size(bleio_packet_buffer_obj_t *self) {
uint16_t mtu;
if (self->conn_handle == BLE_CONN_HANDLE_INVALID) {
return 0;
}
bleio_connection_internal_t *connection;
for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) {
connection = &bleio_connections[i];
if (connection->conn_handle == self->conn_handle) {
break;
}
}
if (connection->mtu == 0) {
mtu = 23;
}
if (self->characteristic->max_length > mtu) {
mtu = self->characteristic->max_length;
}
uint16_t att_overhead = 3;
return mtu - att_overhead;
}
bool common_hal_bleio_packet_buffer_deinited(bleio_packet_buffer_obj_t *self) {
return self->characteristic == NULL;
}
void common_hal_bleio_packet_buffer_deinit(bleio_packet_buffer_obj_t *self) {
if (!common_hal_bleio_packet_buffer_deinited(self)) {
ble_drv_remove_event_handler(packet_buffer_on_ble_client_evt, self);
}
}

View File

@ -0,0 +1,49 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019-2020 Scott Shawcroft 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_COMMON_HAL_BLEIO_PACKETBUFFER_H
#define MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_PACKETBUFFER_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;
// Ring buffer storing consecutive incoming values.
ringbuf_t ringbuf;
uint8_t* outgoing[2];
uint16_t pending_size;
uint16_t conn_handle;
uint8_t pending_index;
uint8_t write_type;
bool client;
bool packet_queued;
} bleio_packet_buffer_obj_t;
#endif // MICROPY_INCLUDED_NRF_COMMON_HAL_BLEIO_PACKETBUFFER_H

View File

@ -48,6 +48,9 @@ void check_nrf_error(uint32_t err_code) {
case NRF_ERROR_TIMEOUT:
mp_raise_msg(&mp_type_TimeoutError, NULL);
return;
case BLE_ERROR_INVALID_CONN_HANDLE:
mp_raise_bleio_ConnectionError(translate("Not connected"));
return;
default:
mp_raise_bleio_BluetoothError(translate("Unknown soft device error: %04x"), err_code);
break;

View File

@ -236,6 +236,7 @@ SRC_COMMON_HAL_ALL = \
_bleio/CharacteristicBuffer.c \
_bleio/Connection.c \
_bleio/Descriptor.c \
_bleio/PacketBuffer.c \
_bleio/Service.c \
_bleio/UUID.c \
analogio/AnalogIn.c \

View File

@ -0,0 +1,201 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Scott Shawcroft 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/mperrno.h"
#include "py/ioctl.h"
#include "py/objproperty.h"
#include "py/runtime.h"
#include "py/stream.h"
#include "shared-bindings/_bleio/__init__.h"
#include "shared-bindings/_bleio/PacketBuffer.h"
#include "shared-bindings/_bleio/UUID.h"
#include "shared-bindings/util.h"
//| .. currentmodule:: _bleio
//|
//| :class:`PacketBuffer` -- Packet-oriented characteristic usage.
//| =====================================================================
//|
//| Accumulates a Characteristic's incoming packets in a FIFO buffer and facilitates packet aware
//| outgoing writes. A packet's size is either the characteristic length or the maximum transmission
//| unit (MTU), whichever is smaller. The MTU can change so check `packet_size` before creating a
//| buffer to store data.
//|
//| When we're the server, we ignore all connections besides the first to subscribe to
//| notifications.
//|
//| .. class:: PacketBuffer(characteristic, *, buffer_size)
//|
//| Monitor the given Characteristic. Each time a new value is written to the Characteristic
//| add the newly-written bytes to a FIFO buffer.
//|
//| :param Characteristic characteristic: The Characteristic to monitor.
//| It may be a local Characteristic provided by a Peripheral Service, or a remote Characteristic
//| in a remote Service that a Central has connected to.
//| :param int buffer_size: Size of ring buffer (in packets of the Characteristic's maximum
//| length) that stores incoming packets coming from the peer.
//|
STATIC mp_obj_t bleio_packet_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 };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_characteristic, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_buffer_size, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT },
};
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;
if (buffer_size < 1) {
mp_raise_ValueError_varg(translate("%q must be >= 1"), MP_QSTR_buffer_size);
}
if (!MP_OBJ_IS_TYPE(characteristic, &bleio_characteristic_type)) {
mp_raise_TypeError(translate("Expected a Characteristic"));
}
bleio_packet_buffer_obj_t *self = m_new_obj(bleio_packet_buffer_obj_t);
self->base.type = &bleio_packet_buffer_type;
common_hal_bleio_packet_buffer_construct(self, MP_OBJ_TO_PTR(characteristic), buffer_size);
return MP_OBJ_FROM_PTR(self);
}
STATIC void check_for_deinit(bleio_packet_buffer_obj_t *self) {
if (common_hal_bleio_packet_buffer_deinited(self)) {
raise_deinited_error();
}
}
//| .. method:: readinto(buf)
//|
//| Reads a single BLE packet into the ``buf``. Raises an exception if the next packet is longer
//| than the given buffer. Use `packet_size` to read the maximum length of a single packet.
//|
//| :return: number of bytes read and stored into ``buf``
//| :rtype: int
//|
STATIC mp_obj_t bleio_packet_buffer_readinto(mp_obj_t self_in, mp_obj_t buffer_obj) {
bleio_packet_buffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
check_for_deinit(self);
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(buffer_obj, &bufinfo, MP_BUFFER_WRITE);
return MP_OBJ_NEW_SMALL_INT(common_hal_bleio_packet_buffer_readinto(self, bufinfo.buf, bufinfo.len));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bleio_packet_buffer_readinto_obj, bleio_packet_buffer_readinto);
//| .. method:: write(data, *, header=None)
//|
//| Writes all bytes from data into the same outgoing packet. The bytes from header are included
//| before data when the pending packet is currently empty.
//|
//| This does not block until the data is sent. It only blocks until the data is pending.
//|
// TODO: Add a kwarg `merge=False` to dictate whether subsequent writes are merged into a pending
// one.
STATIC mp_obj_t bleio_packet_buffer_write(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_data, ARG_header };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_data, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_header, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
bleio_packet_buffer_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
check_for_deinit(self);
mp_buffer_info_t data_bufinfo;
mp_get_buffer_raise(args[ARG_data].u_obj, &data_bufinfo, MP_BUFFER_READ);
mp_buffer_info_t header_bufinfo;
header_bufinfo.len = 0;
if (args[ARG_header].u_obj != MP_OBJ_NULL) {
mp_get_buffer_raise(args[ARG_header].u_obj, &header_bufinfo, MP_BUFFER_READ);
}
common_hal_bleio_packet_buffer_write(self, data_bufinfo.buf, data_bufinfo.len,
header_bufinfo.buf, header_bufinfo.len);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bleio_packet_buffer_write_obj, 1, bleio_packet_buffer_write);
//| .. method:: deinit()
//|
//| Disable permanently.
//|
STATIC mp_obj_t bleio_packet_buffer_deinit(mp_obj_t self_in) {
bleio_packet_buffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_bleio_packet_buffer_deinit(self);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(bleio_packet_buffer_deinit_obj, bleio_packet_buffer_deinit);
//| .. attribute:: packet_size
//|
//| Maximum size of each packet in bytes. This is the minimum of the Characterstic length and
//| the negotiated Maximum Transfer Unit (MTU).
//|
STATIC mp_obj_t bleio_packet_buffer_get_packet_size(mp_obj_t self_in) {
bleio_packet_buffer_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_bool(common_hal_bleio_packet_buffer_get_packet_size(self));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(bleio_packet_buffer_get_packet_size_obj, bleio_packet_buffer_get_packet_size);
const mp_obj_property_t bleio_packet_buffer_packet_size_obj = {
.base.type = &mp_type_property,
.proxy = { (mp_obj_t)&bleio_packet_buffer_get_packet_size_obj,
(mp_obj_t)&mp_const_none_obj,
(mp_obj_t)&mp_const_none_obj },
};
STATIC const mp_rom_map_elem_t bleio_packet_buffer_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&bleio_packet_buffer_deinit_obj) },
// Standard stream methods.
{ MP_OBJ_NEW_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&bleio_packet_buffer_readinto_obj) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_write), MP_ROM_PTR(&bleio_packet_buffer_write_obj) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_packet_size), MP_ROM_PTR(&bleio_packet_buffer_packet_size_obj) },
};
STATIC MP_DEFINE_CONST_DICT(bleio_packet_buffer_locals_dict, bleio_packet_buffer_locals_dict_table);
const mp_obj_type_t bleio_packet_buffer_type = {
{ &mp_type_type },
.name = MP_QSTR_PacketBuffer,
.make_new = bleio_packet_buffer_make_new,
.locals_dict = (mp_obj_dict_t*)&bleio_packet_buffer_locals_dict
};

View File

@ -0,0 +1,43 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Scott Shawcroft 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_SHARED_BINDINGS_BLEIO_PACKETBUFFER_H
#define MICROPY_INCLUDED_SHARED_BINDINGS_BLEIO_PACKETBUFFER_H
#include "common-hal/_bleio/PacketBuffer.h"
extern const mp_obj_type_t bleio_packet_buffer_type;
extern void common_hal_bleio_packet_buffer_construct(
bleio_packet_buffer_obj_t *self, bleio_characteristic_obj_t *characteristic,
size_t buffer_size);
void common_hal_bleio_packet_buffer_write(bleio_packet_buffer_obj_t *self, uint8_t *data, size_t len, uint8_t* header, size_t header_len);
int common_hal_bleio_packet_buffer_readinto(bleio_packet_buffer_obj_t *self, uint8_t *data, size_t len);
uint16_t common_hal_bleio_packet_buffer_get_packet_size(bleio_packet_buffer_obj_t *self);
bool common_hal_bleio_packet_buffer_deinited(bleio_packet_buffer_obj_t *self);
void common_hal_bleio_packet_buffer_deinit(bleio_packet_buffer_obj_t *self);
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_BLEIO_PACKETBUFFER_H

View File

@ -35,6 +35,7 @@
#include "shared-bindings/_bleio/CharacteristicBuffer.h"
#include "shared-bindings/_bleio/Connection.h"
#include "shared-bindings/_bleio/Descriptor.h"
#include "shared-bindings/_bleio/PacketBuffer.h"
#include "shared-bindings/_bleio/ScanEntry.h"
#include "shared-bindings/_bleio/ScanResults.h"
#include "shared-bindings/_bleio/Service.h"
@ -69,6 +70,7 @@
//| CharacteristicBuffer
//| Connection
//| Descriptor
//| PacketBuffer
//| ScanEntry
//| ScanResults
//| Service
@ -140,8 +142,9 @@ STATIC const mp_rom_map_elem_t bleio_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_Characteristic), MP_ROM_PTR(&bleio_characteristic_type) },
{ MP_ROM_QSTR(MP_QSTR_CharacteristicBuffer), MP_ROM_PTR(&bleio_characteristic_buffer_type) },
{ MP_ROM_QSTR(MP_QSTR_Descriptor), MP_ROM_PTR(&bleio_descriptor_type) },
{ MP_ROM_QSTR(MP_QSTR_PacketBuffer), MP_ROM_PTR(&bleio_packet_buffer_type) },
{ MP_ROM_QSTR(MP_QSTR_ScanEntry), MP_ROM_PTR(&bleio_scanentry_type) },
{ MP_ROM_QSTR(MP_QSTR_ScanResults), MP_ROM_PTR(&bleio_scanresults_type) },
{ MP_ROM_QSTR(MP_QSTR_ScanResults), MP_ROM_PTR(&bleio_scanresults_type) },
{ MP_ROM_QSTR(MP_QSTR_Service), MP_ROM_PTR(&bleio_service_type) },
{ MP_ROM_QSTR(MP_QSTR_UUID), MP_ROM_PTR(&bleio_uuid_type) },