前一篇文章介紹透過BLE advertisement將溫濕度感應器的數值以Advertising packet傳送給附近的其他BLE browsers(scaners),不須經過connect即可獲得資料。
本篇文章介紹如何使用BTstack建立簡單的GATT server 與GATT client,將溫濕度透過ATT protocol傳送給其他設備。
一、ATT server
1. 首先要建立GATT profile: services & characteristics
(source: Bluetooth specification document)
在本範例中:建立三個primary server分別為
GAP_SERVICE、GATT_SERVICE與ORG_BLUETOOTH_SERVICE_ENVIRONMENTAL_SENSING
其分別包還的Characteristics如下圖所示:
將上述內容建立成gatt_ht_server.gatt,使用BTstack 工具compile_gatt.py將檔案轉換成相對應的文件規範格式。可將此編譯指令建立在CMakeLists.txt檔案中。可改成
pico_btstack_make_gatt_header(le_sm_peripheral PRIVATE "${CMAKE_CURRENT_LIST_DIR}/gatt_ht-server.gatt")
advertisement data只包含以下三個簡單的data type
(1) profile_data: 由上述compile_gatt.py產生的attribute database。
(2)att_read_callback: client read character呼叫的callback function。
(3)att_write_callbak: client write request呼叫的callback function。
設定timer_resource透過att_server_notify定時對client端發送notification。
詳細的程式碼附於文章末尾。
二、ATT Client:
1. scan advertisement、connect server、discovery service/characteristics、read/write characteristics and receive server notification流程如下圖所示。
相對應完整程式碼附於文章末尾
三、成果影片
四、程式碼
- gatt_ht_server.c
#include <stdio.h> #include "pico/stdlib.h" #include "btstack.h" #include "pico/cyw43_arch.h" #include "dht11/dht11.h" #include "gatt_ht_server.h" #include "math.h" #define HEARTBEAT_PERIOD_MS 3000 uint8_t adv_data[] = { // Flags: 0x02: General Discoverable Mode, 0x04:BR/EDR Not Supported 0x02, BLUETOOTH_DATA_TYPE_FLAGS, 0x06, // Local Name 0x09, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'P', 'i', 'c', 'o', 'W', '-', 'H','T', 0x03, BLUETOOTH_DATA_TYPE_INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, 0x1a, 0x18, }; uint8_t adv_data_len = sizeof(adv_data); 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); float temp; float humi; uint16_t temp_i, humi_i; bool le_notification_enabled; hci_con_handle_t con_handle; static btstack_packet_callback_registration_t hci_event_callback_registration; static btstack_timer_source_t temp_humi_noti; 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){ if (att_handle == ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_TEMPERATURE_01_VALUE_HANDLE){ att_server_request_can_send_now_event(connection_handle); return att_read_callback_handle_little_endian_16(temp_i, offset, buffer, buffer_size); } if (att_handle == ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_HUMIDITY_01_VALUE_HANDLE){ uint16_t ret = att_read_callback_handle_little_endian_16(humi_i, offset, buffer, buffer_size); att_server_request_can_send_now_event(connection_handle); return ret; } return 0; } 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_ORG_BLUETOOTH_CHARACTERISTIC_TEMPERATURE_01_CLIENT_CONFIGURATION_HANDLE || att_handle == ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_HUMIDITY_01_CLIENT_CONFIGURATION_HANDLE) { le_notification_enabled = little_endian_read_16(buffer, 0) == GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION; con_handle = connection_handle; } return 0; } static void temp_humi_handle(struct btstack_timer_source *ts){ if (le_notification_enabled) { if (dht11_get_data(&temp, &humi)) { temp_i=(uint16_t)round(temp*100); humi_i=(uint16_t)round(humi*100); printf("temp:%f, humi:%f\n", temp, humi); att_server_request_can_send_now_event(con_handle); } } btstack_run_loop_set_timer(ts, HEARTBEAT_PERIOD_MS); btstack_run_loop_add_timer(ts); } static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ UNUSED(channel); UNUSED(size); if (packet_type != HCI_EVENT_PACKET) return; 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_ORG_BLUETOOTH_CHARACTERISTIC_TEMPERATURE_01_VALUE_HANDLE, (uint8_t*)&temp_i, 2); att_server_notify(con_handle, ATT_CHARACTERISTIC_ORG_BLUETOOTH_CHARACTERISTIC_HUMIDITY_01_VALUE_HANDLE, (uint8_t*)&humi_i, 2); break; default: break; } } int main() { stdio_init_all(); if (cyw43_arch_init()) { printf("cyw43_init error\n"); return 0; } l2cap_init(); sm_init(); dht11_init(); // 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); // setup ATT server att_server_init(profile_data, att_read_callback, att_write_callback); // register for HCI events hci_event_callback_registration.callback = &packet_handler; hci_add_event_handler(&hci_event_callback_registration); // register for ATT event att_server_register_packet_handler(packet_handler); // set timer temp_humi_noti.process = &temp_humi_handle; btstack_run_loop_set_timer(&temp_humi_noti, HEARTBEAT_PERIOD_MS); btstack_run_loop_add_timer(&temp_humi_noti); hci_power_control(HCI_POWER_ON); while(1) { tight_loop_contents(); } return 0; }
- gatt_ht_server.gatt
PRIMARY_SERVICE, GAP_SERVICE CHARACTERISTIC, GAP_DEVICE_NAME, READ, "PicoW-HT" PRIMARY_SERVICE, GATT_SERVICE CHARACTERISTIC, GATT_DATABASE_HASH, READ, // Environment PRIMARY_SERVICE, ORG_BLUETOOTH_SERVICE_ENVIRONMENTAL_SENSING // Temperature Characteristic, with read and notify CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_TEMPERATURE, READ | NOTIFY | DYNAMIC, // Humidity Characteristic, with read and notify CHARACTERISTIC, ORG_BLUETOOTH_CHARACTERISTIC_HUMIDITY, READ | NOTIFY | DYNAMIC,
- CMakeLists.txt(server)
execute_process(COMMAND /home/duser/pico/pico-sdk/lib/btstack/tool/compile_gatt.py gatt_ht_server.gatt gatt_ht_server.h WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} ECHO_OUTPUT_VARIABLE ECHO_ERROR_VARIABLE ) # Generated Cmake Pico project file cmake_minimum_required(VERSION 3.13) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) # Initialise pico_sdk from installed location # (note this can come from environment, CMake cache etc) set(PICO_SDK_PATH "/home/duser/pico/pico-sdk") set(PICO_BOARD pico_w CACHE STRING "Board type") # Pull in Raspberry Pi Pico SDK (must be before project) include(pico_sdk_import.cmake) if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") endif() project(gatt_ht_server C CXX ASM) # Initialise the Raspberry Pi Pico SDK pico_sdk_init() # Add executable. Default name is the project name, version 0.1 add_executable(gatt_ht_server gatt_ht_server.c ) pico_set_program_name(gatt_ht_server "gatt_ht_server") pico_set_program_version(gatt_ht_server "0.1") pico_enable_stdio_uart(gatt_ht_server 1) pico_enable_stdio_usb(gatt_ht_server 0) # Add the standard library to the build target_link_libraries(gatt_ht_server pico_stdlib pico_cyw43_arch_none pico_btstack_cyw43 pico_btstack_ble) # Add the standard include files to the build target_include_directories(gatt_ht_server PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required ) add_subdirectory(dht11) target_link_libraries(gatt_ht_server dht11) pico_add_extra_outputs(gatt_ht_server)
- gatt_ht_client.c
#include <stdio.h> #include "pico/stdlib.h" #include "btstack.h" #include "pico/cyw43_arch.h" #include "pico_tft.h" #include "fonts/font_fixedsys_mono_16.h" #include "tft_string/tft_string.h" #define SERVER_DEVICE_NAME "PicoW-HT" typedef enum { CLIENT_STOP=0, QUERY_SERVICE, QUERY_CHARACTERISTIC_TEMPERATURE, QUERY_CHARACTERISTIC_HUMIDITY, ENABLE_NOTIFICATION, ENABLE_COMPLETE } client_event_query_t; float rtemp, rhumi, ntemp, nhumi; client_event_query_t client_event_query; gatt_client_notification_t notification_listener_temp, notification_listener_humi; static hci_con_handle_t connection_handler; gatt_client_service_t environmental_sensing; gatt_client_characteristic_t temperature, humidity; static btstack_packet_callback_registration_t hci_event_callback_registration; static void handle_hci_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); static void tft_show_message(uint8_t *msg); static void tft_print_data(); uint8_t advertisement_report_find_device(uint16_t uuid16, uint8_t * name, 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; uint8_t local_name[40]; uint8_t found = 0; 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); int i; switch (data_type){ case BLUETOOTH_DATA_TYPE_INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS: for (i=0; i<data_size;i+=2){ if (little_endian_read_16(data, i) == uuid16) { found = found | 0x1; break; } } break; case BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME: memcpy(local_name, data, data_size); local_name[data_size]='\0'; if (strcmp(local_name, name) == 0) { found = found | 0x2; } break; default: break; } } return found; } static void handle_hci_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ UNUSED(channel); UNUSED(size); bd_addr_t addr; bd_addr_type_t addr_type; if (packet_type != HCI_EVENT_PACKET) return; uint8_t event = hci_event_packet_get_type(packet); switch (event) { case BTSTACK_EVENT_STATE: // BTstack activated, get started if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) break; gap_set_scan_parameters(0,0x0030, 0x0030); gap_start_scan(); break; case GAP_EVENT_ADVERTISING_REPORT: tft_show_message("Scan Device..."); if (advertisement_report_find_device(ORG_BLUETOOTH_SERVICE_ENVIRONMENTAL_SENSING, SERVER_DEVICE_NAME, packet)==0x3) { gap_event_advertising_report_get_address(packet, addr); addr_type = gap_event_advertising_report_get_address_type(packet); gap_stop_scan(); gap_connect(addr, addr_type); } break; case HCI_EVENT_LE_META: // wait for connection complete if (hci_event_le_meta_get_subevent_code(packet) != HCI_SUBEVENT_LE_CONNECTION_COMPLETE) break; connection_handler = hci_subevent_le_connection_complete_get_connection_handle(packet); // query primary services client_event_query=QUERY_SERVICE; gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, connection_handler, ORG_BLUETOOTH_SERVICE_ENVIRONMENTAL_SENSING); break; case HCI_EVENT_DISCONNECTION_COMPLETE: tft_show_message("Device Disconn"); client_event_query = CLIENT_STOP; gap_start_scan(); break; default: break; } } 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); uint16_t temp; switch(hci_event_packet_get_type(packet)){ case GATT_EVENT_SERVICE_QUERY_RESULT: gatt_event_service_query_result_get_service(packet, &environmental_sensing); break; case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT: if (client_event_query == QUERY_CHARACTERISTIC_TEMPERATURE) { gatt_event_characteristic_query_result_get_characteristic(packet, &temperature); } if (client_event_query == QUERY_CHARACTERISTIC_HUMIDITY) { gatt_event_characteristic_query_result_get_characteristic(packet, &humidity); } break; case GATT_EVENT_QUERY_COMPLETE: switch(client_event_query) { case QUERY_SERVICE: client_event_query = QUERY_CHARACTERISTIC_TEMPERATURE; gatt_client_discover_characteristics_for_service_by_uuid16(handle_gatt_client_event, connection_handler, &environmental_sensing, ORG_BLUETOOTH_CHARACTERISTIC_TEMPERATURE); break; case QUERY_CHARACTERISTIC_HUMIDITY: client_event_query = ENABLE_NOTIFICATION; gatt_client_listen_for_characteristic_value_updates(¬ification_listener_temp, handle_gatt_client_event, connection_handler, &temperature); gatt_client_listen_for_characteristic_value_updates(¬ification_listener_humi, handle_gatt_client_event, connection_handler, &humidity); // enable notifications gatt_client_write_client_characteristic_configuration(handle_gatt_client_event, connection_handler, &temperature, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION); //gatt_client_write_client_characteristic_configuration(handle_gatt_client_event, connection_handler, // &humidity, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION); break; case QUERY_CHARACTERISTIC_TEMPERATURE: client_event_query = QUERY_CHARACTERISTIC_HUMIDITY; gatt_client_discover_characteristics_for_service_by_uuid16(handle_gatt_client_event, connection_handler, &environmental_sensing,ORG_BLUETOOTH_CHARACTERISTIC_HUMIDITY); break; case ENABLE_NOTIFICATION: client_event_query = ENABLE_COMPLETE; break; } break; case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT: if (humidity.value_handle == gatt_event_characteristic_value_query_result_get_value_handle(packet)) { temp = little_endian_read_16(gatt_event_characteristic_value_query_result_get_value(packet), 0); rhumi = temp/100.0; tft_print_data(); printf("humidity read:%.02f%c\n", rhumi, '%'); } if (temperature.value_handle == gatt_event_characteristic_value_query_result_get_value_handle(packet)) { temp = little_endian_read_16(gatt_event_characteristic_value_query_result_get_value(packet), 0); rtemp = temp/100.0; tft_print_data(); printf("temperature read:%.02f C\n", rtemp); } break; case GATT_EVENT_NOTIFICATION: if (temperature.value_handle == gatt_event_notification_get_value_handle(packet)) { ntemp = little_endian_read_16(gatt_event_notification_get_value(packet), 0)/100.0; tft_print_data(); printf("temp:%.02f C\n", ntemp); } if (humidity.value_handle == gatt_event_notification_get_value_handle(packet)) { nhumi = little_endian_read_16(gatt_event_notification_get_value(packet), 0)/100.0; tft_print_data(); printf("humi:%.02f %c\n", nhumi, '%'); } break; case ATT_EVENT_CAN_SEND_NOW: printf("att send\n"); break; default: break; } } void tft_show_message(uint8_t* msg) { tft_fill_rect(0,0,TFT_WIDTH, TFT_HEIGHT, 0xffff); tft_draw_string(20,5, "ATT Read:", 0xf800, &font_fixedsys_mono_16); } void tft_print_data() { char ts[50]; tft_fill_rect(0,0,TFT_WIDTH, TFT_HEIGHT, 0xffff); tft_draw_string(20,5, "ATT Read:", 0xf800, &font_fixedsys_mono_16); sprintf(ts, "T:%.02f C, H:%.0f%c", rtemp, rhumi, '%'); tft_draw_string(10, 30, ts, 0x001f, &font_fixedsys_mono_16); tft_draw_string(10,70, "ATT Notification:", 0xf800, &font_fixedsys_mono_16); sprintf(ts, "T:%.02f C, H:%.0f%c", ntemp, nhumi, '%'); tft_draw_string(10, 95, ts, 0x001f, &font_fixedsys_mono_16); } int main() { stdio_init_all(); tft_init(); if (cyw43_arch_init()) { printf("cyw43_init error\n"); return 0; } client_event_query = CLIENT_STOP; l2cap_init(); sm_init(); // setup GATT client l2cap_init(); // Initialize GATT client gatt_client_init(); // Optinoally, Setup security manager sm_init(); sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT); // register for HCI events hci_event_callback_registration.callback = &handle_hci_event; hci_add_event_handler(&hci_event_callback_registration); hci_power_control(HCI_POWER_ON); while(1) { if (client_event_query == ENABLE_COMPLETE) { sleep_ms(4500); gatt_client_read_value_of_characteristics_by_uuid16(handle_gatt_client_event, connection_handler, temperature.start_handle, temperature.end_handle, ORG_BLUETOOTH_CHARACTERISTIC_TEMPERATURE); sleep_ms(100); gatt_client_read_value_of_characteristics_by_uuid16(handle_gatt_client_event, connection_handler, humidity.start_handle, humidity.end_handle, ORG_BLUETOOTH_CHARACTERISTIC_HUMIDITY); } tight_loop_contents(); } return 0; }
- gatt_ht_client.gatt
PRIMARY_SERVICE, GAP_SERVICE CHARACTERISTIC, GAP_DEVICE_NAME, READ, "GATT HT Client"
沒有留言:
張貼留言