prettyprint

2023年3月27日 星期一

[Raspberry Pi Pico W] BTstack: Ep 2. Bluetooth Classic SPP piconet -- remote control and text messaging

本文章介紹使用Raspberry Pi Pico W建構 Bluetooth Classic SPP(serial port profile)的Piconet。使用兩個 Pi Pico W開發板、android phone與Linux(debian),一個Pi Pico W當SPP server,一個Pi Pico W當SPP client。如下圖所示。


所有SPP client(piconet slave)透過SPP server(piconet master)互相溝通。

一、Pico W SPP server:

建立SPP server需要用到bluetooth stack 的portocol與profile如下圖所示:

1. initialize HCI, L2CAP, SPD and RFCOMM protocol:
2.register HCP, RFCOMM packet handler:
在這篇文章中使用同一個function。
3. 建立SPP service 的SDP service record。
使定service record handle為0x10001。(SPP service class的UUID為0x1101)。

二、Pico W SPP client:
建立SPP client需要用到bluetooth stack 的portocol與profile如下圖所示:
如同SPP server一樣要先init l2cap, rfcomm和指定packet handler。
當HCI is working時執行gap_inquiry 收尋bluetooth device。

執行SDP query查詢0x1101 SDP database找到SPP service。
接著建立connection。
相關程式碼附於文末。
其他詳細動態解說請觀看下面成果影片。
三、成果影片:




四、程式碼:

picow_spp_server

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ctype.h"
 
#include "btstack.h"
#include "pico/cyw43_arch.h"
#include "pico/stdlib.h"
#include "pico/util/queue.h"
#include "cJSON.h"

#define MAX_CLIENT_DEVICE 7
#define SWITCH_PIN  15
enum {
    DEVICE_RELEASED=0,
    DEVICE_OPENED,
};

typedef struct _spp_client_t_ {
    uint8_t client_addr[18];    //string format 
    uint8_t   device_name[21];
    uint16_t  rfcomm_channel_id;
    uint16_t mtu;
    uint8_t   device_state;
} spp_client_t;

typedef struct _message_t {
    uint8_t device_name[21];
    uint8_t content[1124];
    uint16_t content_len;
    uint16_t channel_id;
    uint8_t type[5];
} message_t;

queue_t message_queue;

spp_client_t spp_client[MAX_CLIENT_DEVICE];

#define RFCOMM_SERVER_CHANNEL 1

static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);

static uint8_t  spp_service_buffer[1024];
static btstack_packet_callback_registration_t hci_event_callback_registration;

void set_spp_client_channel(bd_addr_t addr, uint16_t rfcomm_channel_id, uint16_t mtu) {
    uint8_t index;
    bool found_device=false;
    for (index=0; index < MAX_CLIENT_DEVICE; index++) {
        if (strcmp(spp_client[index].client_addr, bd_addr_to_str(addr)) == 0) {
            spp_client[index].rfcomm_channel_id = rfcomm_channel_id;
            spp_client[index].mtu = mtu;
            spp_client[index].device_state = DEVICE_OPENED;
            found_device=true;
            break;
        }
    }
    if(!found_device) {
        for (index=0; index < MAX_CLIENT_DEVICE; index++) {
            if (spp_client[index].device_state == DEVICE_RELEASED) {
                strcpy(spp_client[index].client_addr, bd_addr_to_str(addr));
                spp_client[index].rfcomm_channel_id = rfcomm_channel_id;
                spp_client[index].mtu = mtu;
                spp_client[index].device_state = DEVICE_OPENED;
                break;
            }
        }
    }
    message_t message;
    sprintf(message.content, "Client Connected");
    message.content_len=strlen(message.content);
    sprintf(message.type,"info");
    message.channel_id=rfcomm_channel_id;

    queue_try_add(&message_queue, &message);
    rfcomm_request_can_send_now_event(rfcomm_channel_id);
}
void set_spp_client_close(uint16_t rfcomm_channel_id) {
    for (int i=0; i < MAX_CLIENT_DEVICE; i++) {
        if (spp_client[i].rfcomm_channel_id == rfcomm_channel_id) {
printf("client close, addr:%s\n", spp_client[i].client_addr);
            spp_client[i].rfcomm_channel_id = 0;
            memset(spp_client[i].client_addr, 0, 18);
            spp_client[i].device_state = DEVICE_RELEASED;
            break;
        }
    }

}

uint16_t  get_spp_client_channel(uint8_t *device_name) {
    uint8_t index;
    uint16_t rfcomm_channel_id=0;

    for (index=0; index < MAX_CLIENT_DEVICE; index++) {
        if (strcmp(spp_client[index].device_name, device_name) == 0) {
            rfcomm_channel_id = spp_client[index].rfcomm_channel_id;
            break;
        }
    }
    return rfcomm_channel_id;
}

void set_spp_client_name(uint8_t  *addr, uint8_t *device_name) {
    for (int i=0; i < strlen(addr); i++) {
        addr[i]= (uint8_t)toupper(addr[i]);
    }

    for (int i=0; i < MAX_CLIENT_DEVICE; i++) {
        if (strcmp(spp_client[i].client_addr, addr) == 0) {
            strcpy(spp_client[i].device_name, device_name);
        }
    }
}

void server_action(uint8_t *type, uint8_t *data) {

    if (strcmp(type, "cmd")==0) {
        if (strcmp(data, "switch") == 0) {
            gpio_put(SWITCH_PIN, !gpio_get(SWITCH_PIN));
        }
    }
    
}

void send_message(message_t message) {
    if (message.channel_id!=0) {
            if (queue_try_add(&message_queue, &message)) { 
                rfcomm_request_can_send_now_event(message.channel_id);
            }
        } else {
            printf("%s channel error\n", message.device_name);
        }
}
void spp_process_packet(uint8_t *packet, uint16_t size) {
    message_t message;
    memset(message.content, 0, 256);
    cJSON* json_obj = cJSON_CreateObject();
    json_obj = cJSON_Parse(packet);
    uint8_t *name = cJSON_GetStringValue(cJSON_GetObjectItem(json_obj, "name"));
    uint8_t *type = cJSON_GetStringValue(cJSON_GetObjectItem(json_obj, "type"));
    uint8_t *data = cJSON_GetStringValue(cJSON_GetObjectItem(json_obj, "data"));

    if(strcmp(name, "S0") == 0) {
        server_action(type, data);
        cJSON_Delete(json_obj);
        return;
    }

    if (strcmp(type, "cmd")==0) {  //cmd
        strcpy(message.device_name, name);
        strcpy(message.type, type);
        message.channel_id = get_spp_client_channel(name);
        if (strcmp(data, "list") == 0) {
            strcpy(message.type, "info");
            for (int i=0; i < MAX_CLIENT_DEVICE; i++) {
                if (spp_client[i].device_state == DEVICE_OPENED) {
                    strcat(message.content,spp_client[i].device_name);
                    strcat(message.content,",");
                }
            }
            message.content_len = strlen(message.content);
            
        } else {
            strcpy(message.content, data);
            message.content_len = strlen(data);

        }
        send_message(message);
        cJSON_Delete(json_obj);
        return;
        
    }
    if (strcmp(type, "set") == 0) {
        set_spp_client_name(data, name);
        cJSON_Delete(json_obj);
        return;
    }
    if (strcmp(type, "txt")==0 || strcmp(type, "rgb")==0) {
        strcpy(message.device_name, name);
        strcpy(message.content, data);
        strcpy(message.type, type);
        message.channel_id = get_spp_client_channel(name);
        message.content_len = strlen(data);
        send_message(message);
        cJSON_Delete(json_obj);
        return;
    }
    
}
static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
    UNUSED(channel);
    uint16_t rfcomm_channel_id;
    bd_addr_t event_addr;
    uint8_t   rfcomm_channel_nr;
    uint16_t  mtu;
    message_t message;
    uint8_t mesg_buff[256];

    switch (packet_type) {
        case HCI_EVENT_PACKET:
            switch (hci_event_packet_get_type(packet)) {
                case BTSTACK_EVENT_STATE:
                    if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING) {
                        cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true);
                    }
                    if (btstack_event_state_get_state(packet) == HCI_STATE_OFF) {
                        cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
                    }
                    break;
                case HCI_EVENT_PIN_CODE_REQUEST:
                    // inform about pin code request
                    printf("Pin code request - using '0000'\n");
                    hci_event_pin_code_request_get_bd_addr(packet, event_addr);
                    gap_pin_code_response(event_addr, "0000");
                    break;

                case HCI_EVENT_USER_CONFIRMATION_REQUEST:
                    // ssp: inform about user confirmation request
                    printf("SSP User Confirmation Request with numeric value '%06"PRIu32"'\n", little_endian_read_32(packet, 8));
                    printf("SSP User Confirmation Auto accept\n");
                    break;

                case RFCOMM_EVENT_INCOMING_CONNECTION:
                    rfcomm_event_incoming_connection_get_bd_addr(packet, event_addr);
                    rfcomm_channel_nr = rfcomm_event_incoming_connection_get_server_channel(packet);
                    rfcomm_channel_id = rfcomm_event_incoming_connection_get_rfcomm_cid(packet);
                    printf("RFCOMM channel %u requested for %s\n", rfcomm_channel_nr, bd_addr_to_str(event_addr));
                    rfcomm_accept_connection(rfcomm_channel_id);
                    break;
               
                case RFCOMM_EVENT_CHANNEL_OPENED:
                    if (rfcomm_event_channel_opened_get_status(packet)) {
                        printf("RFCOMM channel open failed, status 0x%02x\n", rfcomm_event_channel_opened_get_status(packet));
                    } else {
                        rfcomm_event_channel_opened_get_bd_addr(packet, event_addr);
                        rfcomm_channel_id = rfcomm_event_channel_opened_get_rfcomm_cid(packet);
                        mtu = rfcomm_event_channel_opened_get_max_frame_size(packet);
                        set_spp_client_channel(event_addr, rfcomm_channel_id, mtu);
                        
                        printf("RFCOMM channel open succeeded. New RFCOMM Channel ID %u, max frame size %u\n", rfcomm_channel_id, mtu);
                    }
                    break;
                case RFCOMM_EVENT_CAN_SEND_NOW:
                    rfcomm_channel_id = rfcomm_event_can_send_now_get_rfcomm_cid(packet);
                    if (!queue_is_empty(&message_queue)) {
                        if (queue_try_peek(&message_queue, &message)) {
                            if (message.channel_id == rfcomm_channel_id) {
                                sprintf(mesg_buff, "{\"type\":\"%s\", \"data\":\"%s\"}", message.type, message.content);
                                rfcomm_send(rfcomm_channel_id, (uint8_t*) mesg_buff, (uint16_t)strlen(mesg_buff));
                                queue_try_remove(&message_queue, &message);
                            }else {
                                busy_wait_ms(1);
                                rfcomm_request_can_send_now_event(rfcomm_channel_id);
                            }   
                        } else {
                            printf("peek queue error\n");
                        }

                    }
                    break;
               
                case RFCOMM_EVENT_CHANNEL_CLOSED:
                    printf("RFCOMM channel closed\n");
                    
                    set_spp_client_close(rfcomm_event_channel_closed_get_rfcomm_cid(packet));

                    break;
                
                default:
                    break;
            }
            break;

        case RFCOMM_DATA_PACKET:
            spp_process_packet(packet, size);
            break;

        default:
            break;
    }
}

int main()
{
    stdio_init_all();

    if (cyw43_arch_init())
    {
        printf("cyw43 init error\n");
        return 0;
    }
    gpio_init(SWITCH_PIN);
    gpio_set_dir(SWITCH_PIN, true);
    gpio_put(SWITCH_PIN,false);
    cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
    for (int i=0; i < MAX_CLIENT_DEVICE; i++) {
        spp_client[i].device_state = DEVICE_RELEASED;
        spp_client[i].rfcomm_channel_id=0;
    }

    queue_init(&message_queue, sizeof(message_t), 10);

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

    l2cap_init();
    rfcomm_init();
    rfcomm_register_service(packet_handler, RFCOMM_SERVER_CHANNEL, 0xffff);  // reserved channel, mtu limited by l2cap
   
    sdp_init();
   
    memset(spp_service_buffer, 0, sizeof(spp_service_buffer));
    spp_create_sdp_record(spp_service_buffer, 0x10001, RFCOMM_SERVER_CHANNEL, "PicoW SPP Service");
    sdp_register_service(spp_service_buffer);
    printf("SDP service record size: %u\n", de_get_len(spp_service_buffer));

    gap_discoverable_control(1);
    gap_ssp_set_io_capability(SSP_IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
    gap_set_local_name("PicoW_SPP_S");

    // turn on!
    hci_power_control(HCI_POWER_ON);

    while (1)
    {
        tight_loop_contents();
    }
    return 0;
}

pico_spp_client
 #include <stdio.h>
#include "pico/stdlib.h"
#include "btstack.h"
#include "pico/cyw43_arch.h"
#include "inttypes.h"
#include "cJSON.h"
#include "ws2812.h"

#define QUERY_DURATION 5
#define WS2812_PIN 15
#define TOUCH_PIN 16
// Major device class: 0x01--(Computer) or 0x02--(Phone)  minor device class 0x0c(Laptop)
#define PEER_COD 0b0001100001100
#define SPP_SERVICE_UUID    0x1101

enum {
    START_INQUIRY=0,
    STOP_INQUIRY,
    PEER_NAME_INQUIRY,
    START_SDP_INQUERY,
    SDP_CHANNEL_QUERY,
};
static uint8_t state;
static bd_addr_t peer_addr;
uint8_t peer_name[240];

uint8_t client_buff[2048];

static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
static uint8_t spp_server_channel=0;
static uint16_t rfcomm_cid;
static uint16_t rfcomm_mtu;
static uint8_t page_scan_repetition_mode;
static uint16_t clock_offset;
   

static btstack_packet_callback_registration_t hci_event_callback_registration;
static btstack_context_callback_registration_t handle_sdp_client_query_request;

void set_client_device_name(bd_addr_t addr) {
    if (rfcomm_cid == 0) {
        printf("server channel 0\n");
    }
    sprintf(client_buff, "{\"name\":\"C0\",\"type\":\"set\",\"data\":\"%s\"}", 
                bd_addr_to_str(addr));
    rfcomm_request_can_send_now_event(rfcomm_cid);

}

void process_data(uint8_t *packet, uint16_t size) {
    cJSON* json_obj = cJSON_CreateObject();
    json_obj = cJSON_Parse(packet);
    uint8_t *type = cJSON_GetStringValue(cJSON_GetObjectItem(json_obj, "type"));
    uint8_t *data = cJSON_GetStringValue(cJSON_GetObjectItem(json_obj, "data"));

    if(strcmp(type, "rgb") == 0) {
        ws2812_play(data, strlen(data));
        cJSON_Delete(json_obj);
        return;
    }

}

static void handle_query_rfcomm_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
    UNUSED(packet_type);
    UNUSED(channel);
    UNUSED(size);

    switch (hci_event_packet_get_type(packet)){
        case SDP_EVENT_QUERY_RFCOMM_SERVICE:
            spp_server_channel = sdp_event_query_rfcomm_service_get_rfcomm_channel(packet);
            break;
        case SDP_EVENT_QUERY_COMPLETE:
            if (sdp_event_query_complete_get_status(packet)){
                state = START_SDP_INQUERY;
                sdp_client_query_rfcomm_channel_and_name_for_uuid(&handle_query_rfcomm_event, peer_addr, SPP_SERVICE_UUID);
                break;
            } 
            if (spp_server_channel == 0){
                state = START_INQUIRY;
                gap_inquiry_start(QUERY_DURATION);
                break;
            }
            rfcomm_create_channel(packet_handler, peer_addr, spp_server_channel, NULL); 
            break;
        default:
            break;
    }
}

static void handle_start_sdp_client_query(void * context){
    UNUSED(context);
    if (state != START_SDP_INQUERY) return;
    state = SDP_CHANNEL_QUERY;
    sdp_client_query_rfcomm_channel_and_name_for_uuid(&handle_query_rfcomm_event, peer_addr, SPP_SERVICE_UUID);
}

static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
    UNUSED(channel);
    bd_addr_t event_addr;
    uint8_t   rfcomm_channel_nr;
    uint32_t class_of_device;
    uint8_t name_len;
    

	switch (packet_type) {
		case HCI_EVENT_PACKET:
			switch (hci_event_packet_get_type(packet)) {

                case BTSTACK_EVENT_STATE:

                    if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) return;
                    state = START_INQUIRY;
                    gap_inquiry_start(QUERY_DURATION);
                    break;

                case GAP_EVENT_INQUIRY_RESULT:
                    if (state != START_INQUIRY) break;
                    class_of_device = gap_event_inquiry_result_get_class_of_device(packet);
                    gap_event_inquiry_result_get_bd_addr(packet, event_addr);
                    memcpy(peer_addr, event_addr, 6);
                    page_scan_repetition_mode = gap_event_inquiry_result_get_page_scan_repetition_mode(packet);
                    clock_offset = gap_event_inquiry_result_get_clock_offset(packet);

                    if (gap_event_inquiry_result_get_name_available(packet)){
                        int name_len = gap_event_inquiry_result_get_name_len(packet);
                        memcpy(peer_name, gap_event_inquiry_result_get_name(packet), name_len);
                        peer_name[name_len] = 0;
                        if (strcmp(peer_name, "PicoW_SPP_S")==0) { 
                            state = STOP_INQUIRY;
                            
                        }
                    } else {
                        state = PEER_NAME_INQUIRY;
                    }
                    
                                         
                    break;
                    
                case GAP_EVENT_INQUIRY_COMPLETE:
                    switch (state){
                        case PEER_NAME_INQUIRY:
                            gap_remote_name_request(peer_addr ,page_scan_repetition_mode,  clock_offset | 0x8000);
                            gap_inquiry_start(QUERY_DURATION);
                            break;
                        case START_INQUIRY:                    
                            gap_inquiry_start(QUERY_DURATION);
                            break;                        
                        case STOP_INQUIRY:
                            gap_inquiry_stop();
                            break;
                        default:
                            break;
                    }
                    
                    break;
                case HCI_EVENT_REMOTE_NAME_REQUEST_COMPLETE:

                    strcpy(peer_name, &packet[9]);
                    if (strcmp(peer_name, "PicoW_SPP_S")==0) { 
                            state = STOP_INQUIRY;
                        printf("Start to connect and query for SPP service\n");    
                        sdp_client_query_rfcomm_channel_and_name_for_uuid(&handle_query_rfcomm_event, peer_addr, SPP_SERVICE_UUID); 
                    } else {
                        state = START_INQUIRY;
                        gap_inquiry_start(QUERY_DURATION);
                    }
                    break;

                case HCI_EVENT_PIN_CODE_REQUEST:
                    // inform about pin code request
                    printf("Pin code request - using '0000'\n");
                    hci_event_pin_code_request_get_bd_addr(packet, event_addr);
                    gap_pin_code_response(event_addr, "0000");
                    break;

                case HCI_EVENT_USER_CONFIRMATION_REQUEST:
                    // inform about user confirmation request
                    printf("SSP User Confirmation Request with numeric value '%06"PRIu32"'\n", little_endian_read_32(packet, 8));
                    printf("SSP User Confirmation Auto accept\n");
                    break;
/* 
                case RFCOMM_EVENT_INCOMING_CONNECTION:
                    rfcomm_event_incoming_connection_get_bd_addr(packet, event_addr);
                    rfcomm_channel_nr = rfcomm_event_incoming_connection_get_server_channel(packet);
                    rfcomm_cid = rfcomm_event_incoming_connection_get_rfcomm_cid(packet);
                    printf("RFCOMM channel 0x%02x requested for %s\n", rfcomm_channel_nr, bd_addr_to_str(event_addr));
                    rfcomm_accept_connection(rfcomm_cid);
					break;
*/					
				case RFCOMM_EVENT_CHANNEL_OPENED:
					if (rfcomm_event_channel_opened_get_status(packet)) {
                        printf("RFCOMM channel open failed, status 0x%02x\n", rfcomm_event_channel_opened_get_status(packet));
                        state = START_INQUIRY;
                        gap_inquiry_start(QUERY_DURATION);
                    } else {
                        rfcomm_cid = rfcomm_event_channel_opened_get_rfcomm_cid(packet);
                        rfcomm_mtu = rfcomm_event_channel_opened_get_max_frame_size(packet);
                        bd_addr_t add_buff;
                        gap_local_bd_addr(add_buff);
                        set_client_device_name(add_buff);
                        cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true);

                        // disable page/inquiry scan to get max performance
                        //gap_discoverable_control(0);
                        //gap_connectable_control(0);
                    }

					break;
                case RFCOMM_EVENT_CAN_SEND_NOW:
                    rfcomm_send(rfcomm_cid, client_buff, strlen(client_buff));
                    break;


                case RFCOMM_EVENT_CHANNEL_CLOSED:
                    printf("RFCOMM channel closed\n");
                    rfcomm_cid = 0;
                    spp_server_channel=0;
                    cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);

                    // re-enable page/inquiry scan again
                    state = START_INQUIRY;
                    gap_inquiry_start(QUERY_DURATION);

                    //gap_discoverable_control(0);
                    //gap_connectable_control(0);
                    break;



                default:
                    break;
			}
            break;
                        
        case RFCOMM_DATA_PACKET:
            // data in packet
            process_data(packet, size);

            break;

        default:
            break;
	}

}
void switch_callback(uint gpio, uint32_t event_mask) {
    if (gpio == TOUCH_PIN && event_mask == GPIO_IRQ_EDGE_RISE) {
        sprintf(client_buff, "{\"name\":\"S0\",\"type\":\"cmd\",\"data\":\"switch\"}");
        rfcomm_request_can_send_now_event(spp_server_channel);
    }
}

int main()
{
    stdio_init_all();

    if (cyw43_arch_init()) {
        puts("cyw43 init error");
        return 0;
    }
    cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);

    gpio_set_irq_enabled_with_callback(TOUCH_PIN, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &switch_callback);
    
    ws2812_init(pio0, 0, WS2812_PIN);

    l2cap_init();

    rfcomm_init();

    handle_sdp_client_query_request.callback = &handle_start_sdp_client_query;
    sdp_client_register_query_callback(&handle_sdp_client_query_request);

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

    //gap_ssp_set_io_capability(SSP_IO_CAPABILITY_DISPLAY_YES_NO);
    gap_ssp_set_io_capability(SSP_IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
    gap_set_local_name("PiocW_Client_0");


    gap_discoverable_control(0);
    gap_connectable_control(0);
    // turn on!
	hci_power_control(HCI_POWER_ON);


    
  
    

    while(1) {
        tight_loop_contents();
    }

    return 0;
}

沒有留言:

張貼留言