Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions extmod/btstack/modbluetooth_btstack.c
Original file line number Diff line number Diff line change
Expand Up @@ -1544,6 +1544,28 @@ int mp_bluetooth_l2cap_recvinto(uint16_t conn_handle, uint16_t cid, uint8_t *buf
return MP_EOPNOTSUPP;
}

mp_int_t mp_bluetooth_l2cap_send_bulk(uint16_t conn_handle, uint16_t cid, const uint8_t *buf, size_t len) {
(void)conn_handle;
(void)cid;
(void)buf;
(void)len;
return -MP_EOPNOTSUPP;
}

int mp_bluetooth_l2cap_send_ready(uint16_t conn_handle, uint16_t cid, bool *ready) {
(void)conn_handle;
(void)cid;
(void)ready;
return MP_EOPNOTSUPP;
}

int mp_bluetooth_l2cap_get_peer_mtu(uint16_t conn_handle, uint16_t cid, uint16_t *peer_mtu) {
(void)conn_handle;
(void)cid;
(void)peer_mtu;
return MP_EOPNOTSUPP;
}

#endif // MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS

MP_REGISTER_ROOT_POINTER(struct _mp_bluetooth_btstack_root_pointers_t *bluetooth_btstack_root_pointers);
Expand Down
272 changes: 272 additions & 0 deletions extmod/modbluetooth.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "py/objarray.h"
#include "py/qstr.h"
#include "py/runtime.h"
#include "py/stream.h"
#include "extmod/modbluetooth.h"
#include <string.h>

Expand Down Expand Up @@ -91,6 +92,9 @@ typedef struct {
mp_obj_bluetooth_uuid_t irq_data_uuid;
ringbuf_t ringbuf;
#endif
#if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS
mp_obj_t l2cap_channel_obj;
#endif
} mp_obj_bluetooth_ble_t;

static const mp_obj_type_t mp_type_bluetooth_ble;
Expand Down Expand Up @@ -307,6 +311,12 @@ static mp_obj_t bluetooth_ble_active(size_t n_args, const mp_obj_t *args) {
if (activate) {
err = mp_bluetooth_init();
} else {
#if MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS
mp_obj_bluetooth_ble_t *ble = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
if (ble != NULL) {
ble->l2cap_channel_obj = MP_OBJ_NULL;
}
#endif
err = mp_bluetooth_deinit();
}
bluetooth_handle_errno(err);
Expand Down Expand Up @@ -939,6 +949,258 @@ static mp_obj_t bluetooth_ble_l2cap_recvinto(size_t n_args, const mp_obj_t *args
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_l2cap_recvinto_obj, 4, 4, bluetooth_ble_l2cap_recvinto);

// ----------------------------------------------------------------------------
// L2CAPChannel stream type
// ----------------------------------------------------------------------------

typedef struct {
mp_obj_base_t base;
uint16_t conn_handle;
uint16_t cid;
uint16_t peer_mtu;
int32_t timeout_ms; // -1=blocking(default), 0=non-blocking, >0=timed
bool connected;
} mp_obj_bluetooth_l2cap_channel_t;

static const mp_obj_type_t mp_type_bluetooth_l2cap_channel;

static mp_uint_t l2cap_channel_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) {
mp_obj_bluetooth_l2cap_channel_t *self = MP_OBJ_TO_PTR(self_in);

if (!self->connected) {
// EOF on disconnected channel.
return 0;
}

size_t avail = size;
int err = mp_bluetooth_l2cap_recvinto(self->conn_handle, self->cid, buf, &avail);
if (err != 0) {
*errcode = err;
return MP_STREAM_ERROR;
}

if (avail > 0) {
return avail;
}

// No data available.
if (self->timeout_ms == 0) {
// Non-blocking mode.
*errcode = MP_EAGAIN;
return MP_STREAM_ERROR;
}

// Blocking mode: poll until data arrives, disconnect, or timeout.
mp_uint_t start = mp_hal_ticks_ms();
for (;;) {
mp_event_wait_ms(1);

if (!self->connected) {
return 0; // EOF
}

avail = size;
err = mp_bluetooth_l2cap_recvinto(self->conn_handle, self->cid, buf, &avail);
if (err != 0) {
*errcode = err;
return MP_STREAM_ERROR;
}
if (avail > 0) {
return avail;
}

if (self->timeout_ms > 0) {
mp_uint_t elapsed = mp_hal_ticks_ms() - start;
if (elapsed >= (mp_uint_t)self->timeout_ms) {
*errcode = MP_ETIMEDOUT;
return MP_STREAM_ERROR;
}
}
}
}

static mp_uint_t l2cap_channel_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) {
mp_obj_bluetooth_l2cap_channel_t *self = MP_OBJ_TO_PTR(self_in);

if (!self->connected) {
*errcode = MP_ENOTCONN;
return MP_STREAM_ERROR;
}

if (self->timeout_ms == 0) {
// Non-blocking: check if ready, send one SDU at a time.
bool ready = false;
int err = mp_bluetooth_l2cap_send_ready(self->conn_handle, self->cid, &ready);
if (err != 0) {
*errcode = err;
return MP_STREAM_ERROR;
}
if (!ready) {
*errcode = MP_EAGAIN;
return MP_STREAM_ERROR;
}
// Cap to peer MTU (one SDU via raw send path).
if (size > self->peer_mtu) {
size = self->peer_mtu;
}
bool stalled = false;
int err2 = mp_bluetooth_l2cap_send(self->conn_handle, self->cid, buf, size, &stalled);
if (err2 != 0) {
*errcode = err2;
return MP_STREAM_ERROR;
}
} else {
// Blocking: bulk send handles auto-split and drain loop internally.
// Returns bytes sent on success (>= 0), negative errno on error.
mp_int_t sent = mp_bluetooth_l2cap_send_bulk(self->conn_handle, self->cid, buf, size);
if (sent < 0) {
*errcode = -sent;
return MP_STREAM_ERROR;
}
return (mp_uint_t)sent;
}

return size;
}

static mp_uint_t l2cap_channel_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) {
mp_obj_bluetooth_l2cap_channel_t *self = MP_OBJ_TO_PTR(self_in);

if (request == MP_STREAM_POLL) {
mp_uint_t flags = arg;
mp_uint_t ret = 0;

if (!self->connected) {
ret |= MP_STREAM_POLL_HUP;
// Disconnected channel is readable (returns EOF) and not writable.
if (flags & MP_STREAM_POLL_RD) {
ret |= MP_STREAM_POLL_RD;
}
return ret;
}

if (flags & MP_STREAM_POLL_RD) {
size_t avail = 0;
int err = mp_bluetooth_l2cap_recvinto(self->conn_handle, self->cid, NULL, &avail);
if (err == 0 && avail > 0) {
ret |= MP_STREAM_POLL_RD;
}
}

if (flags & MP_STREAM_POLL_WR) {
bool ready = false;
int err = mp_bluetooth_l2cap_send_ready(self->conn_handle, self->cid, &ready);
if (err == 0 && ready) {
ret |= MP_STREAM_POLL_WR;
}
}

return ret;
}

if (request == MP_STREAM_CLOSE) {
if (self->connected) {
mp_bluetooth_l2cap_disconnect(self->conn_handle, self->cid);
self->connected = false;
}
return 0;
}

*errcode = MP_EINVAL;
return MP_STREAM_ERROR;
}

static const mp_stream_p_t l2cap_channel_stream_p = {
.read = l2cap_channel_read,
.write = l2cap_channel_write,
.ioctl = l2cap_channel_ioctl,
};

static mp_obj_t l2cap_channel_setblocking(mp_obj_t self_in, mp_obj_t flag_in) {
mp_obj_bluetooth_l2cap_channel_t *self = MP_OBJ_TO_PTR(self_in);
if (mp_obj_is_true(flag_in)) {
self->timeout_ms = -1;
} else {
self->timeout_ms = 0;
}
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_2(l2cap_channel_setblocking_obj, l2cap_channel_setblocking);

static mp_obj_t l2cap_channel_settimeout(mp_obj_t self_in, mp_obj_t timeout_in) {
mp_obj_bluetooth_l2cap_channel_t *self = MP_OBJ_TO_PTR(self_in);
if (timeout_in == mp_const_none) {
self->timeout_ms = -1;
} else {
#if MICROPY_PY_BUILTINS_FLOAT
self->timeout_ms = (int32_t)(mp_obj_get_float(timeout_in) * 1000);
#else
self->timeout_ms = mp_obj_get_int(timeout_in) * 1000;
#endif
if (self->timeout_ms < 0) {
mp_raise_ValueError(MP_ERROR_TEXT("timeout must be >= 0"));
}
}
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_2(l2cap_channel_settimeout_obj, l2cap_channel_settimeout);

static const mp_rom_map_elem_t l2cap_channel_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) },
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
{ MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) },
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) },
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&mp_stream___exit___obj) },
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_stream_close_obj) },
{ MP_ROM_QSTR(MP_QSTR_setblocking), MP_ROM_PTR(&l2cap_channel_setblocking_obj) },
{ MP_ROM_QSTR(MP_QSTR_settimeout), MP_ROM_PTR(&l2cap_channel_settimeout_obj) },
};
static MP_DEFINE_CONST_DICT(l2cap_channel_locals_dict, l2cap_channel_locals_dict_table);

static MP_DEFINE_CONST_OBJ_TYPE(
mp_type_bluetooth_l2cap_channel,
MP_QSTR_L2CAPChannel,
MP_TYPE_FLAG_NONE,
protocol, &l2cap_channel_stream_p,
locals_dict, &l2cap_channel_locals_dict
);

// Factory method: ble.l2cap_channel(conn_handle, cid)
static mp_obj_t bluetooth_ble_l2cap_channel(mp_obj_t self_in, mp_obj_t conn_handle_in, mp_obj_t cid_in) {
(void)self_in;
uint16_t conn_handle = mp_obj_get_int(conn_handle_in);
uint16_t cid = mp_obj_get_int(cid_in);

// Verify the channel exists by querying available data.
size_t avail = 0;
int err = mp_bluetooth_l2cap_recvinto(conn_handle, cid, NULL, &avail);
if (err != 0) {
mp_raise_OSError(err);
}

// Get peer MTU.
uint16_t peer_mtu = 0;
err = mp_bluetooth_l2cap_get_peer_mtu(conn_handle, cid, &peer_mtu);
if (err != 0) {
mp_raise_OSError(err);
}

mp_obj_bluetooth_l2cap_channel_t *chan = mp_obj_malloc(mp_obj_bluetooth_l2cap_channel_t, &mp_type_bluetooth_l2cap_channel);
chan->conn_handle = conn_handle;
chan->cid = cid;
chan->peer_mtu = peer_mtu;
chan->timeout_ms = -1; // Blocking by default.
chan->connected = true;

// Store on BLE singleton for lifecycle tracking (GC reachable via root pointer).
mp_obj_bluetooth_ble_t *ble = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
ble->l2cap_channel_obj = MP_OBJ_FROM_PTR(chan);

return MP_OBJ_FROM_PTR(chan);
}
static MP_DEFINE_CONST_FUN_OBJ_3(bluetooth_ble_l2cap_channel_obj, bluetooth_ble_l2cap_channel);

#endif // MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS

#if MICROPY_PY_BLUETOOTH_ENABLE_HCI_CMD
Expand Down Expand Up @@ -1010,6 +1272,7 @@ static const mp_rom_map_elem_t bluetooth_ble_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_l2cap_disconnect), MP_ROM_PTR(&bluetooth_ble_l2cap_disconnect_obj) },
{ MP_ROM_QSTR(MP_QSTR_l2cap_send), MP_ROM_PTR(&bluetooth_ble_l2cap_send_obj) },
{ MP_ROM_QSTR(MP_QSTR_l2cap_recvinto), MP_ROM_PTR(&bluetooth_ble_l2cap_recvinto_obj) },
{ MP_ROM_QSTR(MP_QSTR_l2cap_channel), MP_ROM_PTR(&bluetooth_ble_l2cap_channel_obj) },
#endif
#if MICROPY_PY_BLUETOOTH_ENABLE_HCI_CMD
{ MP_ROM_QSTR(MP_QSTR_hci_cmd), MP_ROM_PTR(&bluetooth_ble_hci_cmd_obj) },
Expand Down Expand Up @@ -1433,6 +1696,15 @@ void mp_bluetooth_on_l2cap_connect(uint16_t conn_handle, uint16_t cid, uint16_t
}

void mp_bluetooth_on_l2cap_disconnect(uint16_t conn_handle, uint16_t cid, uint16_t psm, uint16_t status) {
// Update stream channel object if it matches.
mp_obj_bluetooth_ble_t *ble = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
if (ble->l2cap_channel_obj != MP_OBJ_NULL) {
mp_obj_bluetooth_l2cap_channel_t *chan = MP_OBJ_TO_PTR(ble->l2cap_channel_obj);
if (chan->conn_handle == conn_handle && chan->cid == cid) {
chan->connected = false;
}
}

mp_int_t args[] = {conn_handle, cid, psm, status};
invoke_irq_handler(MP_BLUETOOTH_IRQ_L2CAP_DISCONNECT, args, 4, 0, NULL_ADDR, NULL_UUID, NULL_DATA, NULL_DATA_LEN, 0);
}
Expand Down
3 changes: 3 additions & 0 deletions extmod/modbluetooth.h
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,9 @@ int mp_bluetooth_l2cap_connect(uint16_t conn_handle, uint16_t psm, uint16_t mtu)
int mp_bluetooth_l2cap_disconnect(uint16_t conn_handle, uint16_t cid);
int mp_bluetooth_l2cap_send(uint16_t conn_handle, uint16_t cid, const uint8_t *buf, size_t len, bool *stalled);
int mp_bluetooth_l2cap_recvinto(uint16_t conn_handle, uint16_t cid, uint8_t *buf, size_t *len);
mp_int_t mp_bluetooth_l2cap_send_bulk(uint16_t conn_handle, uint16_t cid, const uint8_t *buf, size_t len);
int mp_bluetooth_l2cap_send_ready(uint16_t conn_handle, uint16_t cid, bool *ready);
int mp_bluetooth_l2cap_get_peer_mtu(uint16_t conn_handle, uint16_t cid, uint16_t *peer_mtu);
#endif // MICROPY_PY_BLUETOOTH_ENABLE_L2CAP_CHANNELS

#if MICROPY_PY_BLUETOOTH_ENABLE_HCI_CMD
Expand Down
Loading
Loading