prettyprint

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

沒有留言:

張貼留言