Files
pico-examples/pico_w/bt/standalone/client.c
2023-06-06 10:43:05 -05:00

292 lines
12 KiB
C

/**
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include "btstack.h"
#include "pico/cyw43_arch.h"
#include "pico/stdlib.h"
#if 0
#define DEBUG_LOG(...) printf(__VA_ARGS__)
#else
#define DEBUG_LOG(...)
#endif
#define LED_QUICK_FLASH_DELAY_MS 100
#define LED_SLOW_FLASH_DELAY_MS 1000
typedef enum {
TC_OFF,
TC_IDLE,
TC_W4_SCAN_RESULT,
TC_W4_CONNECT,
TC_W4_SERVICE_RESULT,
TC_W4_CHARACTERISTIC_RESULT,
TC_W4_ENABLE_NOTIFICATIONS_COMPLETE,
TC_W4_READY
} gc_state_t;
static btstack_packet_callback_registration_t hci_event_callback_registration;
static gc_state_t state = TC_OFF;
static bd_addr_t server_addr;
static bd_addr_type_t server_addr_type;
static hci_con_handle_t connection_handle;
static gatt_client_service_t server_service;
static gatt_client_characteristic_t server_characteristic;
static bool listener_registered;
static gatt_client_notification_t notification_listener;
static btstack_timer_source_t heartbeat;
static void client_start(void){
DEBUG_LOG("Start scanning!\n");
state = TC_W4_SCAN_RESULT;
gap_set_scan_parameters(0,0x0030, 0x0030);
gap_start_scan();
}
static bool advertisement_report_contains_service(uint16_t service, uint8_t *advertisement_report){
// get advertisement from report event
const uint8_t * adv_data = gap_event_advertising_report_get_data(advertisement_report);
uint8_t adv_len = gap_event_advertising_report_get_data_length(advertisement_report);
// iterate over advertisement data
ad_context_t context;
for (ad_iterator_init(&context, adv_len, adv_data) ; ad_iterator_has_more(&context) ; ad_iterator_next(&context)){
uint8_t data_type = ad_iterator_get_data_type(&context);
uint8_t data_size = ad_iterator_get_data_len(&context);
const uint8_t * data = ad_iterator_get_data(&context);
switch (data_type){
case BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS:
for (int i = 0; i < data_size; i += 2) {
uint16_t type = little_endian_read_16(data, i);
if (type == service) return true;
}
default:
break;
}
}
return false;
}
static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
UNUSED(packet_type);
UNUSED(channel);
UNUSED(size);
uint8_t att_status;
switch(state){
case TC_W4_SERVICE_RESULT:
switch(hci_event_packet_get_type(packet)) {
case GATT_EVENT_SERVICE_QUERY_RESULT:
// store service (we expect only one)
DEBUG_LOG("Storing service\n");
gatt_event_service_query_result_get_service(packet, &server_service);
break;
case GATT_EVENT_QUERY_COMPLETE:
att_status = gatt_event_query_complete_get_att_status(packet);
if (att_status != ATT_ERROR_SUCCESS){
printf("SERVICE_QUERY_RESULT, ATT Error 0x%02x.\n", att_status);
gap_disconnect(connection_handle);
break;
}
// service query complete, look for characteristic
state = TC_W4_CHARACTERISTIC_RESULT;
DEBUG_LOG("Search for env sensing characteristic.\n");
gatt_client_discover_characteristics_for_service_by_uuid16(handle_gatt_client_event, connection_handle, &server_service, ORG_BLUETOOTH_CHARACTERISTIC_TEMPERATURE);
break;
default:
break;
}
break;
case TC_W4_CHARACTERISTIC_RESULT:
switch(hci_event_packet_get_type(packet)) {
case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
DEBUG_LOG("Storing characteristic\n");
gatt_event_characteristic_query_result_get_characteristic(packet, &server_characteristic);
break;
case GATT_EVENT_QUERY_COMPLETE:
att_status = gatt_event_query_complete_get_att_status(packet);
if (att_status != ATT_ERROR_SUCCESS){
printf("CHARACTERISTIC_QUERY_RESULT, ATT Error 0x%02x.\n", att_status);
gap_disconnect(connection_handle);
break;
}
// register handler for notifications
listener_registered = true;
gatt_client_listen_for_characteristic_value_updates(&notification_listener, handle_gatt_client_event, connection_handle, &server_characteristic);
// enable notifications
DEBUG_LOG("Enable notify on characteristic.\n");
state = TC_W4_ENABLE_NOTIFICATIONS_COMPLETE;
gatt_client_write_client_characteristic_configuration(handle_gatt_client_event, connection_handle,
&server_characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
break;
default:
break;
}
break;
case TC_W4_ENABLE_NOTIFICATIONS_COMPLETE:
switch(hci_event_packet_get_type(packet)) {
case GATT_EVENT_QUERY_COMPLETE:
DEBUG_LOG("Notifications enabled, ATT status 0x%02x\n", gatt_event_query_complete_get_att_status(packet));
if (gatt_event_query_complete_get_att_status(packet) != ATT_ERROR_SUCCESS) break;
state = TC_W4_READY;
break;
default:
break;
}
break;
case TC_W4_READY:
switch(hci_event_packet_get_type(packet)) {
case GATT_EVENT_NOTIFICATION: {
uint16_t value_length = gatt_event_notification_get_value_length(packet);
const uint8_t *value = gatt_event_notification_get_value(packet);
DEBUG_LOG("Indication value len %d\n", value_length);
if (value_length == 2) {
float temp = little_endian_read_16(value, 0);
printf("read temp %.2f degc\n", temp / 100);
} else {
printf("Unexpected length %d\n", value_length);
}
break;
}
default:
printf("Unknown packet type 0x%02x\n", hci_event_packet_get_type(packet));
break;
}
break;
default:
printf("error\n");
break;
}
}
static void hci_event_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
UNUSED(size);
UNUSED(channel);
bd_addr_t local_addr;
if (packet_type != HCI_EVENT_PACKET) return;
uint8_t event_type = hci_event_packet_get_type(packet);
switch(event_type){
case BTSTACK_EVENT_STATE:
if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING) {
gap_local_bd_addr(local_addr);
printf("BTstack up and running on %s.\n", bd_addr_to_str(local_addr));
client_start();
} else {
state = TC_OFF;
}
break;
case GAP_EVENT_ADVERTISING_REPORT:
if (state != TC_W4_SCAN_RESULT) return;
// check name in advertisement
if (!advertisement_report_contains_service(ORG_BLUETOOTH_SERVICE_ENVIRONMENTAL_SENSING, packet)) return;
// store address and type
gap_event_advertising_report_get_address(packet, server_addr);
server_addr_type = gap_event_advertising_report_get_address_type(packet);
// stop scanning, and connect to the device
state = TC_W4_CONNECT;
gap_stop_scan();
printf("Connecting to device with addr %s.\n", bd_addr_to_str(server_addr));
gap_connect(server_addr, server_addr_type);
break;
case HCI_EVENT_LE_META:
// wait for connection complete
switch (hci_event_le_meta_get_subevent_code(packet)) {
case HCI_SUBEVENT_LE_CONNECTION_COMPLETE:
if (state != TC_W4_CONNECT) return;
connection_handle = hci_subevent_le_connection_complete_get_connection_handle(packet);
// initialize gatt client context with handle, and add it to the list of active clients
// query primary services
DEBUG_LOG("Search for env sensing service.\n");
state = TC_W4_SERVICE_RESULT;
gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, connection_handle, ORG_BLUETOOTH_SERVICE_ENVIRONMENTAL_SENSING);
break;
default:
break;
}
break;
case HCI_EVENT_DISCONNECTION_COMPLETE:
// unregister listener
connection_handle = HCI_CON_HANDLE_INVALID;
if (listener_registered){
listener_registered = false;
gatt_client_stop_listening_for_characteristic_value_updates(&notification_listener);
}
printf("Disconnected %s\n", bd_addr_to_str(server_addr));
if (state == TC_OFF) break;
client_start();
break;
default:
break;
}
}
static void heartbeat_handler(struct btstack_timer_source *ts) {
// Invert the led
static bool quick_flash;
static bool led_on = true;
led_on = !led_on;
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, led_on);
if (listener_registered && led_on) {
quick_flash = !quick_flash;
} else if (!listener_registered) {
quick_flash = false;
}
// Restart timer
btstack_run_loop_set_timer(ts, (led_on || quick_flash) ? LED_QUICK_FLASH_DELAY_MS : LED_SLOW_FLASH_DELAY_MS);
btstack_run_loop_add_timer(ts);
}
int main() {
stdio_init_all();
// initialize CYW43 driver architecture (will enable BT if/because CYW43_ENABLE_BLUETOOTH == 1)
if (cyw43_arch_init()) {
printf("failed to initialise cyw43_arch\n");
return -1;
}
l2cap_init();
sm_init();
sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
// setup empty ATT server - only needed if LE Peripheral does ATT queries on its own, e.g. Android and iOS
att_server_init(NULL, NULL, NULL);
gatt_client_init();
hci_event_callback_registration.callback = &hci_event_handler;
hci_add_event_handler(&hci_event_callback_registration);
// set one-shot btstack timer
heartbeat.process = &heartbeat_handler;
btstack_run_loop_set_timer(&heartbeat, LED_SLOW_FLASH_DELAY_MS);
btstack_run_loop_add_timer(&heartbeat);
// turn on!
hci_power_control(HCI_POWER_ON);
// btstack_run_loop_execute is only required when using the 'polling' method (e.g. using pico_cyw43_arch_poll library).
// This example uses the 'threadsafe background` method, where BT work is handled in a low priority IRQ, so it
// is fine to call bt_stack_run_loop_execute() but equally you can continue executing user code.
#if 1 // this is only necessary when using polling (which we aren't, but we're showing it is still safe to call in this case)
btstack_run_loop_execute();
#else
// this core is free to do it's own stuff except when using 'polling' method (in which case you should use
// btstacK_run_loop_ methods to add work to the run loop.
// this is a forever loop in place of where user code would go.
while(true) {
sleep_ms(1000);
}
#endif
return 0;
}