当前位置: 首页 > 工具软件 > btstack > 使用案例 >

用btstack开发一个简单的蓝牙自拍杆

皇甫智明
2023-12-01

蓝牙自拍杆一些背景知识

蓝牙自拍杆HID描述符介绍

该描述符支持单拍,连拍,调焦,前后摄像头切换,拍照录像切换。其中,调焦,前后摄像头切换,拍照录像切换功能需要特定的手机才能支持。

0x05,0x0C,       // Usage Page (Consumer)
0x09,0x01,       // Usage (Consumer Control)
0xA1,0x01,       // Collection
0x85,0x01,       // Report ID (1)
0x09,0xCD,       // Usage (None)
0x09,0xA0,       // Usage (VCR Plus)
0x09,0x82,       // Usage (Mode Step)
0x09,0xB6,       // Usage (Scan Previous Track)
0x09,0xEA,       // Usage (Volume Decrement)
0x09,0xE9,       // Usage (Volume Decrement)
0x0A,0x2D,0x02,  // Usage (AC Zoom In)
0x0A,0x2E,0x02,  // Usage (AC Zoom Out)
0x15,0x00,       // Logical Minimum (0)
0x25,0x01,       // Logical Maximum (1)
0x75,0x01,       // Report Size (1)
0x95,0x08,       // Report Count (8)
0x81,0x02,       // Input
0xC0,            // End Collection
功能KeyCode说明
单拍/连拍0x10/0x20就是音量加/减 按键
拍照/录像切换0x02
前/后摄像头切换0x04
调焦缩小0x80
调焦放大0x40
按键释放0x00

开发流程

添加源码文件

在btstack/example 下添加 hid_selfie_stick.c文件,源码如下:


#define BTSTACK_FILE__ "hid_selfie_stick.c"
 

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>

#include "btstack.h"

#ifdef HAVE_BTSTACK_STDIN
#include "btstack_stdin.h"
#endif


// 蓝牙自拍杆示例

// to enable demo text on POSIX systems
// #undef HAVE_BTSTACK_STDIN

// timing of keypresses
#define TYPING_KEYDOWN_MS  60
#define TYPING_DELAY_MS    20

// When not set to 0xffff, sniff and sniff subrating are enabled
static uint16_t host_max_latency = 1600;
static uint16_t host_min_timeout = 3200;

#define REPORT_ID 0x01

// close to USB HID Specification 1.1, Appendix B.1
const uint8_t hid_descriptor_selfie_stick[] = {
    0x05,0x0C,       // Usage Page (Consumer)
    0x09,0x01,       // Usage (Consumer Control)
    0xA1,0x01,       // Collection

    0x85,0x01,       // Report ID (1)
    0x09,0xCD,       // Usage (None)

    0x09,0xA0,       // Usage (VCR Plus)
    0x09,0x82,       // Usage (Mode Step)
    0x09,0xB6,       // Usage (Scan Previous Track)
    0x09,0xEA,       // Usage (Volume Decrement)
    0x09,0xE9,       // Usage (Volume Decrement)
    0x0A,0x2D,0x02,  // Usage (AC Zoom In)
    0x0A,0x2E,0x02,  // Usage (AC Zoom Out)

    0x15,0x00,       // Logical Minimum (0)
    0x25,0x01,       // Logical Maximum (1)
    0x75,0x01,       // Report Size (1)
    0x95,0x08,       // Report Count (8)
    0x81,0x02,       // Input

    0xC0,            // End Collection
};

// 
#define CHAR_ILLEGAL     0xff
#define CHAR_RETURN     '\n'
#define CHAR_ESCAPE      27
#define CHAR_TAB         '\t'
#define CHAR_BACKSPACE   0x7f


// STATE

static uint8_t hid_service_buffer[300];
static uint8_t device_id_sdp_service_buffer[100];
static const char hid_device_name[] = "BTstack HID selfie_stick";
static btstack_packet_callback_registration_t hci_event_callback_registration;
static uint16_t hid_cid;
static uint8_t hid_boot_device = 0;

// HID Report sending
static uint8_t                send_buffer_storage[16];
static btstack_ring_buffer_t  send_buffer;
static btstack_timer_source_t send_timer;
static uint8_t                send_modifier;
static uint8_t                send_keycode;
static bool                   send_active;

#ifdef HAVE_BTSTACK_STDIN
static bd_addr_t device_addr;
static const char * device_addr_string = "00:1F:20:86:DF:52";
#endif

static enum {
    APP_BOOTING,
    APP_NOT_CONNECTED,
    APP_CONNECTING,
    APP_CONNECTED
} app_state = APP_BOOTING;


static void send_report(uint8_t keycode){
    uint8_t report[] = { 0xa1, REPORT_ID, keycode};
    hid_device_send_interrupt_message(hid_cid, &report[0], sizeof(report));
}

static void trigger_key_up(btstack_timer_source_t * ts){
    UNUSED(ts);
    send_report(0);//释放按键
    // hid_device_request_can_send_now_event(hid_cid);
}


// Demo Application

#ifdef HAVE_BTSTACK_STDIN

// On systems with STDIN, we can directly type on the console

static void stdin_process(char character){
    switch (app_state){
        case APP_BOOTING:
        case APP_CONNECTING:
            // ignore
            break;
        case APP_CONNECTED:// 连接完成后才能操作
            switch (character)
            {
                case 'D'://单拍——减小音量键
                    send_keycode = 0x10;
                    break;
                case 'U'://连拍——增加音量键
                    send_keycode = 0x20;
                    break;
                case 't'://拍照/录像切换
                    send_keycode = 0x02;
                    break;
                case 'T':// 前/后摄像头切换
                    send_keycode = 0x04;
                    break;
                case 'S'://调焦缩小
                    send_keycode = 0x80;
                    break;
                case 'A'://调焦放大
                    send_keycode = 0x40;
                    break;

                default:
                    break;
            }
            if(send_keycode){
                send_report(send_keycode);
                send_keycode = 0x00;
                btstack_run_loop_set_timer_handler(&send_timer, trigger_key_up);
                btstack_run_loop_set_timer(&send_timer, TYPING_KEYDOWN_MS);
                btstack_run_loop_add_timer(&send_timer);
            }
            break;
        case APP_NOT_CONNECTED:
            printf("Connecting to %s...\n", bd_addr_to_str(device_addr));
            hid_device_connect(device_addr, &hid_cid);
            break;
        default:
            btstack_assert(false);
            break;
    }
}
#else

// On embedded systems, send constant demo text

#define TYPING_DEMO_PERIOD_MS 100

static const char * demo_text = "\n\nHello World!\n\nThis is the BTstack HID selfie_stick Demo running on an Embedded Device.\n\n";
static int demo_pos;
static btstack_timer_source_t demo_text_timer;

#endif

static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t * packet, uint16_t packet_size){
    UNUSED(channel);
    UNUSED(packet_size);
    uint8_t status;
    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;
                    app_state = APP_NOT_CONNECTED;
                    break;

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

                case HCI_EVENT_HID_META:
                    switch (hci_event_hid_meta_get_subevent_code(packet)){
                        case HID_SUBEVENT_CONNECTION_OPENED:
                            status = hid_subevent_connection_opened_get_status(packet);
                            if (status != ERROR_CODE_SUCCESS) {
                                // outgoing connection failed
                                printf("Connection failed, status 0x%x\n", status);
                                app_state = APP_NOT_CONNECTED;
                                hid_cid = 0;
                                return;
                            }
                            app_state = APP_CONNECTED;
                            hid_cid = hid_subevent_connection_opened_get_hid_cid(packet);
#ifdef HAVE_BTSTACK_STDIN                        
                            printf("HID Connected, please start typing...\n");
#else                        
                            printf("HID Connected, sending demo text...\n");
#endif
                            break;
                        case HID_SUBEVENT_CONNECTION_CLOSED:
                            btstack_run_loop_remove_timer(&send_timer);
                            printf("HID Disconnected\n");
                            app_state = APP_NOT_CONNECTED;
                            hid_cid = 0;
                            break;
                        case HID_SUBEVENT_CAN_SEND_NOW:
                            // if (!send_keycode){
                            //     send_report(0);//释放按键
                            // }
                            break;
                        default:
                            break;
                    }
                    break;
                default:
                    break;
            }
            break;
        default:
            break;
    }
}

/* @section Main Application Setup
 *
 * @text Listing MainConfiguration shows main application code. 
 * To run a HID Device service you need to initialize the SDP, and to create and register HID Device record with it. 
 * At the end the Bluetooth stack is started.
 */

/* LISTING_START(MainConfiguration): Setup HID Device */

int btstack_main(int argc, const char * argv[]);
int btstack_main(int argc, const char * argv[]){
    (void)argc;
    (void)argv;

    // allow to get found by inquiry
    gap_discoverable_control(1);
    // use Limited Discoverable Mode; Peripheral; selfie_stick as CoD
    gap_set_class_of_device(0x2540);
    // set local name to be identified - zeroes will be replaced by actual BD ADDR
    gap_set_local_name("HID selfie_stick 00:00:00:00:00:00");
    // allow for role switch in general and sniff mode
    gap_set_default_link_policy_settings( LM_LINK_POLICY_ENABLE_ROLE_SWITCH | LM_LINK_POLICY_ENABLE_SNIFF_MODE );
    // allow for role switch on outgoing connections - this allow HID Host to become master when we re-connect to it
    gap_set_allow_role_switch(true);

    // L2CAP
    l2cap_init();

#ifdef ENABLE_BLE
    // Initialize LE Security Manager. Needed for cross-transport key derivation
    sm_init();
#endif

    // SDP Server
    sdp_init();
    memset(hid_service_buffer, 0, sizeof(hid_service_buffer));

    uint8_t hid_virtual_cable = 0;
    uint8_t hid_remote_wake = 1;
    uint8_t hid_reconnect_initiate = 1;
    uint8_t hid_normally_connectable = 1;

    hid_sdp_record_t hid_params = {
        // hid sevice subclass 2540 selfie_stick, hid counntry code 33 US
        0x2540, 33, 
        hid_virtual_cable, hid_remote_wake, 
        hid_reconnect_initiate, hid_normally_connectable,
        hid_boot_device,
        host_max_latency, host_min_timeout,
        3200,
        hid_descriptor_selfie_stick,
        sizeof(hid_descriptor_selfie_stick),
        hid_device_name
    };
    
    hid_create_sdp_record(hid_service_buffer, 0x10001, &hid_params);

    printf("HID service record size: %u\n", de_get_len( hid_service_buffer));
    sdp_register_service(hid_service_buffer);

    // See https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers if you don't have a USB Vendor ID and need a Bluetooth Vendor ID
    // device info: BlueKitchen GmbH, product 1, version 1
    device_id_create_sdp_record(device_id_sdp_service_buffer, 0x10003, DEVICE_ID_VENDOR_ID_SOURCE_BLUETOOTH, BLUETOOTH_COMPANY_ID_BLUEKITCHEN_GMBH, 1, 1);
    printf("Device ID SDP service record size: %u\n", de_get_len((uint8_t*)device_id_sdp_service_buffer));
    sdp_register_service(device_id_sdp_service_buffer);

    // HID Device
    hid_device_init(hid_boot_device, sizeof(hid_descriptor_selfie_stick), hid_descriptor_selfie_stick);
       
    // register for HCI events
    hci_event_callback_registration.callback = &packet_handler;
    hci_add_event_handler(&hci_event_callback_registration);

    // register for HID events
    hid_device_register_packet_handler(&packet_handler);

#ifdef HAVE_BTSTACK_STDIN
    sscanf_bd_addr(device_addr_string, device_addr);
    btstack_stdin_setup(stdin_process);
#endif

    // turn on!
    hci_power_control(HCI_POWER_ON);
    return 0;
}
/* LISTING_END */
/* EXAMPLE_END */

btstack_config.h配置注意点

由于例程时采用串口输入字母来模拟自拍杆的一些动作的,所以要在这个头文件中需要定义HAVE_BTSTACK_STDIN

#define HAVE_BTSTACK_STDIN

测试使用

在串口助手中发送以下字母,可以实现对应的功能
D:单拍——减小音量键
U:连拍——增加音量键
t:拍照/录像切换
T:前/后摄像头切换
S:调焦缩小
A:调焦放大
遗留问题:本人用自己的手机测试发现,调焦缩小与放大没有功能,D与U都是单拍。希望懂的朋友指点一下

 类似资料: