From bf9af4c740a9c96b4079519e4612cfd48f52a85d Mon Sep 17 00:00:00 2001 From: Matt Kelly Date: Wed, 22 Feb 2017 23:58:08 -0500 Subject: [PATCH] esp32: Put btstack in its own task and fix missing byte in outgoing packets --- port/esp32/main/component.mk | 4 +- port/esp32/main/le_counter.c | 259 +++++++++++++++++++++++++++++++++++ port/esp32/main/le_counter.h | 46 +++++++ port/esp32/main/main.c | 84 +++++++++--- 4 files changed, 375 insertions(+), 18 deletions(-) create mode 100644 port/esp32/main/le_counter.c create mode 100644 port/esp32/main/le_counter.h diff --git a/port/esp32/main/component.mk b/port/esp32/main/component.mk index 87113e078..1da302d15 100644 --- a/port/esp32/main/component.mk +++ b/port/esp32/main/component.mk @@ -11,9 +11,9 @@ BTSTACK_ROOT := ../../.. # Examples #include ${BTSTACK_ROOT}/example/Makefile.inc -COMPONENT_ADD_INCLUDEDIRS := $(BTSTACK_ROOT)/src/ble $(BTSTACK_ROOT)/src $(BTSTACK_ROOT)/platform/embedded . +COMPONENT_ADD_INCLUDEDIRS := $(BTSTACK_ROOT)/src/ble/gatt-service $(BTSTACK_ROOT)/src/ble $(BTSTACK_ROOT)/src $(BTSTACK_ROOT)/platform/embedded . -COMPONENT_SRCDIRS := $(BTSTACK_ROOT)/src/ble $(BTSTACK_ROOT)/src/ $(BTSTACK_ROOT)/platform/embedded . +COMPONENT_SRCDIRS := $(BTSTACK_ROOT)/src/ble/gatt-service $(BTSTACK_ROOT)/src/ble $(BTSTACK_ROOT)/src/ $(BTSTACK_ROOT)/platform/embedded . CFLAGS += -Wno-format diff --git a/port/esp32/main/le_counter.c b/port/esp32/main/le_counter.c new file mode 100644 index 000000000..99bc111b9 --- /dev/null +++ b/port/esp32/main/le_counter.c @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2014 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +// ***************************************************************************** +/* EXAMPLE_START(le_counter): LE Peripheral - Heartbeat Counter over GATT + * + * @text All newer operating systems provide GATT Client functionality. + * The LE Counter examples demonstrates how to specify a minimal GATT Database + * with a custom GATT Service and a custom Characteristic that sends periodic + * notifications. + */ + // ***************************************************************************** + +#include +#include +#include +#include + +#include "le_counter.h" +#include "btstack.h" +#include "ble/gatt-service/battery_service_server.h" + +#define HEARTBEAT_PERIOD_MS 1000 + +/* @section Main Application Setup + * + * @text Listing MainConfiguration shows main application code. + * It initializes L2CAP, the Security Manager and configures the ATT Server with the pre-compiled + * ATT Database generated from $le_counter.gatt$. + * Additionally, it enables the Battery Service Server with the current battery level. + * Finally, it configures the advertisements + * and the heartbeat handler and boots the Bluetooth stack. + * In this example, the Advertisement contains the Flags attribute and the device name. + * The flag 0x06 indicates: LE General Discoverable Mode and BR/EDR not supported. + */ + +/* LISTING_START(MainConfiguration): Init L2CAP SM ATT Server and start heartbeat timer */ +static int le_notification_enabled; +static btstack_timer_source_t heartbeat; +static btstack_packet_callback_registration_t hci_event_callback_registration; +static hci_con_handle_t con_handle; +static uint8_t battery = 100; + +static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); +static uint16_t att_read_callback(hci_con_handle_t con_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size); +static int att_write_callback(hci_con_handle_t con_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size); +static void heartbeat_handler(struct btstack_timer_source *ts); +static void beat(void); + +const uint8_t adv_data[] = { + // Flags general discoverable, BR/EDR not supported + 0x02, 0x01, 0x06, + // Name + 0x0b, 0x09, 'L', 'E', ' ', 'C', 'o', 'u', 'n', 't', 'e', 'r', +}; +const uint8_t adv_data_len = sizeof(adv_data); + +static void le_counter_setup(void){ + + // register for HCI events + hci_event_callback_registration.callback = &packet_handler; + hci_add_event_handler(&hci_event_callback_registration); + + l2cap_init(); + + // setup le device db + le_device_db_init(); + + // setup SM: Display only + sm_init(); + + // setup ATT server + att_server_init(profile_data, att_read_callback, att_write_callback); + att_server_register_packet_handler(packet_handler); + + // setup battery service + battery_service_server_init(battery); + + // setup advertisements + uint16_t adv_int_min = 0x0030; + uint16_t adv_int_max = 0x0030; + uint8_t adv_type = 0; + bd_addr_t null_addr; + memset(null_addr, 0, 6); + gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, 0, null_addr, 0x07, 0x00); + gap_advertisements_set_data(adv_data_len, (uint8_t*) adv_data); + gap_advertisements_enable(1); + + // set one-shot timer + heartbeat.process = &heartbeat_handler; + btstack_run_loop_set_timer(&heartbeat, HEARTBEAT_PERIOD_MS); + btstack_run_loop_add_timer(&heartbeat); + + // beat once + beat(); +} +/* LISTING_END */ + +/* + * @section Heartbeat Handler + * + * @text The heartbeat handler updates the value of the single Characteristic provided in this example, + * and request a ATT_EVENT_CAN_SEND_NOW to send a notification if enabled see Listing heartbeat. + */ + + /* LISTING_START(heartbeat): Hearbeat Handler */ +static int counter = 0; +static char counter_string[30]; +static int counter_string_len; + +static void beat(void){ + counter++; + counter_string_len = sprintf(counter_string, "BTstack counter %04u", counter); + puts(counter_string); +} + +static void heartbeat_handler(struct btstack_timer_source *ts){ + if (le_notification_enabled) { + beat(); + att_server_request_can_send_now_event(con_handle); + } + + // simulate battery drain + battery--; + if (battery < 50) { + battery = 100; + } + battery_service_server_set_battery_value(battery); + + btstack_run_loop_set_timer(ts, HEARTBEAT_PERIOD_MS); + btstack_run_loop_add_timer(ts); +} +/* LISTING_END */ + +/* + * @section Packet Handler + * + * @text The packet handler is used to: + * - stop the counter after a disconnect + * - send a notification when the requested ATT_EVENT_CAN_SEND_NOW is received + */ + +/* LISTING_START(packetHandler): Packet Handler */ +static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ + UNUSED(channel); + UNUSED(size); + + switch (packet_type) { + case HCI_EVENT_PACKET: + switch (hci_event_packet_get_type(packet)) { + case HCI_EVENT_DISCONNECTION_COMPLETE: + le_notification_enabled = 0; + break; + case ATT_EVENT_CAN_SEND_NOW: + att_server_notify(con_handle, ATT_CHARACTERISTIC_0000FF11_0000_1000_8000_00805F9B34FB_01_VALUE_HANDLE, (uint8_t*) counter_string, counter_string_len); + break; + } + break; + } +} +/* LISTING_END */ + +/* + * @section ATT Read + * + * @text The ATT Server handles all reads to constant data. For dynamic data like the custom characteristic, the registered + * att_read_callback is called. To handle long characteristics and long reads, the att_read_callback is first called + * with buffer == NULL, to request the total value length. Then it will be called again requesting a chunk of the value. + * See Listing attRead. + */ + +/* LISTING_START(attRead): ATT Read */ + +// ATT Client Read Callback for Dynamic Data +// - if buffer == NULL, don't copy data, just return size of value +// - if buffer != NULL, copy data and return number bytes copied +// @param offset defines start of attribute value +static uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){ + UNUSED(connection_handle); + + if (att_handle == ATT_CHARACTERISTIC_0000FF11_0000_1000_8000_00805F9B34FB_01_VALUE_HANDLE){ + if (buffer){ + memcpy(buffer, &counter_string[offset], buffer_size); + return buffer_size; + } else { + return counter_string_len; + } + } + return 0; +} +/* LISTING_END */ + + +/* + * @section ATT Write + * + * @text The only valid ATT write in this example is to the Client Characteristic Configuration, which configures notification + * and indication. If the ATT handle matches the client configuration handle, the new configuration value is stored and used + * in the heartbeat handler to decide if a new value should be sent. See Listing attWrite. + */ + +/* LISTING_START(attWrite): ATT Write */ +static int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size){ + UNUSED(transaction_mode); + UNUSED(offset); + UNUSED(buffer_size); + + if (att_handle != ATT_CHARACTERISTIC_0000FF11_0000_1000_8000_00805F9B34FB_01_CLIENT_CONFIGURATION_HANDLE) return 0; + le_notification_enabled = little_endian_read_16(buffer, 0) == GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION; + con_handle = connection_handle; + return 0; +} +/* LISTING_END */ + +int btstack_main(void); +int btstack_main(void) +{ + le_counter_setup(); + + // turn on! + hci_power_control(HCI_POWER_ON); + + return 0; +} +/* EXAMPLE_END */ diff --git a/port/esp32/main/le_counter.h b/port/esp32/main/le_counter.h new file mode 100644 index 000000000..1b64f625e --- /dev/null +++ b/port/esp32/main/le_counter.h @@ -0,0 +1,46 @@ + +// le_counter.h generated from ../../example/le_counter.gatt for BTstack + +// binary representation +// attribute size in bytes (16), flags(16), handle (16), uuid (16/128), value(...) + +#include + +const uint8_t profile_data[] = +{ + // 0x0001 PRIMARY_SERVICE-GAP_SERVICE + 0x0a, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x28, 0x00, 0x18, + // 0x0002 CHARACTERISTIC-GAP_DEVICE_NAME-READ + 0x0d, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x28, 0x02, 0x03, 0x00, 0x00, 0x2a, + // 0x0003 VALUE-GAP_DEVICE_NAME-READ-'SPP+LE Counter' + 0x16, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x2a, 0x53, 0x50, 0x50, 0x2b, 0x4c, 0x45, 0x20, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, + + // 0x0004 PRIMARY_SERVICE-GATT_SERVICE + 0x0a, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x28, 0x01, 0x18, + // 0x0005 CHARACTERISTIC-GATT_SERVICE_CHANGED-READ + 0x0d, 0x00, 0x02, 0x00, 0x05, 0x00, 0x03, 0x28, 0x02, 0x06, 0x00, 0x05, 0x2a, + // 0x0006 VALUE-GATT_SERVICE_CHANGED-READ-'' + 0x08, 0x00, 0x02, 0x00, 0x06, 0x00, 0x05, 0x2a, +// Counter Service + + // 0x0007 PRIMARY_SERVICE-0000FF10-0000-1000-8000-00805F9B34FB + 0x18, 0x00, 0x02, 0x00, 0x07, 0x00, 0x00, 0x28, 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x10, 0xff, 0x00, 0x00, +// Counter Characteristic, with read and notify + // 0x0008 CHARACTERISTIC-0000FF11-0000-1000-8000-00805F9B34FB-READ | NOTIFY | DYNAMIC + 0x1b, 0x00, 0x02, 0x00, 0x08, 0x00, 0x03, 0x28, 0x12, 0x09, 0x00, 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x11, 0xff, 0x00, 0x00, + // 0x0009 VALUE-0000FF11-0000-1000-8000-00805F9B34FB-READ | NOTIFY | DYNAMIC-'' + 0x16, 0x00, 0x12, 0x03, 0x09, 0x00, 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x11, 0xff, 0x00, 0x00, + // 0x000a CLIENT_CHARACTERISTIC_CONFIGURATION + 0x0a, 0x00, 0x0a, 0x01, 0x0a, 0x00, 0x02, 0x29, 0x00, 0x00, + // END + 0x00, 0x00, +}; // total size 99 bytes + + +// +// list mapping between characteristics and handles +// +#define ATT_CHARACTERISTIC_GAP_DEVICE_NAME_01_VALUE_HANDLE 0x0003 +#define ATT_CHARACTERISTIC_GATT_SERVICE_CHANGED_01_VALUE_HANDLE 0x0006 +#define ATT_CHARACTERISTIC_0000FF11_0000_1000_8000_00805F9B34FB_01_VALUE_HANDLE 0x0009 +#define ATT_CHARACTERISTIC_0000FF11_0000_1000_8000_00805F9B34FB_01_CLIENT_CONFIGURATION_HANDLE 0x000a diff --git a/port/esp32/main/main.c b/port/esp32/main/main.c index 648ab5563..3362a95c4 100644 --- a/port/esp32/main/main.c +++ b/port/esp32/main/main.c @@ -50,14 +50,19 @@ #include "hci_dump.h" #include "bt.h" -static int can_send_packet_now = 1; +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +static uint8_t _hci_cmd_buf[1028]; + +static int _can_send_packet_now = 1; static void (*transport_packet_handler)(uint8_t packet_type, uint8_t *packet, uint16_t size); static void host_send_pkt_available_cb(void) { printf("host_send_pkt_available_cb\n"); - can_send_packet_now = 1; + _can_send_packet_now = 1; } static int host_recv_pkt_cb(uint8_t *data, uint16_t len) @@ -82,7 +87,13 @@ static const esp_vhci_host_callback_t vhci_host_cb = { */ static void transport_init(const void *transport_config){ printf("transport_init\n"); - esp_vhci_host_register_callback(&vhci_host_cb); +} + +static btstack_data_source_t hci_transport_data_source; + +static void transport_run(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type){ + (void)ds; + (void)callback_type; } /** @@ -90,6 +101,9 @@ static void transport_init(const void *transport_config){ */ static int transport_open(void){ printf("transport_open\n"); + btstack_run_loop_set_data_source_handler(&hci_transport_data_source, &transport_run); + btstack_run_loop_enable_data_source_callbacks(&hci_transport_data_source, DATA_SOURCE_CALLBACK_POLL); + btstack_run_loop_add_data_source(&hci_transport_data_source); return 0; } @@ -98,6 +112,7 @@ static int transport_open(void){ */ static int transport_close(void){ printf("transport_close\n"); + btstack_run_loop_remove_data_source(&hci_transport_data_source); return 0; } @@ -111,14 +126,33 @@ static void transport_register_packet_handler(void (*handler)(uint8_t packet_typ static int transport_can_send_packet_now(uint8_t packet_type) { printf("transport_can_send_packet_now\n"); - return can_send_packet_now; + return _can_send_packet_now; } static int transport_send_packet(uint8_t packet_type, uint8_t *packet, int size){ (void)packet_type; - printf("transport_send_packet\n"); - esp_vhci_host_send_packet(packet, size); - printf("AFTER transport_send_packet\n"); + printf("transport_send_packet: type = %u, len = %u, data = [ ", packet_type, size); + for (size_t i = 0; i < size; i++) { + printf("%02X ", packet[i]); + } + printf("]\n"); + switch (packet_type){ + case HCI_COMMAND_DATA_PACKET: + _hci_cmd_buf[0] = HCI_COMMAND_DATA_PACKET; + break; + case HCI_ACL_DATA_PACKET: + _hci_cmd_buf[0] = HCI_ACL_DATA_PACKET; + break; + default: + // @TODO + break; + } + + memcpy(&_hci_cmd_buf[1], packet, size); + esp_vhci_host_send_packet(_hci_cmd_buf, size + 1); + + _can_send_packet_now = 0; + return 0; } @@ -130,9 +164,9 @@ static const hci_transport_t transport = { &transport_register_packet_handler, &transport_can_send_packet_now, &transport_send_packet, - NULL, - NULL, - NULL, + NULL, // set baud rate + NULL, // reset link + NULL, // set SCO config }; static const hci_transport_t * transport_get_instance(void){ @@ -178,17 +212,35 @@ static void btstack_setup(void){ extern int btstack_main(int argc, const char * argv[]); +/* + * @brief: send HCI commands to perform BLE advertising; + */ +void btstack_task(void *pvParameters) +{ + esp_vhci_host_register_callback(&vhci_host_cb); + printf("btstack_task start\n"); + + printf("-------------- btstack_setup\n"); + btstack_setup(); + printf("-------------- btstack_setup end\n"); + + printf("-------------- btstack_main\n"); + btstack_main(0, NULL); + printf("-------------- btstack_main end\n"); + + printf("Entering btstack run loop!\n"); + btstack_run_loop_execute(); + printf("Run loop exited...this is unexpected\n"); +} + +void hal_led_toggle() {} + // main int app_main(void){ esp_bt_controller_init(); - btstack_setup(); - btstack_main(0, NULL); - - printf("Entering btstack run loop!\n"); - btstack_run_loop_execute(); - printf("Run loop exited...this is unexpected\n"); + xTaskCreatePinnedToCore(&btstack_task, "btstack_task", 2048, NULL, 5, NULL, 0); return 0; }