Skip to content

Commit

Permalink
drv/bluetooth: add new Pybricks capabilities characteristic
Browse files Browse the repository at this point in the history
This adds a new Pybricks capabilities characteristic to the Pybricks
BLE service. This characteristic allows reading the hub capabilities
that are defined by the application.
  • Loading branch information
dlech committed Oct 10, 2022
1 parent ed2e584 commit b83536e
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 42 deletions.
3 changes: 3 additions & 0 deletions bricks/cityhub/pbsys_app_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

#define PBSYS_APP_HUB_FEATURE_FLAGS (PBIO_PYBRICKS_FEATURE_REPL | PBIO_PYBRICKS_FEATURE_USER_PROG_FORMAT_MULTI_MPY_V6)
#define PBSYS_APP_USER_PROGRAM_SIZE (16 * 1024 - 512)
3 changes: 3 additions & 0 deletions bricks/essentialhub/pbsys_app_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

#define PBSYS_APP_HUB_FEATURE_FLAGS (PBIO_PYBRICKS_FEATURE_REPL | PBIO_PYBRICKS_FEATURE_USER_PROG_FORMAT_MULTI_MPY_V6)
#define PBSYS_APP_USER_PROGRAM_SIZE (255 * 1024)
3 changes: 3 additions & 0 deletions bricks/movehub/pbsys_app_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

#define PBSYS_APP_HUB_FEATURE_FLAGS (PBIO_PYBRICKS_FEATURE_USER_PROG_FORMAT_MULTI_MPY_V6)
#define PBSYS_APP_USER_PROGRAM_SIZE (4 * 1024 - 128)
3 changes: 3 additions & 0 deletions bricks/primehub/pbsys_app_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

#define PBSYS_APP_HUB_FEATURE_FLAGS (PBIO_PYBRICKS_FEATURE_REPL | PBIO_PYBRICKS_FEATURE_USER_PROG_FORMAT_MULTI_MPY_V6)
#define PBSYS_APP_USER_PROGRAM_SIZE (255 * 1024)
3 changes: 3 additions & 0 deletions bricks/technichub/pbsys_app_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

#define PBSYS_APP_HUB_FEATURE_FLAGS (PBIO_PYBRICKS_FEATURE_REPL | PBIO_PYBRICKS_FEATURE_USER_PROG_FORMAT_MULTI_MPY_V6)
#define PBSYS_APP_USER_PROGRAM_SIZE (16 * 1024 - 512)
3 changes: 2 additions & 1 deletion lib/ble5stack/central/att.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ extern "C"
* Refer to ble_user_config.h for the device-specific maximum MTU value.
*/
#define ATT_MTU_SIZE 23 //L2CAP_MTU_SIZE //!< Minimum ATT MTU size
//#define ATT_MAX_MTU_SIZE (255-L2CAP_HDR_SIZE) //!< Maximum ATT MTU size
#define ATT_MAX_MTU_SIZE 158 //(255-L2CAP_HDR_SIZE) //!< Maximum ATT MTU size
// Pybricks: official LEGO firmware uses 158
/** @} End ATT_MTU_Sizes */

/**
Expand Down
42 changes: 34 additions & 8 deletions lib/pbio/drv/bluetooth/bluetooth_stm32_bluenrg.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <pbio/task.h>
#include <pbio/util.h>
#include <pbio/version.h>
#include <pbsys/app.h>

#include <contiki.h>
#include <lego_lwp3.h>
Expand Down Expand Up @@ -94,7 +95,9 @@ static uint16_t remote_handle;
static uint16_t remote_lwp3_char_handle;

// Pybricks GATT service handles
static uint16_t pybricks_service_handle, pybricks_char_handle;
static uint16_t pybricks_service_handle;
static uint16_t pybricks_command_event_char_handle;
static uint16_t pybricks_hub_capabilities_char_handle;

// Nordic UART GATT service handles
static uint16_t uart_service_handle, uart_rx_char_handle, uart_tx_char_handle;
Expand Down Expand Up @@ -339,7 +342,7 @@ static PT_THREAD(send_value_notification(struct pt *pt, pbio_task_t *task))
goto done;
}
service_handle = pybricks_service_handle;
attr_handle = pybricks_char_handle;
attr_handle = pybricks_command_event_char_handle;
} else if (send->connection == PBDRV_BLUETOOTH_CONNECTION_UART) {
if (!uart_tx_notify_en) {
goto done;
Expand Down Expand Up @@ -807,24 +810,47 @@ static PT_THREAD(init_pybricks_service(struct pt *pt)) {
};

// c5f50002-8280-46da-89f4-6d8051e4aeef
static const uint8_t pybricks_char_uuid[] = {
static const uint8_t pybricks_command_event_char_uuid[] = {
0xef, 0xae, 0xe4, 0x51, 0x80, 0x6d, 0xf4, 0x89,
0xda, 0x46, 0x80, 0x82, 0x02, 0x00, 0xf5, 0xc5
};

// c5f50003-8280-46da-89f4-6d8051e4aeef
static const uint8_t pybricks_hub_capabilities_char_uuid[] = {
0xef, 0xae, 0xe4, 0x51, 0x80, 0x6d, 0xf4, 0x89,
0xda, 0x46, 0x80, 0x82, 0x03, 0x00, 0xf5, 0xc5
};

PT_BEGIN(pt);

PT_WAIT_WHILE(pt, write_xfer_size);
aci_gatt_add_serv_begin(UUID_TYPE_128, pybricks_service_uuid, PRIMARY_SERVICE, 4);
aci_gatt_add_serv_begin(UUID_TYPE_128, pybricks_service_uuid, PRIMARY_SERVICE, 6);
PT_WAIT_UNTIL(pt, hci_command_complete);
aci_gatt_add_serv_end(&pybricks_service_handle);

PT_WAIT_WHILE(pt, write_xfer_size);
aci_gatt_add_char_begin(pybricks_service_handle, UUID_TYPE_128, pybricks_char_uuid,
aci_gatt_add_char_begin(pybricks_service_handle, UUID_TYPE_128, pybricks_command_event_char_uuid,
NUS_CHAR_SIZE, CHAR_PROP_WRITE_WITHOUT_RESP | CHAR_PROP_WRITE | CHAR_PROP_NOTIFY, ATTR_PERMISSION_NONE,
GATT_NOTIFY_ATTRIBUTE_WRITE, MIN_ENCRY_KEY_SIZE, CHAR_VALUE_LEN_VARIABLE);
PT_WAIT_UNTIL(pt, hci_command_complete);
aci_gatt_add_char_end(&pybricks_char_handle);
aci_gatt_add_char_end(&pybricks_command_event_char_handle);

PT_WAIT_WHILE(pt, write_xfer_size);
aci_gatt_add_char_begin(pybricks_service_handle, UUID_TYPE_128, pybricks_hub_capabilities_char_uuid,
PBIO_PYBRICKS_HUB_CAPABILITIES_VALUE_SIZE, CHAR_PROP_READ, ATTR_PERMISSION_NONE,
GATT_DONT_NOTIFY_EVENTS, MIN_ENCRY_KEY_SIZE, CHAR_VALUE_LEN_CONSTANT);
PT_WAIT_UNTIL(pt, hci_command_complete);
aci_gatt_add_char_end(&pybricks_hub_capabilities_char_handle);

PT_WAIT_WHILE(pt, write_xfer_size);
{
uint8_t buf[PBIO_PYBRICKS_HUB_CAPABILITIES_VALUE_SIZE];
pbio_pybricks_hub_capabilities(buf, ATT_MTU - 3, PBSYS_APP_HUB_FEATURE_FLAGS, PBSYS_APP_USER_PROGRAM_SIZE);
aci_gatt_update_char_value_begin(pybricks_service_handle, pybricks_hub_capabilities_char_handle,
0, PBIO_PYBRICKS_HUB_CAPABILITIES_VALUE_SIZE, buf);
}
PT_WAIT_UNTIL(pt, hci_command_complete);
aci_gatt_update_char_value_end();

PT_END(pt);
}
Expand Down Expand Up @@ -886,11 +912,11 @@ static void handle_event(hci_event_pckt *event) {

case EVT_BLUE_GATT_ATTRIBUTE_MODIFIED: {
evt_gatt_attr_modified *subevt = (evt_gatt_attr_modified *)evt->data;
if (subevt->attr_handle == pybricks_char_handle + 1) {
if (subevt->attr_handle == pybricks_command_event_char_handle + 1) {
if (receive_handler) {
receive_handler(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS, subevt->att_data, subevt->data_length);
}
} else if (subevt->attr_handle == pybricks_char_handle + 2) {
} else if (subevt->attr_handle == pybricks_command_event_char_handle + 2) {
pybricks_notify_en = subevt->att_data[0];
} else if (subevt->attr_handle == uart_rx_char_handle + 1) {
if (receive_handler) {
Expand Down
49 changes: 39 additions & 10 deletions lib/pbio/drv/bluetooth/bluetooth_stm32_cc2640.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <pbio/task.h>
#include <pbio/util.h>
#include <pbio/version.h>
#include <pbsys/app.h>

#include <contiki.h>
#include <lego_lwp3.h>
Expand Down Expand Up @@ -131,7 +132,8 @@ static uint16_t gap_service_handle, gap_service_end_handle;
// Device information service handles
static uint16_t dev_info_service_handle, dev_info_service_end_handle;
// Pybricks service handles
static uint16_t pybricks_service_handle, pybricks_service_end_handle, pybricks_char_handle;
static uint16_t pybricks_service_handle, pybricks_service_end_handle;
static uint16_t pybricks_command_event_char_handle, pybricks_capabilities_char_handle;
// Pybricks tx notifications enabled
static bool pybricks_notify_en;
// Nordic UART service handles
Expand Down Expand Up @@ -379,7 +381,7 @@ static PT_THREAD(send_value_notification(struct pt *pt, pbio_task_t *task))
task->status = PBIO_ERROR_INVALID_OP;
goto done;
}
attr_handle = pybricks_char_handle;
attr_handle = pybricks_command_event_char_handle;
} else if (send->connection == PBDRV_BLUETOOTH_CONNECTION_UART) {
if (!uart_tx_notify_en) {
task->status = PBIO_ERROR_INVALID_OP;
Expand Down Expand Up @@ -808,9 +810,12 @@ static void handle_event(uint8_t *packet) {

switch (event_code) {
case ATT_EVENT_EXCHANGE_MTU_REQ: {
// uint16_t client_mtu = (data[7] << 8) | data[6];
attExchangeMTURsp_t rsp;

rsp.serverRxMTU = 158;
rsp.serverRxMTU = ATT_MAX_MTU_SIZE;
// REVISIT: may need to keep a table of min(client_mtu, MAX_ATT_MTU_SIZE)
// for each connection if any known clients have smaller MTU
ATT_ExchangeMTURsp(connection_handle, &rsp);
}
break;
Expand Down Expand Up @@ -867,7 +872,11 @@ static void handle_event(uint8_t *packet) {
} else if (start_handle <= pybricks_service_handle + 1) {
read_by_type_response_uuid128(connection_handle, pybricks_service_handle + 1,
GATT_PROP_WRITE_NO_RSP | GATT_PROP_WRITE | GATT_PROP_NOTIFY,
pbio_pybricks_control_char_uuid);
pbio_pybricks_command_event_char_uuid);
} else if (start_handle <= pybricks_service_handle + 4) {
read_by_type_response_uuid128(connection_handle, pybricks_service_handle + 4,
GATT_PROP_READ,
pbio_pybricks_hub_capabilities_char_uuid);
} else if (start_handle <= uart_service_handle + 1) {
read_by_type_response_uuid128(connection_handle, uart_service_handle + 1,
GATT_PROP_WRITE_NO_RSP, pbio_nus_rx_char_uuid);
Expand Down Expand Up @@ -971,7 +980,7 @@ static void handle_event(uint8_t *packet) {
rsp.len = 7;
rsp.pValue = buf;
ATT_ReadRsp(connection_handle, &rsp);
} else if (handle == pybricks_char_handle + 1) {
} else if (handle == pybricks_command_event_char_handle + 1) {
attReadRsp_t rsp;
uint8_t buf[ATT_MTU_SIZE - 1];

Expand All @@ -980,6 +989,15 @@ static void handle_event(uint8_t *packet) {
rsp.len = 2;
rsp.pValue = buf;
ATT_ReadRsp(connection_handle, &rsp);
} else if (handle == pybricks_capabilities_char_handle + 1) {
attReadRsp_t rsp;
uint8_t buf[PBIO_PYBRICKS_HUB_CAPABILITIES_VALUE_SIZE];

// REVISIT: client MTU may be smaller, in which case we can't used fixed value for MTU
pbio_pybricks_hub_capabilities(buf, ATT_MAX_MTU_SIZE - 3, PBSYS_APP_HUB_FEATURE_FLAGS, PBSYS_APP_USER_PROGRAM_SIZE);
rsp.len = sizeof(buf);
rsp.pValue = buf;
ATT_ReadRsp(connection_handle, &rsp);
} else if (handle == uart_tx_char_handle + 1) {
attReadRsp_t rsp;
uint8_t buf[ATT_MTU_SIZE - 1];
Expand Down Expand Up @@ -1089,11 +1107,11 @@ static void handle_event(uint8_t *packet) {
uint16_t char_handle = (data[9] << 8) | data[8];

DBG("w: %04X %04X %d", char_handle, uart_tx_char_handle, pdu_len - 4);
if (char_handle == pybricks_char_handle) {
if (char_handle == pybricks_command_event_char_handle) {
if (receive_handler) {
receive_handler(PBDRV_BLUETOOTH_CONNECTION_PYBRICKS, &data[10], pdu_len - 4);
}
} else if (char_handle == pybricks_char_handle + 1) {
} else if (char_handle == pybricks_command_event_char_handle + 1) {
pybricks_notify_en = data[10];
DBG("noti: %d", pybricks_notify_en);
} else if (char_handle == uart_rx_char_handle) {
Expand Down Expand Up @@ -1529,7 +1547,7 @@ static PT_THREAD(init_pybricks_service(struct pt *pt)) {
PT_BEGIN(pt);

PT_WAIT_WHILE(pt, write_xfer_size);
GATT_AddService(GATT_PRIMARY_SERVICE_UUID, 4, GATT_MIN_ENCRYPT_KEY_SIZE);
GATT_AddService(GATT_PRIMARY_SERVICE_UUID, 6, GATT_MIN_ENCRYPT_KEY_SIZE);
PT_WAIT_UNTIL(pt, hci_command_status);
// ignoring response data

Expand All @@ -1539,19 +1557,30 @@ static PT_THREAD(init_pybricks_service(struct pt *pt)) {
// ignoring response data

PT_WAIT_WHILE(pt, write_xfer_size);
GATT_AddAttribute2(pbio_pybricks_control_char_uuid, GATT_PERMIT_READ | GATT_PERMIT_WRITE);
GATT_AddAttribute2(pbio_pybricks_command_event_char_uuid, GATT_PERMIT_READ | GATT_PERMIT_WRITE);
PT_WAIT_UNTIL(pt, hci_command_status);
// ignoring response data

PT_WAIT_WHILE(pt, write_xfer_size);
GATT_AddAttribute(GATT_CLIENT_CHAR_CFG_UUID, GATT_PERMIT_READ | GATT_PERMIT_WRITE);
PT_WAIT_UNTIL(pt, hci_command_status);

PT_WAIT_WHILE(pt, write_xfer_size);
GATT_AddAttribute(GATT_CHARACTER_UUID, GATT_PERMIT_READ);
PT_WAIT_UNTIL(pt, hci_command_status);
// ignoring response data

PT_WAIT_WHILE(pt, write_xfer_size);
GATT_AddAttribute2(pbio_pybricks_hub_capabilities_char_uuid, GATT_PERMIT_READ | GATT_PERMIT_WRITE);
PT_WAIT_UNTIL(pt, hci_command_status);
// ignoring response data

// the response to the last GATT_AddAttribute contains the first and last handles
// that were allocated.
pybricks_service_handle = (read_buf[13] << 8) | read_buf[12];
pybricks_service_end_handle = (read_buf[15] << 8) | read_buf[14];
pybricks_char_handle = pybricks_service_handle + 2;
pybricks_command_event_char_handle = pybricks_service_handle + 2;
pybricks_capabilities_char_handle = pybricks_service_handle + 4;
DBG("pybricks: %04X", pybricks_service_handle);

PT_END(pt);
Expand Down
1 change: 1 addition & 0 deletions lib/pbio/drv/bluetooth/pybricks_service.gatt
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_PNP_ID, DYNAMIC | READ,
// Pybricks service
PRIMARY_SERVICE, C5F50001-8280-46DA-89F4-6D8051E4AEEF
CHARACTERISTIC, C5F50002-8280-46DA-89F4-6D8051E4AEEF, NOTIFY | WRITE | WRITE_WITHOUT_RESPONSE | DYNAMIC,
CHARACTERISTIC, C5F50003-8280-46DA-89F4-6D8051E4AEEF, READ | DYNAMIC,

#import <nordic_spp_service.gatt>
47 changes: 29 additions & 18 deletions lib/pbio/drv/bluetooth/pybricks_service_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
#include <stdint.h>
#include <string.h>

#include <pbsys/app.h>
#include <pbio/math.h>
#include <pbio/protocol.h>

#include "btstack_defines.h"
Expand All @@ -64,37 +66,45 @@ static att_service_handler_t pybricks_service;
static pybricks_characteristic_write_callback_t client_callback;
static pybricks_characteristic_configuration_callback_t client_configuration_callback;

static uint16_t pybricks_value_handle;
static uint16_t pybricks_client_configuration_handle;
static uint16_t pybricks_client_configuration_value;
static uint16_t pybricks_command_event_value_handle;
static uint16_t pybricks_command_event_client_configuration_handle;
static uint16_t pybricks_command_event_client_configuration_value;
static uint16_t pybricks_hub_capabilities_value_handle;

// TODO: need a way to reset pybricks_client_configuration_value on disconnect
// TODO: need a way to reset pybricks_command_event_client_configuration_value on disconnect

static uint16_t pybricks_service_read_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) {
UNUSED(con_handle);
UNUSED(offset);
UNUSED(buffer_size);

if (attribute_handle == pybricks_client_configuration_handle) {
if (attribute_handle == pybricks_command_event_client_configuration_handle) {
if (buffer) {
little_endian_store_16(buffer, 0, pybricks_client_configuration_value);
little_endian_store_16(buffer, 0, pybricks_command_event_client_configuration_value);
}
return 2;
}

if (attribute_handle == pybricks_hub_capabilities_value_handle) {
if (buffer && buffer_size >= PBIO_PYBRICKS_HUB_CAPABILITIES_VALUE_SIZE) {
pbio_pybricks_hub_capabilities(buffer, pbio_math_min(att_server_get_mtu(con_handle) - 3, 512),
PBSYS_APP_HUB_FEATURE_FLAGS, PBSYS_APP_USER_PROGRAM_SIZE);
}
return PBIO_PYBRICKS_HUB_CAPABILITIES_VALUE_SIZE;
}

return 0;
}

static int pybricks_service_write_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) {
UNUSED(transaction_mode);
UNUSED(offset);
UNUSED(buffer_size);

if (attribute_handle == pybricks_value_handle) {
if (attribute_handle == pybricks_command_event_value_handle) {
client_callback(con_handle, &buffer[0], buffer_size);
}
if (attribute_handle == pybricks_client_configuration_handle) {
pybricks_client_configuration_value = little_endian_read_16(buffer, 0);
client_configuration_callback(con_handle, pybricks_client_configuration_value);
else if (attribute_handle == pybricks_command_event_client_configuration_handle) {
pybricks_command_event_client_configuration_value = little_endian_read_16(buffer, 0);
client_configuration_callback(con_handle, pybricks_command_event_client_configuration_value);
}

return 0;
Expand All @@ -117,12 +127,13 @@ void pybricks_service_server_init(
btstack_assert(service_found != 0);
UNUSED(service_found);

// get characteristic value handle and client configuration handle
pybricks_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid128(start_handle, end_handle, pbio_pybricks_control_char_uuid);
pybricks_client_configuration_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid128(start_handle, end_handle, pbio_pybricks_control_char_uuid);
// get characteristic value and descriptor handles
pybricks_command_event_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid128(start_handle, end_handle, pbio_pybricks_command_event_char_uuid);
pybricks_command_event_client_configuration_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid128(start_handle, end_handle, pbio_pybricks_command_event_char_uuid);
pybricks_hub_capabilities_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid128(start_handle, end_handle, pbio_pybricks_hub_capabilities_char_uuid);

log_info("pybricks_value_handle 0x%02x", pybricks_value_handle);
log_info("pybricks_client_configuration_handle 0x%02x", pybricks_client_configuration_handle);
log_info("pybricks_command_event_value_handle 0x%02x", pybricks_command_event_value_handle);
log_info("pybricks_command_event_client_configuration_handle 0x%02x", pybricks_command_event_client_configuration_handle);

// register service with ATT Server
pybricks_service.start_handle = start_handle;
Expand All @@ -148,7 +159,7 @@ void pybricks_service_server_request_can_send_now(btstack_context_callback_regis
* @param size
*/
int pybricks_service_server_send(hci_con_handle_t con_handle, const uint8_t *data, uint16_t size) {
return att_server_notify(con_handle, pybricks_value_handle, data, size);
return att_server_notify(con_handle, pybricks_command_event_value_handle, data, size);
}

#endif // PBDRV_CONFIG_BLUETOOTH_BTSTACK
29 changes: 28 additions & 1 deletion lib/pbio/include/pbio/protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,35 @@ typedef enum {

uint32_t pbio_pybricks_event_status_report(uint8_t *buf, uint32_t flags);

/**
* Application-specific feature flag supported by a hub.
*/
typedef enum {
// NB: the values are part of the protocol, so don't change the values!

/**
* Hub support interactive REPL.
*/
PBIO_PYBRICKS_FEATURE_REPL = 1 << 0,
/**
* Hub supports user program with multiple MicroPython .mpy files ABI v6.
*/
PBIO_PYBRICKS_FEATURE_USER_PROG_FORMAT_MULTI_MPY_V6 = 1 << 1,
} pbio_pybricks_feature_flags_t;

void pbio_pybricks_hub_capabilities(uint8_t *buf,
uint16_t max_char_size,
pbio_pybricks_feature_flags_t feature_flags,
uint32_t max_user_prog_size);

/**
* Number of bytes in the Pybricks hub capabilities characteristic value.
*/
#define PBIO_PYBRICKS_HUB_CAPABILITIES_VALUE_SIZE 10

extern const uint8_t pbio_pybricks_service_uuid[];
extern const uint8_t pbio_pybricks_control_char_uuid[];
extern const uint8_t pbio_pybricks_command_event_char_uuid[];
extern const uint8_t pbio_pybricks_hub_capabilities_char_uuid[];

extern const uint16_t pbio_gatt_device_info_service_uuid;
extern const uint16_t pbio_gatt_firmware_version_char_uuid;
Expand Down
Loading

0 comments on commit b83536e

Please sign in to comment.