ble client: alow and guard exclusive connection establishment, parallel disconnects, and scanning

This commit is contained in:
mila@ringwald.ch 2013-11-21 15:15:15 +00:00
parent 1573cd5f74
commit 7df3e0bf12
2 changed files with 405 additions and 235 deletions

View File

@ -46,6 +46,7 @@
#include <string.h>
#include <btstack/run_loop.h>
#include <btstack/hci_cmds.h>
#include <btstack/utils.h>
#include "config.h"
@ -65,24 +66,22 @@ typedef enum {
IDLE,
//
START_SCAN,
W4_SCAN_ACTIVE,
W4_SCANNING,
//
SCAN_ACTIVE,
SCANNING,
//
STOP_SCAN,
W4_SCAN_STOPPED,
// IDLE,
W4_CONNECTED,
CONNECTED,
//
DISCONNECT,
W4_DISCONNECTED
W4_SCAN_STOPPED
} state_t;
static state_t state = W4_ON;
static linked_list_t le_connections = NULL;
void le_central_init(){
state = W4_ON;
le_connections = NULL;
}
void (*le_central_callback)(le_central_event_t * event);
@ -108,48 +107,14 @@ void le_central_register_handler(void (*le_callback)(le_central_event_t* event))
}
}
static void hexdump2(void *data, int size){
int i;
for (i=0; i<size;i++){
printf("%02X ", ((uint8_t *)data)[i]);
}
printf("\n");
}
void gatt_client_init(){
le_connections = NULL;
state = W4_ON;
}
void le_central_start_scan(){
if (state != IDLE) return;
state = START_SCAN;
gatt_client_run();
}
void le_central_stop_scan(){
if (state != SCAN_ACTIVE) return;
state = STOP_SCAN;
gatt_client_run();
}
static void le_peripheral_init(le_peripheral_t *context){
memset(context, 0, sizeof(le_peripheral_t));
context->state = P_IDLE;
}
void le_central_connect(le_peripheral_t *context, uint8_t addr_type, bd_addr_t addr){
le_peripheral_init(context);
context->state = P_W2_CONNECT;
context->address_type = addr_type;
memcpy (context->address, addr, 6);
linked_list_add(&le_connections, (linked_item_t *) context);
if (state == SCAN_ACTIVE){
le_central_stop_scan();
}
gatt_client_run();
static void send_gatt_connection_complete_event(le_peripheral_t * peripheral, uint8_t status){
le_peripheral_event_t event;
event.type = GATT_CONNECTION_COMPLETE;
event.device = peripheral;
event.status = status;
(*le_central_callback)((le_central_event_t*)&event);
}
static le_peripheral_t * get_peripheral_for_handle(uint16_t handle){
@ -178,6 +143,10 @@ static inline le_peripheral_t * get_peripheral_w4_disconnected(){
return get_peripheral_with_state(P_W4_DISCONNECTED);
}
static inline le_peripheral_t * get_peripheral_w4_connect_cancelled(){
return get_peripheral_with_state(P_W4_CONNECT_CANCELLED);
}
static inline le_peripheral_t * get_peripheral_w2_connect(){
return get_peripheral_with_state(P_W2_CONNECT);
}
@ -190,84 +159,292 @@ static inline le_peripheral_t * get_peripheral_w2_exchange_MTU(){
return get_peripheral_with_state(P_W2_EXCHANGE_MTU);
}
void le_central_cancel_connect(le_peripheral_t *context){
if (context->state == P_IDLE) return;
if (context->state == P_W2_CONNECT){
context->state = P_IDLE;
return;
static le_peripheral_t * get_peripheral_with_address(uint8_t addr_type, bd_addr_t addr){
linked_item_t *it;
for (it = (linked_item_t *) le_connections; it ; it = it->next){
le_peripheral_t * peripheral = (le_peripheral_t *) it;
if (BD_ADDR_CMP(addr, peripheral->address) == 0 && peripheral->address_type == addr_type){
return peripheral;
}
}
state = DISCONNECT;
return 0;
}
static void handle_advertising_packet(uint8_t *packet){
int num_reports = packet[3];
int i;
int total_data_length = 0;
int data_offset = 0;
for (i=0; i<num_reports;i++){
total_data_length += packet[4+num_reports*8+i];
}
for (i=0; i<num_reports;i++){
ad_event_t advertisement_event;
advertisement_event.type = GATT_ADVERTISEMENT;
advertisement_event.event_type = packet[4+i];
advertisement_event.address_type = packet[4+num_reports+i];
bt_flip_addr(advertisement_event.address, &packet[4+num_reports*2+i*6]);
advertisement_event.length = packet[4+num_reports*8+i];
advertisement_event.data = &packet[4+num_reports*9+data_offset];
data_offset += advertisement_event.length;
advertisement_event.rssi = packet[4+num_reports*9+total_data_length + i];
(*le_central_callback)((le_central_event_t*)&advertisement_event);
}
}
static void handle_peripheral_list(){
// only one connect is allowed, wait for result
if (get_peripheral_w4_connected()) return;
// only one cancel connect is allowed, wait for result
if (get_peripheral_w4_connect_cancelled()) return;
if (!hci_can_send_packet_now(HCI_COMMAND_DATA_PACKET)) return;
if (!l2cap_can_send_conectionless_packet_now()) return;
linked_item_t *it;
for (it = (linked_item_t *) le_connections; it ; it = it->next){
le_peripheral_t * peripheral = (le_peripheral_t *) it;
switch (peripheral->state){
case P_W2_CONNECT:
peripheral->state = P_W4_CONNECTED;
hci_send_cmd(&hci_le_create_connection,
1000, // scan interval: 625 ms
1000, // scan interval: 625 ms
0, // don't use whitelist
0, // peer address type: public
peripheral->address, // remote bd addr
peripheral->address_type, // random or public
80, // conn interval min
80, // conn interval max (3200 * 0.625)
0, // conn latency
2000, // supervision timeout
0, // min ce length
1000 // max ce length
);
return;
case P_W2_CANCEL_CONNECT:
peripheral->state = P_W4_CONNECT_CANCELLED;
hci_send_cmd(&hci_le_create_connection_cancel);
return;
case P_W2_EXCHANGE_MTU:
{
peripheral->state = P_W4_EXCHANGE_MTU;
uint16_t mtu = l2cap_max_mtu_for_handle(peripheral->handle);
uint8_t request[3];
request[0] = ATT_EXCHANGE_MTU_REQUEST;
bt_store_16(request, 1, mtu);
l2cap_send_connectionless(peripheral->handle, L2CAP_CID_ATTRIBUTE_PROTOCOL, request, sizeof(request));
return;
}
case P_W2_DISCONNECT:
peripheral->state = P_W4_DISCONNECTED;
hci_send_cmd(&hci_disconnect, peripheral->handle,0x13);
return;
default:
break;
}
}
}
void le_central_start_scan(){
if (state != IDLE) return;
state = START_SCAN;
gatt_client_run();
}
void le_central_stop_scan(){
if (state != SCANNING) return;
state = STOP_SCAN;
gatt_client_run();
}
static void le_peripheral_init(le_peripheral_t *context, uint8_t addr_type, bd_addr_t addr){
memset(context, 0, sizeof(le_peripheral_t));
context->state = P_W2_CONNECT;
context->address_type = addr_type;
memcpy (context->address, addr, 6);
}
le_command_status_t le_central_connect(le_peripheral_t *context, uint8_t addr_type, bd_addr_t addr){
//TODO: align with hci connection list capacity
le_peripheral_t * peripheral = get_peripheral_with_address(addr_type, addr);
if (!peripheral) {
le_peripheral_init(context, addr_type, addr);
linked_list_add(&le_connections, (linked_item_t *) context);
} else if (peripheral == context) {
if (context->state != P_W2_CONNECT) return BLE_PERIPHERAL_IN_WRONG_STATE;
} else {
return BLE_PERIPHERAL_DIFFERENT_CONTEXT_FOR_ADDRESS_ALREADY_EXISTS;
}
gatt_client_run();
return BLE_PERIPHERAL_OK;
}
le_command_status_t le_central_disconnect(le_peripheral_t *context){
// printf("*** le_central_disconnect::CALLED DISCONNECT \n");
le_peripheral_t * peripheral = get_peripheral_with_address(context->address_type, context->address);
if (!peripheral || (peripheral && peripheral != context)){
return BLE_PERIPHERAL_DIFFERENT_CONTEXT_FOR_ADDRESS_ALREADY_EXISTS;
}
switch(context->state){
case P_W2_CONNECT:
linked_list_remove(&le_connections, (linked_item_t *) context);
send_gatt_connection_complete_event(context, 0);
break;
case P_W4_CONNECTED:
case P_W2_CANCEL_CONNECT:
// trigger cancel connect
context->state = P_W2_CANCEL_CONNECT;
break;
case P_W2_EXCHANGE_MTU:
case P_W4_EXCHANGE_MTU:
case P_CONNECTED:
case P_W2_DISCONNECT:
// trigger disconnect
context->state = P_W2_DISCONNECT;
break;
case P_W4_DISCONNECTED:
case P_W4_CONNECT_CANCELLED:
return BLE_PERIPHERAL_IN_WRONG_STATE;
}
gatt_client_run();
return BLE_PERIPHERAL_OK;
}
static void gatt_client_run(){
if (state == W4_ON) return;
handle_peripheral_list();
// check if command is send
if (!hci_can_send_packet_now(HCI_COMMAND_DATA_PACKET)) return;
if (!l2cap_can_send_conectionless_packet_now()) return;
le_peripheral_t * peripheral = NULL;
// hadle peripherals list
switch(state){
case IDLE:
if (! (peripheral = get_peripheral_w2_connect()) ) break;
peripheral->state = P_W4_CONNECTED;
state = W4_CONNECTED;
hci_send_cmd(&hci_le_create_connection,
1000, // scan interval: 625 ms
1000, // scan interval: 625 ms
0, // don't use whitelist
0, // peer address type: public
peripheral->address, // remote bd addr
peripheral->address_type, // random or public
80, // conn interval min
80, // conn interval max (3200 * 0.625)
0, // conn latency
2000, // supervision timeout
0, // min ce length
1000 // max ce length
);
break;
case START_SCAN:
state = W4_SCAN_ACTIVE;
state = W4_SCANNING;
hci_send_cmd(&hci_le_set_scan_enable, 1, 0);
break;
return;
case STOP_SCAN:
state = W4_SCAN_STOPPED;
hci_send_cmd(&hci_le_set_scan_enable, 0, 0);
break;
case CONNECTED:{
le_peripheral_t * peripheral = get_peripheral_w2_exchange_MTU();
if (!peripheral) break;
return;
peripheral->state = P_W4_EXCHANGE_MTU;
uint16_t mtu = l2cap_max_mtu_for_handle(peripheral->handle);
uint8_t request[3];
request[0] = ATT_EXCHANGE_MTU_REQUEST;
bt_store_16(request, 1, mtu);
l2cap_send_connectionless(peripheral->handle, L2CAP_CID_ATTRIBUTE_PROTOCOL, request, sizeof(request));
break;
}
case DISCONNECT:
peripheral->state = P_W4_DISCONNECTED;
state = W4_DISCONNECTED;
hci_send_cmd(&hci_le_create_connection_cancel);
break;
default:
break;
}
}
static void dump_ad_event(ad_event_t e){
printf("evt-type %u, addr-type %u, addr %s, rssi %u, length adv %u, data: ", e.event_type,
e.address_type, bd_addr_to_str(e.address), e.rssi, e.length);
hexdump2( e.data, e.length);
static void packet_handler (void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
if (packet_type != HCI_EVENT_PACKET) return;
switch (packet[0]) {
case BTSTACK_EVENT_STATE:
// BTstack activated, get started
if (packet[2] == HCI_STATE_WORKING) {
printf("BTstack activated, get started!\n");
state = IDLE;
}
break;
case HCI_EVENT_COMMAND_COMPLETE:
if (COMMAND_COMPLETE_EVENT(packet, hci_le_set_scan_enable)){
switch(state){
case W4_SCANNING:
state = SCANNING;
break;
case W4_SCAN_STOPPED:
state = IDLE;
break;
default:
break;
}
return;
}
if (COMMAND_COMPLETE_EVENT(packet, hci_le_create_connection_cancel)){
// printf("packet_handler:: hci_le_create_connection_cancel: cancel connect\n");
if (packet[3] != 0x0B) break;
// cancel connection failed, as connection already established
le_peripheral_t * peripheral = get_peripheral_w4_connect_cancelled();
peripheral->state = P_W2_DISCONNECT;
break;
}
break;
case HCI_EVENT_DISCONNECTION_COMPLETE:
{
uint16_t handle = READ_BT_16(packet,3);
le_peripheral_t * peripheral = get_peripheral_for_handle(handle);
if (!peripheral) break;
peripheral->state = P_W2_CONNECT;
linked_list_remove(&le_connections, (linked_item_t *) peripheral);
send_gatt_connection_complete_event(peripheral, packet[5]);
// printf("Peripheral disconnected, and removed from list\n");
break;
}
case HCI_EVENT_LE_META:
switch (packet[2]) {
case HCI_SUBEVENT_LE_ADVERTISING_REPORT:
if (state != SCANNING) break;
handle_advertising_packet(packet);
break;
case HCI_SUBEVENT_LE_CONNECTION_COMPLETE: {
// deal with conn cancel, conn fail, conn success
le_peripheral_t * peripheral;
// conn success/error?
peripheral = get_peripheral_w4_connected();
if (peripheral){
if (packet[3]){
// error
linked_list_remove(&le_connections, (linked_item_t *) peripheral);
} else {
// success
peripheral->state = P_W2_EXCHANGE_MTU;
peripheral->handle = READ_BT_16(packet, 4);
}
send_gatt_connection_complete_event(peripheral, packet[3]);
break;
}
// cancel success?
peripheral = get_peripheral_w4_connect_cancelled();
if (!peripheral) break;
linked_list_remove(&le_connections, (linked_item_t *) peripheral);
send_gatt_connection_complete_event(peripheral, packet[3]);
break;
}
default:
break;
}
break;
default:
break;
}
gatt_client_run();
}
@ -278,138 +455,20 @@ static void att_packet_handler(uint8_t packet_type, uint16_t handle, uint8_t *pa
case ATT_EXCHANGE_MTU_RESPONSE:
{
le_peripheral_t * peripheral = get_peripheral_for_handle(handle);
if (!peripheral) return;
uint16_t remote_rx_mtu = READ_BT_16(packet, 1);
uint16_t local_rx_mtu = l2cap_max_mtu_for_handle(handle);
peripheral->mtu = remote_rx_mtu < local_rx_mtu ? remote_rx_mtu : local_rx_mtu;
le_peripheral_event_t p_connected_event;
p_connected_event.type = GATT_CONNECTION_COMPLETE;
p_connected_event.device = peripheral;
p_connected_event.status = 0;
(*le_central_callback)((le_central_event_t*)&p_connected_event);
send_gatt_connection_complete_event(peripheral, 0);
peripheral->state = P_CONNECTED;
break;
}
default:
break;
}
}
static void packet_handler (void * connection, uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
switch (packet_type) {
case HCI_EVENT_PACKET:
switch (packet[0]) {
case BTSTACK_EVENT_STATE:
// bt stack activated, get started
if (packet[2] == HCI_STATE_WORKING) {
printf("Working!\n");
state = IDLE;
}
break;
case HCI_EVENT_COMMAND_COMPLETE:
if (COMMAND_COMPLETE_EVENT(packet, hci_le_set_scan_enable)){
switch(state){
case W4_SCAN_ACTIVE:
state = SCAN_ACTIVE;
break;
case W4_SCAN_STOPPED:
state = IDLE;
break;
default:
break;
}
}
break;
case HCI_EVENT_LE_META:
switch (packet[2]) {
case HCI_SUBEVENT_LE_ADVERTISING_REPORT:
if (state != SCAN_ACTIVE) break;
{
int num_reports = packet[3];
int i;
int total_data_length = 0;
int data_offset = 0;
for (i=0; i<num_reports;i++){
total_data_length += packet[4+num_reports*8+i];
}
for (i=0; i<num_reports;i++){
ad_event_t advertisement_event;
advertisement_event.type = GATT_ADVERTISEMENT;
advertisement_event.event_type = packet[4+i];
advertisement_event.address_type = packet[4+num_reports+i];
bt_flip_addr(advertisement_event.address, &packet[4+num_reports*2+i*6]);
advertisement_event.length = packet[4+num_reports*8+i];
advertisement_event.data = &packet[4+num_reports*9+data_offset];
data_offset += advertisement_event.length;
advertisement_event.rssi = packet[4+num_reports*9+total_data_length + i];
(*le_central_callback)((le_central_event_t*)&advertisement_event);
dump_ad_event(advertisement_event);
}
break;
}
case HCI_SUBEVENT_LE_CONNECTION_COMPLETE:{
switch (state){
case W4_CONNECTED:{
le_peripheral_t * peripheral = get_peripheral_w4_connected();
if (packet[3] != 0) {
le_peripheral_event_t p_connected_event;
p_connected_event.type = GATT_CONNECTION_COMPLETE;
p_connected_event.device = peripheral;
p_connected_event.status = packet[3];
(*le_central_callback)((le_central_event_t*)&p_connected_event);
return;
}
state = CONNECTED;
peripheral->state = P_W2_EXCHANGE_MTU;
peripheral->handle = READ_BT_16(packet, 4);
printf("Connected: handle 0x%04x!\n", peripheral->handle);
break;
}
case W4_DISCONNECTED: {
le_peripheral_t * peripheral = get_peripheral_w4_disconnected();
state = IDLE;
peripheral->state = P_IDLE;
le_peripheral_event_t p_disconnected_event;
p_disconnected_event.type = GATT_CONNECTION_COMPLETE;
p_disconnected_event.device = peripheral;
p_disconnected_event.status = packet[3];
(*le_central_callback)((le_central_event_t*)&p_disconnected_event);
break;
}
default:
break;
}
break;
}
default:
break;
}
break;
default:
break;
}
}
gatt_client_run();
}
@ -435,11 +494,17 @@ void setup(void){
l2cap_register_packet_handler(packet_handler);
}
// main == setup
int main(void)
{
setup();
/* test
le_central_init();
le_central_register_handler(handle_le_central_event);
*/
// turn on!
hci_power_control(HCI_POWER_ON);
@ -449,3 +514,96 @@ int main(void)
// happy compiler!
return 0;
}
/* test
static void dump_peripheral_state(peripheral_state_t p_state){
switch(p_state) {
case P_W2_CONNECT: printf("P_W2_CONNECT"); break;
case P_W4_CONNECTED: printf("P_W4_CONNECTED"); break;
case P_W2_EXCHANGE_MTU: printf("P_W2_EXCHANGE_MTU"); break;
case P_W4_EXCHANGE_MTU: printf("P_W4_EXCHANGE_MTU"); break;
case P_CONNECTED: printf("P_CONNECTED"); break;
case P_W2_CANCEL_CONNECT: printf("P_W2_CANCEL_CONNECT"); break;
case P_W4_CONNECT_CANCELLED: printf("P_W4_CONNECT_CANCELLED"); break;
case P_W2_DISCONNECT: printf("P_W2_DISCONNECT"); break;
case P_W4_DISCONNECTED: printf("P_W4_DISCONNECTED"); break;
};
printf("\n");
}
static void dump_state(){
switch(state){
case W4_ON: printf("W4_ON"); break;
case IDLE: printf("IDLE"); break;
case START_SCAN: printf("START_SCAN"); break;
case W4_SCANNING: printf("W4_SCANNING"); break;
case SCANNING: printf("SCANNING"); break;
case STOP_SCAN: printf("STOP_SCAN"); break;
case W4_SCAN_STOPPED: printf("W4_SCAN_STOPPED"); break;
};
printf("\n");
}
static void hexdump2(void *data, int size){
int i;
for (i=0; i<size;i++){
printf("%02X ", ((uint8_t *)data)[i]);
}
printf("\n");
}
static void dump_ad_event(ad_event_t* e){
printf("evt-type %u, addr-type %u, addr %s, rssi %u, length adv %u, data: ", e->event_type,
e->address_type, bd_addr_to_str(e->address), e->rssi, e->length);
hexdump2( e->data, e->length);
}
le_peripheral_t test_device;
static bd_addr_t test_device_addr = {0x1c, 0xba, 0x8c, 0x20, 0xc7, 0xf6};
void test_client(){
static int i = 0;
switch(i){
case 0:
le_central_start_scan();
printf("--- test_client::calling start scan \n");
break;
case 20:
printf("--- test_client::calling connect peripheral: status %d\n", le_central_connect(&test_device, 0, test_device_addr));
break;
case 40:
i=-1;
// le_central_stop_scan();
printf("--- test_client::calling dissconnect peripheral: status %d\n", le_central_disconnect(&test_device));
break;
default:
break;
}
i++;
}
static void handle_le_central_event(le_central_event_t * event){
ad_event_t * advertisement_event;
le_peripheral_event_t * peripheral_event;
switch (event->type){
case GATT_ADVERTISEMENT:
advertisement_event = (ad_event_t*) event;
dump_ad_event(advertisement_event);
break;
case GATT_CONNECTION_COMPLETE:
peripheral_event = (le_peripheral_event_t *) event;
if (peripheral_event->status == 0){
printf("handle_le_central_event::device is connected\n");
} else {
printf("handle_le_central_event::disconnected with status %02x \n", peripheral_event->status);
}
default:
break;
}
}*/

View File

@ -66,15 +66,27 @@ typedef struct ad_event {
} ad_event_t;
typedef enum {
P_IDLE,
P_W2_CONNECT,
P_W4_CONNECTED,
P_W2_EXCHANGE_MTU,
P_W4_EXCHANGE_MTU,
P_CONNECTED,
P_W4_DISCONNECTED
P_W2_CANCEL_CONNECT,
P_W4_CONNECT_CANCELLED,
P_W2_DISCONNECT,
P_W4_DISCONNECTED,
} peripheral_state_t;
typedef enum {
BLE_PERIPHERAL_OK = 0,
BLE_PERIPHERAL_IN_WRONG_STATE,
BLE_PERIPHERAL_DIFFERENT_CONTEXT_FOR_ADDRESS_ALREADY_EXISTS
} le_command_status_t;
typedef struct le_peripheral{
linked_item_t item;
@ -108,8 +120,8 @@ void le_central_start_scan();
// { type (8), addr_type (8), addr(48), rssi(8), ad_len(8), ad_data(ad_len*8) }
void le_central_stop_scan();
void le_central_connect(le_peripheral_t *context, uint8_t addr_type, bd_addr_t addr);
void le_central_cancel_connect(le_peripheral_t *context);
le_command_status_t le_central_connect(le_peripheral_t *context, uint8_t addr_type, bd_addr_t addr);
le_command_status_t le_central_disconnect(le_peripheral_t *context);
void le_central_get_services(le_peripheral_t *context);
void le_central_get_services_with_uuid16(le_peripheral_t *context, uint16_t uuid16);