prettyprint

2023年6月26日 星期一

[Raspberry Pi Pico W] BTstack: Ep 4. HID device, Custom Bluetooth HID keypad

 在上篇將有線鍵盤透過Raspberry Pi Pico W轉換成無線藍芽鍵盤,

HID device, Convert a USB Cabled Keyboard to a Bluetooth Wireless Keyboard


本篇文章整合4x4 keypad pio程式,自訂一個藍芽鍵盤,如下圖。
  1. 4x4 keypad連接pico w pin 8 ~ pin 15,輸入如key pad上的內容。
  2. 左邊按鈕送出"F1" 功能鍵, key cod為0x3a。
  3. 右邊按鈕送出一串文字。
hid report包含modifier與scan code如下圖所示。
其他詳細文件說明,請參閱前一篇。

本實驗直接使用BTstack example程式hid_keyboard_demo.c。將363行註解。
另外製作送出keypad按鍵與特殊鍵功能。
詳細程式碼附於文末。

成果影片

  • Rebuild this project step by step:
  1. Generate the initial project structure:
    Download pico_project.py from https://github.com/raspberrypi/pico-project-generator
    $ pico_project.py --gui
  2. Copy the 4x4 keypad library:
    Copy (2)keypad.c (3)keypad.h (4)keypad.pio (5)CMakeLists.txt files to pico_keypad directory.
  3. Copy the (1)BT_custom_keypad.c to project directory
  4. Copy (6)btstack_config.h to the project root directory
  5. Copy hid_keyboard_demo.c in pico-sdk/lib/btstack/example folder to the project root directory, and comment out line 363[//demo_text_timer_hanlder(NULL)].
  6. Add the required libraries into CMakeLists.txt in root directory.

  • 程式碼:



(1) main code:BT_custom_keypad.c
 #include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"
#include "hid_keyboard_demo.c"
#include "keypad.h"

#define F1_BUTTON   16
#define TYPE_STRING_BUTTON 17

void send_hid_text(uint8_t* text) {
    btstack_ring_buffer_write(&send_buffer, text, strlen(text));
    send_next(NULL);  
    busy_wait_ms(100);      
}

void send_hid_char(uint8_t c) {
    btstack_ring_buffer_write(&send_buffer, (uint8_t*)&c, 1);
    send_next(NULL);
    busy_wait_ms(100);
}

void send_hid_spec_code(uint8_t m, uint8_t c) {
    send_modifier = m;
    send_keycode = c;
    hid_device_request_can_send_now_event(hid_cid);
}

void key_button_callback(uint gpio, uint32_t events) {
    gpio_set_irq_enabled(gpio, GPIO_IRQ_EDGE_FALL, false);
    gpio_acknowledge_irq(gpio, events);
    
    if (gpio == F1_BUTTON ) {       
        busy_wait_ms(100);     
        send_hid_spec_code(0, 0x3a); //F1 key
        gpio_set_irq_enabled(gpio, GPIO_IRQ_EDGE_FALL, true);
    }
    if (gpio == TYPE_STRING_BUTTON) {
        busy_wait_ms(100);
        send_hid_text("messages...\n");
        gpio_set_irq_enabled(gpio, GPIO_IRQ_EDGE_FALL, true);
    }

}
int main()
{
    stdio_init_all();
    if (cyw43_arch_init()) {
        printf("cyw43 arch init error\n");
        return 0;
    }

    gpio_init(F1_BUTTON);
    gpio_init(TYPE_STRING_BUTTON);
    gpio_pull_up(F1_BUTTON);
    gpio_pull_up(TYPE_STRING_BUTTON);
    gpio_set_irq_enabled_with_callback(F1_BUTTON, GPIO_IRQ_EDGE_FALL, true, key_button_callback);
    gpio_set_irq_enabled_with_callback(TYPE_STRING_BUTTON, GPIO_IRQ_EDGE_FALL, true, key_button_callback);
    keypad_init();
   
    btstack_main(0, NULL);
    gap_set_local_name("PicoW HID Keypad");

   
    uint8_t c;
    while(1) {
        c=get_new_keypad_value();
        
        if (c) {
            send_hid_char(c);
        } else {
            sleep_ms(10);
        }
        
        tight_loop_contents();
    }

    return 0;
}

(2) keypad.c
 #include "keypad.pio.h"
#include "keypad.h"
#include "hardware/clocks.h"
#include "stdio.h"
#include "pico/stdlib.h"


static uint8_t key_value=0;

static const PIO keypad_pio=pio1;
static const uint keypad_sm=0;
static const uint8_t keys[4][4]={
    {'1','2','3','A'},
    {'4','5','6','B'},
    {'7','8','9','C'},
    {'*','0','#','D'}
    };

static void keypad_handle() {
    if (pio_interrupt_get(keypad_pio, 0)) {
        key_value=0;
        pio_interrupt_clear(keypad_pio, 0);
        uint32_t x, y;
          y=pio_sm_get_blocking(keypad_pio, keypad_sm);
        x=pio_sm_get_blocking(keypad_pio, keypad_sm);

        for(uint8_t i = 0 ; i < 4; i++){
            if ((x >> i)==1) {x=i;break;}
        }
        for(uint8_t j = 0 ; j < 4; j++){
            if ((y >> j)==1) {y=j;break;}
        }
        key_value = keys[x][y];
    }
}

static void keypad_pio_init(PIO pio, uint sm, uint set_base, uint in_base, uint freq) {
    uint offset=0;
    pio_sm_config c;
    offset = pio_add_program(pio, &keypad_program);
    c = keypad_program_get_default_config(offset);
    
    for (int i=0; i < 4; i++) pio_gpio_init(pio, in_base+i);
    for (int i=0; i < 4; i++) pio_gpio_init(pio, set_base+i);

    pio_sm_set_consecutive_pindirs(pio, sm, in_base, 4, false);
    pio_sm_set_consecutive_pindirs(pio, sm, set_base, 4, true);

    sm_config_set_in_pins(&c, in_base);
    sm_config_set_set_pins(&c, set_base, 4);
   
    sm_config_set_in_shift(&c, false, false, 32);

    float div = clock_get_hz(clk_sys)/freq;
    sm_config_set_clkdiv(&c, div);

    uint pio_irq = pio_get_index(pio)? PIO1_IRQ_0:PIO0_IRQ_0;
    pio_set_irq0_source_enabled(pio, pis_interrupt0, true);
    irq_add_shared_handler(pio_irq, keypad_handle, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    irq_set_enabled(pio_irq, true);

    pio_sm_init(pio, sm, offset, &c);

    pio_sm_set_enabled(pio, sm, true);
}
uint8_t get_new_keypad_value() {
    uint8_t ret_vale = key_value;
    key_value=0;
    return ret_vale;
}
void keypad_init() {
    keypad_pio_init(keypad_pio, keypad_sm,  8, 12, 2000);
}
(3) keypad.h
 #ifndef __KEYPAD_H
#define __KEYPAD_H


void keypad_init();
uint8_t get_new_keypad_value();

#endif
(4) keypad.pio
 .program keypad
.wrap_target
set_row_1:
set pins, 1 [31]  ;set row_1 High and wait for button bounce
set x,0x1
in pins,4
mov y, isr
jmp !y set_row_2
jmp rx_fifo
    
set_row_2:
set pins, 2  [31]
set x,0x2 
in pins,4  
mov y, isr  
jmp !y set_row_3  
jmp rx_fifo   

set_row_3: 
set pins, 4   [31]
set x,0x4  
in pins,4  
mov y, isr  
jmp !y  set_row_4  
jmp rx_fifo

set_row_4:
set pins, 8   [31]
set x,0x8  
in pins,4  
mov y, isr  
jmp !y  set_row_1

rx_fifo:  
push                  ;push y col   
in x,4  [2]
push                  ;and then x row  
irq  0 rel

wait 0 pin 0     ; check whether key is released
wait 0 pin 1  
wait 0 pin 2  
wait 0 pin 3  
.wrap
(5) CMakeLists.txt(keypad)
 add_library(pico_keypad INTERFACE)
pico_generate_pio_header(pico_keypad ${CMAKE_CURRENT_LIST_DIR}/keypad.pio)
target_sources(pico_keypad INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/keypad.c
)

target_include_directories(pico_keypad INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
)

target_link_libraries(pico_keypad INTERFACE
        hardware_pio
)
(6)btstack_config.h
#ifndef _PICO_BTSTACK_BTSTACK_CONFIG_H
#define _PICO_BTSTACK_BTSTACK_CONFIG_H

// BTstack features that can be enabled
#ifdef ENABLE_BLE
#define ENABLE_LE_PERIPHERAL
#define ENABLE_LE_CENTRAL
#define ENABLE_L2CAP_LE_CREDIT_BASED_FLOW_CONTROL_MODE
#endif

#ifdef ENABLE_MESH
// Mesh Config
#define ENABLE_MESH_ADV_BEARER
#define ENABLE_MESH_GATT_BEARER
#define ENABLE_MESH_PB_ADV
#define ENABLE_MESH_PB_GATT
#define ENABLE_MESH_PROXY_SERVER
#define ENABLE_MESH_RELAY

#define ENABLE_MESH_PROVISIONER

#define MAX_NR_MESH_SUBNETS            2
#define MAX_NR_MESH_TRANSPORT_KEYS    16
#define MAX_NR_MESH_VIRTUAL_ADDRESSES 16
// allow for one NetKey update
#define MAX_NR_MESH_NETWORK_KEYS      (MAX_NR_MESH_SUBNETS+1)
#endif 


#define ENABLE_LOG_INFO
#define ENABLE_LOG_ERROR
#define ENABLE_PRINTF_HEXDUMP
#define ENABLE_SCO_OVER_HCI

// BTstack configuration. buffers, sizes, ...
#define HCI_OUTGOING_PRE_BUFFER_SIZE 4
#define HCI_ACL_PAYLOAD_SIZE (1691 + 4)
#define HCI_ACL_CHUNK_SIZE_ALIGNMENT 4
#define MAX_NR_AVDTP_CONNECTIONS 1
#define MAX_NR_AVDTP_STREAM_ENDPOINTS 1
#define MAX_NR_AVRCP_CONNECTIONS 2
#define MAX_NR_BNEP_CHANNELS 1
#define MAX_NR_BNEP_SERVICES 1
#define MAX_NR_BTSTACK_LINK_KEY_DB_MEMORY_ENTRIES  2
#define MAX_NR_GATT_CLIENTS 1
#define MAX_NR_HCI_CONNECTIONS 2
#define MAX_NR_HID_HOST_CONNECTIONS 1
#define MAX_NR_HIDS_CLIENTS 1
#define MAX_NR_HFP_CONNECTIONS 1
#define MAX_NR_L2CAP_CHANNELS  4
#define MAX_NR_L2CAP_SERVICES  3
#define MAX_NR_RFCOMM_CHANNELS 1
#define MAX_NR_RFCOMM_MULTIPLEXERS 1
#define MAX_NR_RFCOMM_SERVICES 1
#define MAX_NR_SERVICE_RECORD_ITEMS 4
#define MAX_NR_SM_LOOKUP_ENTRIES 3
#define MAX_NR_WHITELIST_ENTRIES 16
#define MAX_NR_LE_DEVICE_DB_ENTRIES 16

// Limit number of ACL/SCO Buffer to use by stack to avoid cyw43 shared bus overrun
#define MAX_NR_CONTROLLER_ACL_BUFFERS 3
#define MAX_NR_CONTROLLER_SCO_PACKETS 3

// Enable and configure HCI Controller to Host Flow Control to avoid cyw43 shared bus overrun
#define ENABLE_HCI_CONTROLLER_TO_HOST_FLOW_CONTROL
#define HCI_HOST_ACL_PACKET_LEN 1024
#define HCI_HOST_ACL_PACKET_NUM 3
#define HCI_HOST_SCO_PACKET_LEN 120
#define HCI_HOST_SCO_PACKET_NUM 3

// Link Key DB and LE Device DB using TLV on top of Flash Sector interface
#define NVM_NUM_DEVICE_DB_ENTRIES 16
#define NVM_NUM_LINK_KEYS 16

// We don't give btstack a malloc, so use a fixed-size ATT DB.
#define MAX_ATT_DB_SIZE 512

// BTstack HAL configuration
#define HAVE_EMBEDDED_TIME_MS

// map btstack_assert onto Pico SDK assert()
#define HAVE_ASSERT

// Some USB dongles take longer to respond to HCI reset (e.g. BCM20702A).
#define HCI_RESET_RESEND_TIMEOUT_MS 1000

#define ENABLE_SOFTWARE_AES128
#define ENABLE_MICRO_ECC_FOR_LE_SECURE_CONNECTIONS

//#define HAVE_BTSTACK_STDIN

// To get the audio demos working even with HCI dump at 115200, this truncates long ACL packets
//#define HCI_DUMP_STDOUT_MAX_SIZE_ACL 100

#ifdef ENABLE_CLASSIC
#define ENABLE_L2CAP_ENHANCED_RETRANSMISSION_MODE
#endif

#define HAVE_MALLOC

#endif // _PICO_BTSTACK_BTSTACK_CONFIG_H

2023年6月23日 星期五

[Raspberry Pi Pico W] BLE Ep 3. BTstack Security manager, ATT read, write and notification

本文章介紹在Raspbery Pi Pico W下使用BTstack BLE功能。

實作peripheral 和central 使用LE secure connection。

central device使用ATT write來控制peripheral上的LED燈條的亮度,  peripheral device使用ATT notification告知central device現在設定led的亮度, central device將資訊顯示在TFT display上。

peripheral 和central device實作如下圖所示。


一、security manager:
每個device通知配對連線對象的IO capability如下:
  1. DisplayOnly: Device can display numbers or text but cannot accept input
  2. KeyboardOnly: Device can accept text or numeric input from the user
  3. DisplayYesNo: Device allows the user to respond with YES or NO
  4. NoInputNoOutput: Device has no input or output capabilities a user can utilize
  5. KeyboardDisplay: Device has both a keyboard and a display

1. Central device SM設定:
sm_init();
// enable LE Secure Connections Only mode - disables Legacy pairing
sm_set_secure_connections_only_mode(true);
// LE Secure Connections, Numeric Comparison
sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_ONLY);
sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION|SM_AUTHREQ_MITM_PROTECTION |SM_AUTHREQ_BONDING);
2.Peripheral device SM設定:
sm_init();
// enable LE Secure Connections Only mode - disables Legacy pairing
sm_set_secure_connections_only_mode(true);
// LE Secure Connections, Numeric Comparison
sm_set_io_capabilities(IO_CAPABILITY_KEYBOARD_ONLY);
sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION|SM_AUTHREQ_MITM_PROTECTION |SM_AUTHREQ_BONDING);


二、ATT read, write, notification:
本實驗使用custom 128bits UUID, ATT attributes如下所示:
  • peripheral device處理HCI, ATT and SM event相對應的handler:
  • central device端處理HCI, ATT, SM相對應的handler:
ATT handler:

  • ATT database:
Pico SDK 1.5.1後在CMake增加了pico_btstack_make_gatt_header函數將.gatt轉成.h ATT database。
  • btstack_config.h 啟用LE功能。
詳細程式碼附於文末。

三、成果展示:


四、程式碼:
  • le_peripheral.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "le_peripheral.h"
#include "pico/cyw43_arch.h"
#include "btstack.h"

#include "inttypes.h"
#include "keypad.h"
#include "ws2812.h"

static btstack_packet_callback_registration_t hci_event_callback_registration;
static btstack_packet_callback_registration_t sm_event_callback_registration;
static void hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
static void att_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
static void sm_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);

static uint16_t led_brightness=0;
static uint8_t led_brightness_scale=10;

hci_con_handle_t con_handle;
bool le_notification_enabled=false;


const uint8_t adv_data[] = {
    // Flags general discoverable, BR/EDR not supported
    0x02, BLUETOOTH_DATA_TYPE_FLAGS, 0x06, 
    // Name
    0x07, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'S','M',' ','L','E','D', 

    0x11, BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS,
    0x40, 0x88,0x12,0x7D,0x32,0x69,0x92,0x95, 0x5D,0x4E, 0xE4,0x37,0x60, 0x06,0x00, 0x00,
};
const uint8_t adv_data_len = sizeof(adv_data);

void set_led_birghness(uint16_t brightness) {

    ws2812_play(brightness, led_brightness_scale);
    if (le_notification_enabled) {
        att_server_request_can_send_now_event(con_handle);

    }

}
uint32_t get_passkey() {
    uint8_t passkey[6];
    char *pt;
    uint8_t one_digit;
    uint8_t pos=0;
    absolute_time_t timeout = get_absolute_time();
    while (1) {
        one_digit=get_new_keypad_value();
        if(one_digit) {
            passkey[pos++] = one_digit;
            printf("%c", one_digit);
            if (pos >=6) break;
        }
        busy_wait_ms(1);
        if (absolute_time_diff_us(timeout, get_absolute_time())> 15000000U) break;
    }
    //passkey[6]='0';
    //printf("passkey:%u\n", atol(passkey));
    return strtoul(passkey, &pt, 10);
}

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_00000661_37E4_4E5D_9592_69327D128840_01_VALUE_HANDLE){          
            uint16_t ret = att_read_callback_handle_little_endian_16(led_brightness, offset, buffer, buffer_size);
            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_00000662_37E4_4E5D_9592_69327D128840_01_VALUE_HANDLE) {
        led_brightness = little_endian_read_16(buffer, 0);
        set_led_birghness(led_brightness);
        return sizeof(uint16_t);
    }
    if (att_handle == ATT_CHARACTERISTIC_00000663_37E4_4E5D_9592_69327D128840_01_VALUE_HANDLE) {
        led_brightness_scale = little_endian_read_16(buffer, 0);
        //printf("led_brightness_scale:%d\n", led_brightness_scale);
        return sizeof(uint16_t);
    }

    if (att_handle == ATT_CHARACTERISTIC_00000661_37E4_4E5D_9592_69327D128840_01_CLIENT_CONFIGURATION_HANDLE) {
            le_notification_enabled = little_endian_read_16(buffer, 0) == GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION;
            con_handle = connection_handle;
            att_server_request_can_send_now_event(con_handle);
    }
    return 0;
}
static void att_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 ATT_EVENT_CAN_SEND_NOW:   

            if (att_server_notify(con_handle, ATT_CHARACTERISTIC_00000661_37E4_4E5D_9592_69327D128840_01_VALUE_HANDLE, 
                        (uint8_t*)&led_brightness, 2)) {
  
            }   
            break;
        default:
            break;
    }
}
static void hci_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;
    hci_con_handle_t con_handle;
    uint8_t status;

    switch (hci_event_packet_get_type(packet)) {
        case BTSTACK_EVENT_STATE:
            // BTstack activated, get started
            if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING){
                
            }
            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;
            con_handle = hci_subevent_le_connection_complete_get_connection_handle(packet);
            //sm_bonding_decline(con_handle);
            sm_request_pairing(con_handle);
            //gap_advertisements_enable(false);
            break;
        
        case HCI_EVENT_DISCONNECTION_COMPLETE:
            le_notification_enabled=false;
            printf("disconnect...\n");
            //gap_advertisements_enable(true);
            break;
        default:
            break;
    }
}


static void sm_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;

    bd_addr_t addr;
    bd_addr_type_t addr_type;

    switch (hci_event_packet_get_type(packet)) {
        case SM_EVENT_JUST_WORKS_REQUEST:
            printf("Just works requested\n");
            sm_just_works_confirm(sm_event_just_works_request_get_handle(packet));
            break;
        case SM_EVENT_NUMERIC_COMPARISON_REQUEST:
            printf("Confirming numeric comparison: %"PRIu32"\n", sm_event_numeric_comparison_request_get_passkey(packet));
            sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet));
            break;
        case SM_EVENT_PASSKEY_DISPLAY_NUMBER:
            printf("Display Passkey: %"PRIu32"\n", sm_event_passkey_display_number_get_passkey(packet));
            break;
        case SM_EVENT_PASSKEY_INPUT_NUMBER:
            printf("Passkey Input requested\n");
            uint32_t passkey;
            passkey = get_passkey();
            printf("Sending fixed passkey %"PRIu32"\n", passkey);
            sm_passkey_input(sm_event_passkey_input_number_get_handle(packet), passkey);
            break;
        case SM_EVENT_PAIRING_STARTED:
            printf("Pairing started\n");
            break;
        case SM_EVENT_PAIRING_COMPLETE:
            switch (sm_event_pairing_complete_get_status(packet)){
                case ERROR_CODE_SUCCESS:
                    printf("Pairing complete, success\n");
                    break;
                case ERROR_CODE_CONNECTION_TIMEOUT:
                    printf("Pairing failed, timeout\n");
                    break;
                case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION:
                    printf("Pairing failed, disconnected\n");
                    break;
                case ERROR_CODE_AUTHENTICATION_FAILURE:
                    printf("Pairing failed, authentication failure with reason = %u\n", sm_event_pairing_complete_get_reason(packet));
                    break;
                default:
                    break;
            }
            break;
        case SM_EVENT_REENCRYPTION_STARTED:
            sm_event_reencryption_complete_get_address(packet, addr);
            printf("Bonding information exists for addr type %u, identity addr %s -> start re-encryption\n",
                   sm_event_reencryption_started_get_addr_type(packet), bd_addr_to_str(addr));
            break;
        case SM_EVENT_REENCRYPTION_COMPLETE:
            switch (sm_event_reencryption_complete_get_status(packet)){
                case ERROR_CODE_SUCCESS:
                    printf("Re-encryption complete, success\n");
                    break;
                case ERROR_CODE_CONNECTION_TIMEOUT:
                    printf("Re-encryption failed, timeout\n");
                    break;
                case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION:
                    printf("Re-encryption failed, disconnected\n");
                    sm_event_reencryption_complete_get_address(packet, addr);
                    addr_type = sm_event_reencryption_started_get_addr_type(packet);
                    gap_delete_bonding(addr_type, addr);
                    sm_request_pairing(sm_event_reencryption_complete_get_handle(packet));
                    break;
                case ERROR_CODE_PIN_OR_KEY_MISSING:
                    printf("Re-encryption failed, bonding information missing\n\n");
                    printf("Assuming remote lost bonding information\n");
                    printf("Deleting local bonding information and start new pairing...\n");
                    sm_event_reencryption_complete_get_address(packet, addr);
                    addr_type = sm_event_reencryption_started_get_addr_type(packet);
                    gap_delete_bonding(addr_type, addr);
                    sm_request_pairing(sm_event_reencryption_complete_get_handle(packet));
                    break;
                default:
                    break;
            }
            break;
        default:
            break;
    }
}

int main()
{
    stdio_init_all();

    //sleep_ms(5000);

    if(cyw43_arch_init()) {
        puts("cyw43_init error");
        return 0;
    }
    ws2812_init(pio0, 2, 17);
    led_brightness=0;
    

    keypad_init();
    

    l2cap_init();

    // setup SM: Display only
    sm_init();

    // setup ATT server
    att_server_init(profile_data, att_read_callback, att_write_callback);

    // setup GATT Client
    gatt_client_init();

    // register handler
    hci_event_callback_registration.callback = &hci_packet_handler;
    hci_add_event_handler(&hci_event_callback_registration);

    att_server_register_packet_handler(att_packet_handler);

    sm_event_callback_registration.callback = &sm_packet_handler;
    sm_add_event_handler(&sm_event_callback_registration);

    // enable LE Secure Connections Only mode - disables Legacy pairing
    sm_set_secure_connections_only_mode(true);

    // LE Secure Connections, Numeric Comparison
    
    sm_set_io_capabilities(IO_CAPABILITY_KEYBOARD_ONLY);
    //sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_ONLY);
    sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION|SM_AUTHREQ_MITM_PROTECTION |SM_AUTHREQ_BONDING);

    // 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);

    hci_power_control(HCI_POWER_ON);
   
   while(1) {
    tight_loop_contents();
    sleep_ms(3000);
    //led_brightness++;
    //if (le_notification_enabled)
        //att_server_request_can_send_now_event(con_handle);
   }

    return 0;
}

  • le_peripheral.gatt

PRIMARY_SERVICE, GAP_SERVICE
CHARACTERISTIC, GAP_DEVICE_NAME, READ, "SM LED Peripheral"

PRIMARY_SERVICE, GATT_SERVICE
CHARACTERISTIC, GATT_DATABASE_HASH, READ,

// led control Service
PRIMARY_SERVICE, 00000660-37E4-4E5D-9592-69327D128840
// lec control Service, Characteristic, led bright with read + notification + authentication
CHARACTERISTIC,  00000661-37E4-4E5D-9592-69327D128840, READ | NOTIFY | ENCRYPTION_KEY_SIZE_16 | DYNAMIC,
// lec control Service, Characteristic, led control bright with write + authentication
CHARACTERISTIC,  00000662-37E4-4E5D-9592-69327D128840, WRITE | ENCRYPTION_KEY_SIZE_16 | DYNAMIC,
// lec control Service, Characteristic, led control scale with write + authentication
CHARACTERISTIC,  00000663-37E4-4E5D-9592-69327D128840, WRITE | ENCRYPTION_KEY_SIZE_16 | DYNAMIC,
  • CMakeLists.txt(peripheral)

# 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(le_peripheral 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(le_peripheral le_peripheral.c )

pico_set_program_name(le_peripheral "le_peripheral")
pico_set_program_version(le_peripheral "0.1")

pico_enable_stdio_uart(le_peripheral 0)
pico_enable_stdio_usb(le_peripheral 1)

# Add the standard library to the build
target_link_libraries(le_peripheral
        pico_stdlib
        pico_cyw43_arch_none
        pico_btstack_cyw43
        pico_btstack_ble)

# Add the standard include files to the build
target_include_directories(le_peripheral PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)

add_subdirectory(pico_keypad)
add_subdirectory(ws2812)
target_link_libraries(le_peripheral
        pico_keypad
        ws2812)
pico_btstack_make_gatt_header(le_peripheral PRIVATE "${CMAKE_CURRENT_LIST_DIR}/le_peripheral.gatt")
pico_add_extra_outputs(le_peripheral)


  • le_central.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 "fonts/font_freemono_mono_bold_24.h"
#include "tft_string/tft_string.h"
#include "le_central.h"

#define BUTTON_PIN  16

#define SERVER_DEVICE_NAME "SM LED"
typedef enum {
    CLIENT_STOP=0,
    QUERY_SERVICE,
    QUERY_CHARACTERISTIC_LED_BRIGHTNESS,
    QUERY_CHARACTERISTIC_LED_SET_BRIGHTNESS,
    QUERY_CHARACTERISTIC_LED_BRIGHT_SCALE,
    ENABLE_NOTIFICATION,
    ENABLE_COMPLETE,
    CLIENT_CONNECTED,
    CLIENT_SET_LED_BRIGHTNESS
} client_event_query_t;

uint16_t led_brightness, led_brightness_scale=10;
uint8_t service_uuid128[]={
    0x00,0x00,0x06,0x60,0x37,0xE4,0x4E,0x5D,0x95,0x92,0x69,0x32,0x7D,0x12,0x88,0x40
};
uint8_t char_led_brightness_uuid128[]={
    0x00,0x00,0x06,0x61,0x37,0xE4,0x4E,0x5D,0x95,0x92,0x69,0x32,0x7D,0x12,0x88,0x40
};
uint8_t char_set_led_brightness_uuid128[]={
    0x00,0x00,0x06,0x62,0x37,0xE4,0x4E,0x5D,0x95,0x92,0x69,0x32,0x7D,0x12,0x88,0x40
};
uint8_t char_led_bright_scale_uuid128[]={
    0x00,0x00,0x06,0x63,0x37,0xE4,0x4E,0x5D,0x95,0x92,0x69,0x32,0x7D,0x12,0x88,0x40
};


client_event_query_t client_event_query;

gatt_client_notification_t notification_listener_led_birghtness;
static hci_con_handle_t connection_handler;
gatt_client_service_t led_control_service;
gatt_client_characteristic_t char_set_led_brightness, char_led_brightness, char_led_bright_scale;

static btstack_packet_callback_registration_t hci_event_callback_registration;
static btstack_packet_callback_registration_t sm_event_callback_registration;
static void sm_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
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_show_passkey(uint32_t passkey);
static void tft_print_data();

uint32_t get_passkey() {
    return 123456;
}

static void sm_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;

    bd_addr_t addr;
    bd_addr_type_t addr_type;

    switch (hci_event_packet_get_type(packet)) {
        case SM_EVENT_JUST_WORKS_REQUEST:
            printf("Just works requested\n");
            sm_just_works_confirm(sm_event_just_works_request_get_handle(packet));
            break;
        case SM_EVENT_NUMERIC_COMPARISON_REQUEST:
            printf("Confirming numeric comparison: %"PRIu32"\n", sm_event_numeric_comparison_request_get_passkey(packet));
            sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet));
            break;
        case SM_EVENT_PASSKEY_DISPLAY_NUMBER:
            printf("Display Passkey: %"PRIu32"\n", sm_event_passkey_display_number_get_passkey(packet));
            tft_show_passkey(sm_event_passkey_display_number_get_passkey(packet));
            break;
        case SM_EVENT_PASSKEY_INPUT_NUMBER:
            printf("Passkey Input requested\n");
            uint32_t passkey;
            passkey = get_passkey();
            printf("Sending fixed passkey %"PRIu32"\n", passkey);
            sm_passkey_input(sm_event_passkey_input_number_get_handle(packet), passkey);
            break;
        case SM_EVENT_PAIRING_STARTED:
            printf("Pairing started\n");
            break;
        case SM_EVENT_PAIRING_COMPLETE:
            switch (sm_event_pairing_complete_get_status(packet)){
                case ERROR_CODE_SUCCESS:
                    printf("Pairing complete, success\n");
                    tft_print_data();
                    break;
                case ERROR_CODE_CONNECTION_TIMEOUT:
                    printf("Pairing failed, timeout\n");
                    tft_show_message("Pairing failed, timeout");
                    busy_wait_ms(1000);
                    gap_disconnect(connection_handler);
                    break;
                case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION:
                    printf("Pairing failed, disconnected\n");
                    tft_show_message("Pairing failed, disconnected");
                    busy_wait_ms(1000);
                    gap_disconnect(connection_handler);
                    break;
                case ERROR_CODE_AUTHENTICATION_FAILURE:
                    printf("Pairing failed, authentication failure with reason = %u\n", sm_event_pairing_complete_get_reason(packet));
                    tft_show_message("authentication failure");
                    busy_wait_ms(1000);
                    gap_disconnect(connection_handler);
                    break;
                default:
                    break;
            }
            break;
        case SM_EVENT_REENCRYPTION_STARTED:
            sm_event_reencryption_complete_get_address(packet, addr);
            printf("Bonding information exists for addr type %u, identity addr %s -> start re-encryption\n",
                   sm_event_reencryption_started_get_addr_type(packet), bd_addr_to_str(addr));
            break;
        case SM_EVENT_REENCRYPTION_COMPLETE:
            switch (sm_event_reencryption_complete_get_status(packet)){
                case ERROR_CODE_SUCCESS:
                    printf("Re-encryption complete, success\n");
                    tft_print_data();
                    break;
                case ERROR_CODE_CONNECTION_TIMEOUT:
                    printf("Re-encryption failed, timeout\n");
                    tft_show_message("Re-encryption failed");
                    busy_wait_ms(1000);

                    break;
                case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION:
                    printf("Re-encryption failed, disconnected\n");
                    tft_show_message("Re-encryption failed");
                    busy_wait_ms(1000);

                    break;
                case ERROR_CODE_PIN_OR_KEY_MISSING:
                    printf("Re-encryption failed, bonding information missing\n\n");
                    printf("Assuming remote lost bonding information\n");
                    printf("Deleting local bonding information and start new pairing...\n");
                    tft_show_message("Re-encryption failed");
                    busy_wait_ms(1000);

                    sm_event_reencryption_complete_get_address(packet, addr);
                    addr_type = sm_event_reencryption_started_get_addr_type(packet);
                    gap_delete_bonding(addr_type, addr);
                    sm_request_pairing(sm_event_reencryption_complete_get_handle(packet));
                    break;
                default:
                    break;
            }
            break;
        default:
            break;
    }
}

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...");
            const uint8_t * adv_data = gap_event_advertising_report_get_data(packet);
            uint8_t         adv_len  = gap_event_advertising_report_get_data_length(packet);
            if (ad_data_contains_uuid128(adv_len, adv_data, service_uuid128)) {
                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
            //sm_bonding_decline(connection_handler);
            //sm_request_pairing(connection_handler);
            client_event_query=QUERY_SERVICE;
            gatt_client_discover_primary_services_by_uuid128(handle_gatt_client_event, connection_handler,
                        service_uuid128);
            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, &led_control_service);
        break;
        case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:

            if (client_event_query == QUERY_CHARACTERISTIC_LED_SET_BRIGHTNESS) { 
                gatt_event_characteristic_query_result_get_characteristic(packet, &char_set_led_brightness);
            }
            if (client_event_query == QUERY_CHARACTERISTIC_LED_BRIGHTNESS) { 
                gatt_event_characteristic_query_result_get_characteristic(packet, &char_led_brightness);
            }
            if (client_event_query == QUERY_CHARACTERISTIC_LED_BRIGHT_SCALE) { 
                gatt_event_characteristic_query_result_get_characteristic(packet, &char_led_bright_scale);
            }
        break;
        case GATT_EVENT_QUERY_COMPLETE:
            switch(client_event_query) {
                case QUERY_SERVICE:
                    client_event_query = QUERY_CHARACTERISTIC_LED_BRIGHTNESS;
                    gatt_client_discover_characteristics_for_service_by_uuid128(handle_gatt_client_event, connection_handler, 
                        &led_control_service, char_led_brightness_uuid128);                
                break;
                case QUERY_CHARACTERISTIC_LED_BRIGHTNESS:
                    client_event_query = QUERY_CHARACTERISTIC_LED_SET_BRIGHTNESS;
                    gatt_client_discover_characteristics_for_service_by_uuid128(handle_gatt_client_event, connection_handler, 
                        &led_control_service, char_set_led_brightness_uuid128);
                break;
                case QUERY_CHARACTERISTIC_LED_SET_BRIGHTNESS:
                    client_event_query = QUERY_CHARACTERISTIC_LED_BRIGHT_SCALE;
                    gatt_client_discover_characteristics_for_service_by_uuid128(handle_gatt_client_event, connection_handler, 
                        &led_control_service,char_led_bright_scale_uuid128);
                break;
                case QUERY_CHARACTERISTIC_LED_BRIGHT_SCALE:
                    client_event_query = ENABLE_NOTIFICATION;
                    gatt_client_listen_for_characteristic_value_updates(&notification_listener_led_birghtness, handle_gatt_client_event, 
                        connection_handler, &char_led_brightness);
                    // enable notifications
                    gatt_client_write_client_characteristic_configuration(handle_gatt_client_event, connection_handler,
                        &char_led_brightness, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);


                break;
                
                case ENABLE_NOTIFICATION:
                client_event_query = CLIENT_CONNECTED;
         
                break;
                case CLIENT_SET_LED_BRIGHTNESS:

                break;
            }
            break;
            case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT:
                if (char_led_brightness.value_handle == gatt_event_characteristic_value_query_result_get_value_handle(packet)) { 
                    led_brightness = little_endian_read_16(gatt_event_characteristic_value_query_result_get_value(packet), 0);

                    tft_print_data();
                    //printf("led_brightness read:%d\n", led_brightness);
                    
                }
                 
            break;
            case GATT_EVENT_NOTIFICATION:

                if (char_led_brightness.value_handle == gatt_event_notification_get_value_handle(packet)) { 
                  
                    led_brightness = little_endian_read_16(gatt_event_notification_get_value(packet), 0);
                    tft_print_data();

                    client_event_query = CLIENT_CONNECTED;
                }

            break;
            case ATT_EVENT_CAN_SEND_NOW:
            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, msg, 0xf800, &font_fixedsys_mono_16);
}

void tft_show_passkey(uint32_t passkey) {
    char ts[40];
    tft_fill_rect(0,0,TFT_WIDTH, TFT_HEIGHT, 0xffff);
    tft_draw_string(20,5, "Pairing Code", 0xf800, &font_fixedsys_mono_16);
    sprintf(ts, "%u", passkey);
    tft_draw_string(30,40, ts, 0x001f, &font_freemono_mono_bold_24);
}
void tft_print_data() {
    char ts[50];
    tft_fill_rect(0,0,TFT_WIDTH, TFT_HEIGHT, 0xffff);
    tft_draw_string(10,5, "LED Birghtness", 0xf800, &font_fixedsys_mono_16);
    tft_draw_line(4,34,155,34, 0x07e0);
    tft_draw_line(155,34,155,65, 0x07e0);
    tft_draw_line(4,65,155,65, 0x07e0);
    tft_draw_line(4,34,4,65, 0x07e0);
    tft_fill_rect(5,35,150/led_brightness_scale*led_brightness, 30, 0x001f);
    
    sprintf(ts, "%d%c ", (int)((float)led_brightness/(float)led_brightness_scale*100), '%');
    tft_draw_string(65,75, ts, 0xf800, &font_fixedsys_mono_16);
    
}
void button_pin_press_callback(uint gpio, uint32_t events) {
    if (gpio == BUTTON_PIN && client_event_query == CLIENT_CONNECTED) {
        led_brightness++;
        if (led_brightness > 10) led_brightness=0;
        client_event_query=CLIENT_SET_LED_BRIGHTNESS;
        gatt_client_write_value_of_characteristic(handle_gatt_client_event, connection_handler, char_set_led_brightness.value_handle, 2, (uint8_t*)&led_brightness);
    }
}
int main()
{
    stdio_init_all();

    //sleep_ms(5000);

    gpio_init(BUTTON_PIN);
    gpio_pull_up(BUTTON_PIN);
    gpio_set_irq_enabled_with_callback(BUTTON_PIN, GPIO_IRQ_LEVEL_LOW , true, &button_pin_press_callback);


    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();

    // register for HCI events
    hci_event_callback_registration.callback = &handle_hci_event;
    hci_add_event_handler(&hci_event_callback_registration);

    sm_event_callback_registration.callback = &sm_packet_handler;
    sm_add_event_handler(&sm_event_callback_registration);
    
    // enable LE Secure Connections Only mode - disables Legacy pairing
    sm_set_secure_connections_only_mode(true);

    // LE Secure Connections, Numeric Comparison
    sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_ONLY);
    sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION|SM_AUTHREQ_MITM_PROTECTION |SM_AUTHREQ_BONDING);

    
    hci_power_control(HCI_POWER_ON);
    while(client_event_query != CLIENT_CONNECTED) {
        tight_loop_contents();
    }
    gatt_client_write_value_of_characteristic(handle_gatt_client_event, connection_handler, char_led_bright_scale.value_handle, 2, (uint8_t*)&led_brightness_scale);

    while(1) {
      

        tight_loop_contents();
    }
    

    return 0;
}

  • le_central.gatt

PRIMARY_SERVICE, GAP_SERVICE
CHARACTERISTIC, GAP_DEVICE_NAME, READ, "SM LED Central"

PRIMARY_SERVICE, GATT_SERVICE
CHARACTERISTIC, GATT_DATABASE_HASH, READ,
  • CMakeLists.txt(central)

# 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(le_central 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(le_central le_central.c )

pico_set_program_name(le_central "le_central")
pico_set_program_version(le_central "0.1")

pico_enable_stdio_uart(le_central 1)
pico_enable_stdio_usb(le_central 0)

# Add the standard library to the build
target_link_libraries(le_central
        pico_stdlib
        pico_cyw43_arch_none
        pico_btstack_cyw43
        pico_btstack_ble)

# Add the standard include files to the build
target_include_directories(le_central PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)
add_subdirectory(pico_tft)
target_link_libraries(le_central 
        pico_tft)   
pico_btstack_make_gatt_header(le_central PRIVATE "${CMAKE_CURRENT_LIST_DIR}/le_central.gatt")
pico_add_extra_outputs(le_central)
  • btstack_config.h

#ifndef _PICO_BTSTACK_BTSTACK_CONFIG_H
#define _PICO_BTSTACK_BTSTACK_CONFIG_H

// BTstack features that can be enabled
#define ENABLE_LE_PERIPHERAL
#define ENABLE_LE_CENTRAL
#define ENABLE_LE_SECURE_CONNECTIONS   ////
#define ENABLE_L2CAP_LE_CREDIT_BASED_FLOW_CONTROL_MODE
#define ENABLE_LOG_INFO
#define ENABLE_LOG_ERROR
#define ENABLE_PRINTF_HEXDUMP
#define ENABLE_SCO_OVER_HCI

// BTstack configuration. buffers, sizes, ...
#define HCI_OUTGOING_PRE_BUFFER_SIZE 4
#define HCI_ACL_PAYLOAD_SIZE (1691 + 4)
#define HCI_ACL_CHUNK_SIZE_ALIGNMENT 4
#define MAX_NR_AVDTP_CONNECTIONS 1
#define MAX_NR_AVDTP_STREAM_ENDPOINTS 1
#define MAX_NR_AVRCP_CONNECTIONS 2
#define MAX_NR_BNEP_CHANNELS 1
#define MAX_NR_BNEP_SERVICES 1
#define MAX_NR_BTSTACK_LINK_KEY_DB_MEMORY_ENTRIES  2
#define MAX_NR_GATT_CLIENTS 1
#define MAX_NR_HCI_CONNECTIONS 2
#define MAX_NR_HID_HOST_CONNECTIONS 1
#define MAX_NR_HIDS_CLIENTS 1
#define MAX_NR_HFP_CONNECTIONS 1
#define MAX_NR_L2CAP_CHANNELS  4
#define MAX_NR_L2CAP_SERVICES  3
#define MAX_NR_RFCOMM_CHANNELS 1
#define MAX_NR_RFCOMM_MULTIPLEXERS 1
#define MAX_NR_RFCOMM_SERVICES 1
#define MAX_NR_SERVICE_RECORD_ITEMS 4
#define MAX_NR_SM_LOOKUP_ENTRIES 3
#define MAX_NR_WHITELIST_ENTRIES 16
#define MAX_NR_LE_DEVICE_DB_ENTRIES 16

// Limit number of ACL/SCO Buffer to use by stack to avoid cyw43 shared bus overrun
#define MAX_NR_CONTROLLER_ACL_BUFFERS 3
#define MAX_NR_CONTROLLER_SCO_PACKETS 3

// Enable and configure HCI Controller to Host Flow Control to avoid cyw43 shared bus overrun
#define ENABLE_HCI_CONTROLLER_TO_HOST_FLOW_CONTROL
#define HCI_HOST_ACL_PACKET_LEN 1124 //1024
#define HCI_HOST_ACL_PACKET_NUM 3
#define HCI_HOST_SCO_PACKET_LEN 120
#define HCI_HOST_SCO_PACKET_NUM 3

// Link Key DB and LE Device DB using TLV on top of Flash Sector interface
#define NVM_NUM_DEVICE_DB_ENTRIES 16
#define NVM_NUM_LINK_KEYS 16

// We don't give btstack a malloc, so use a fixed-size ATT DB.
#define MAX_ATT_DB_SIZE 512

// BTstack HAL configuration
#define HAVE_EMBEDDED_TIME_MS

// map btstack_assert onto Pico SDK assert()
#define HAVE_ASSERT

// Some USB dongles take longer to respond to HCI reset (e.g. BCM20702A).
#define HCI_RESET_RESEND_TIMEOUT_MS 1000

#define ENABLE_SOFTWARE_AES128
#define ENABLE_MICRO_ECC_FOR_LE_SECURE_CONNECTIONS

//#define HAVE_BTSTACK_STDIN

// To get the audio demos working even with HCI dump at 115200, this truncates long ACL packetws
//#define HCI_DUMP_STDOUT_MAX_SIZE_ACL 100

#ifdef ENABLE_CLASSIC
#define ENABLE_L2CAP_ENHANCED_RETRANSMISSION_MODE
#endif

#endif // MICROPY_INCLUDED_EXTMOD_BTSTACK_BTSTACK_CONFIG_H

  

2023年6月14日 星期三

[Raspberry Pi Pico W] BLE Ep 2. BTstack GATT server & GATT Client

 前一篇文章介紹透過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 SDK 1.5.1在CMake 已加入pico_btstack_make_gatt_header函數
可改成
pico_btstack_make_gatt_header(le_sm_peripheral PRIVATE "${CMAKE_CURRENT_LIST_DIR}/gatt_ht-server.gatt")

advertisement data只包含以下三個簡單的data type
設定att server的三個參數如下:
att_server_init(profile_data, att_read_callback, att_write_callback); 
(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(&notification_listener_temp, handle_gatt_client_event, 
                        connection_handler, &temperature);
                    gatt_client_listen_for_characteristic_value_updates(&notification_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"





2023年6月5日 星期一

[Raspberry Pi Pico W] BLE Ep 1. Advertisement: Broadcast Temperature and Humidity

 本文章介紹在Raspberry Pi Pico W 使用Btstack來實現使用BLE的Advertisement來發送安裝在BLE peripheral(broadcaster)上的溫濕度sensor的數據給所有centrals(observers)。

本實驗使用一個Raspberry Pi Pico W連接DHT11,把收集到環境的溫濕度透過BLE advertisement給附近所有的BLE observer。

另一個Raspberry Pi Pico W當作Central(Observer)接收advertisement data顯示在TFT上。

另一個Android Phone安裝Nordic nRF Connect scan 附近BLE broadcaster並顯示收集到的資料。


本實驗僅透過Advertisement data來傳送資料,不需要connect BLE peripheral。


BLE advertisement/scan response packet format如下圖:

每一個advertisement資料結構分成三個欄位:length, type and data。在本實驗中相對應程式的結構如下:
兩個SERVICE_DATA: 1. UUID 0x2A6E表示一個byte的溫度資料,
2. UUID 0x2A6F表示一個byte的濕度資料。
Peripheral端設定advertisement param, data後enable advertisement。

Central端設定HCI packet handler處理 GAP_EVENT_ADVERTISING_REPORT。讀取advertisement data and size。

使用BTstack ad_iterator_* 等functions來取得advertisement packet每的資料夠的
length, type and data。
其他程式詳細內容列於文章後面。

成果展示影片:


程式碼:
1. Peripheral(broadcaster)端:
#include <stdio.h>
#include "pico/stdlib.h"

#include "btstack.h"
#include "pico/cyw43_arch.h"
#include "dht11/dht11.h"
#include "math.h"
#include "stdlib.h"

#define ADV_TEMP_HUMI_PERIOD_MS 3000

uint8_t adv_data[] = {
    // Flags: 0x02: General Discoverable Mode, 0x04:BR/EDR Not Supported
    0x02, BLUETOOTH_DATA_TYPE_FLAGS, 0x06,
    // Name
    0x0a, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'P', 'i', 'c', 'o', 'W', '-', 'B', 'L', 'E', 
    0x03, BLUETOOTH_DATA_TYPE_INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, 0x1a, 0x18,
    //Temperature UUID:0x2A5E
    0x04, BLUETOOTH_DATA_TYPE_SERVICE_DATA, 0x6E,0x2A,0x00, 
    //Humidity UUID: 0x2A6F
    0x04, BLUETOOTH_DATA_TYPE_SERVICE_DATA, 0x6F,0x2A,0x00, 

};

uint8_t adv_data_len = sizeof(adv_data);

static btstack_timer_source_t adv_temp_humi;
uint8_t temp=0, humi=0;

static void adv_temp_humi_handler(struct btstack_timer_source *ts){

    //gap_advertisements_enable(false);
    float t,h;
    if (dht11_get_data(&t, &h)) {
        temp = (int)round(t);
        humi = (int)h;
    }
    adv_data[adv_data_len-1] = humi;
    adv_data[adv_data_len-6] = temp;

    printf("Temp:%d °C, Humi:%d %c\n", temp, humi, '%');
        
    gap_advertisements_set_data(adv_data_len, adv_data);
    //gap_advertisements_enable(true);
    
    btstack_run_loop_set_timer(ts, ADV_TEMP_HUMI_PERIOD_MS);
    btstack_run_loop_add_timer(ts);
} 


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;
    
    // ADV_TYPE:
    // 0b0000: ADV_IND, 0b0001: ADV_DIRECT_IND, 0b0010: ADV_NONCONN_IND, 0b0011: ADV_SCAN_IND
    uint8_t adv_type = 2;  // only advertisement, not connectable.
    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(true);

  
    adv_temp_humi.process = &adv_temp_humi_handler;
    btstack_run_loop_set_timer(&adv_temp_humi, ADV_TEMP_HUMI_PERIOD_MS);
    btstack_run_loop_add_timer(&adv_temp_humi);


    hci_power_control(HCI_POWER_ON);

    
    
    while(1) {
        tight_loop_contents();

    }
    return 0;
}

2. Central(Observer)端:
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/cyw43_arch.h"

#include <stdint.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include "btstack.h"
#include "fonts/font_fixedsys_mono_16.h"
#include "pico_tft.h"
#include "tft_string.h"

#define BROADCAST_DEVICE "PicoW-BLE"

static btstack_packet_callback_registration_t hci_event_callback_registration;

uint8_t broadcast_device_name[20];
uint8_t service_16_uuid[2];
uint8_t temp=0;
uint8_t humi=0;
bool bNewAdvData=false;

static void show_received_advertisement_data(const uint8_t * adv_data, uint8_t adv_size){
    ad_context_t context;
    
    
    for (ad_iterator_init(&context, adv_size, (uint8_t *)adv_data) ; ad_iterator_has_more(&context) ; ad_iterator_next(&context)){
        uint8_t data_type    = ad_iterator_get_data_type(&context);
        uint8_t size         = ad_iterator_get_data_len(&context);
        const uint8_t * data = ad_iterator_get_data(&context);

        switch  (data_type) {
            case BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME:
                memcpy(broadcast_device_name, data,size);
                broadcast_device_name[size]='\0';
            break;
            case BLUETOOTH_DATA_TYPE_INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS:
                memcpy(service_16_uuid, data,size);
            break;
            case BLUETOOTH_DATA_TYPE_SERVICE_DATA:
                if (data[0]==0x6E && data[1]==0x2A) {
                    temp = data[2];
                }
                if (data[0]==0x6F && data[1]==0x2A) {
                    humi = data[2];
                }
            break;
        }

      }
    if (strcmp(broadcast_device_name, BROADCAST_DEVICE) == 0 && 
                service_16_uuid[0]==0x1A && service_16_uuid[1]==0x18) {
            bNewAdvData=true;
         
    }

}

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 GAP_EVENT_ADVERTISING_REPORT:{
            uint8_t length = gap_event_advertising_report_get_data_length(packet);
            const uint8_t * data = gap_event_advertising_report_get_data(packet);
            show_received_advertisement_data(data, length);
            break;
        }
        default:
            break;
    }
}

uint8_t ts[50];
int main() {
    stdio_init_all();
     if (cyw43_arch_init()) {
        printf("cyw43_init error\n");
        return 0;
    }
    

    gap_set_scan_parameters(0,4800,48); // scan type: 0: passive, 1:active
                                        //scan interval 0.625*n, 
    gap_start_scan(); 

    hci_event_callback_registration.callback = &packet_handler;
    hci_add_event_handler(&hci_event_callback_registration);

    hci_power_control(HCI_POWER_ON);

    tft_init();

    while (1) {
        if (bNewAdvData) {
            bNewAdvData=false;
            printf("local name:%s, temp=%d, humi=%d\n", broadcast_device_name, temp, humi);
            tft_fill_rect(0,0,TFT_WIDTH, TFT_HEIGHT, 0xffff);
            sprintf(ts, "Device:%s", broadcast_device_name);
            tft_draw_string(2,10,ts, 0x001f, &font_fixedsys_mono_16);

            sprintf(ts, "Temp:%02dC", temp);
            tft_draw_string(2,50,ts, 0x001f, &font_fixedsys_mono_16);

            sprintf(ts, "Humi:%02d%c", humi, '%');
            tft_draw_string(2,80,ts, 0x001f, &font_fixedsys_mono_16);
        }
    }
}