日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用BlueZ连接蓝牙手柄

發布時間:2023/12/14 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用BlueZ连接蓝牙手柄 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、HOGP協議

常見的藍牙鼠標、藍牙鍵盤、藍牙手柄,它們都屬于HID設備,但與有線設備不同的是,有線鼠標等設備屬于USB HID設備,而藍牙鼠標等設備屬于Bluetooth HID設備,即協議是一樣的,只是通信方式不同。HOGP是HID Over GATT Profile的縮寫,即藍牙HID設備是通過BLE的GATT來實現HID協議的。下圖是手機BLE調試APP掃描獲取到的手柄廣播信息,點擊"RAW"后可以看到原始的廣播數據,解析結果如下:

  • tpye 0x01:藍牙的FLAG信息,0x06表示設備僅支持BLE,不支持經典藍牙,廣播類型為通用廣播。
  • type 0x03:UUID16_ALL,0x1218是16位的HID服務的UUID,這里已經初步表明設備是一個藍牙HID設備。
  • type 0x19:GAP的apperance,即設備的外觀,0xC303表示設備是一個藍牙手柄(Joystick)。
  • type 0x09:藍牙設備的全名,該手柄的設備名叫"269"。



連接藍牙手柄后,可以發現設備支持的服務,其中一個服務是Human Interface Device,該服務也進一步表明了該設備是一個藍牙HID設備。Bluez在連接藍牙HID設備后,在發現服務時如果發現了HID服務,就會讀取Report Map,這個是HID的報告描述符,通過解析這張表就可以知道設備支持哪些功能了,解析功能內核會幫我們完成。

二、內核配置

內核對藍牙HID的支持分為2部分,一部分是藍牙部分,另一部分就是uhid。

在藍牙協議層使能HID協議:

內核驅動中使能uhid:

三、HOGP原理

3.1 Bluez創建HID設備

當主機連上藍牙手柄時,Bluez會發現PnP ID服務,讀取PnP ID服務可以獲取設備的制造商信息,例如VIP和PID,串口會有相應的打印。在向內核注冊HID設備時,VIP和PID是非常重要的參數。

bluetoothd[536]: profiles/deviceinfo/dis.c:read_pnpid_cb() source: 0x01 vendor: 0x1949 product: 0x0402 version: 0x0000

當Bluez繼續發現服務時,會發現HID服務,于是hog-lib.c中的char_discovered_cb函數會被調用,該函數會解析HID服務下所有特征值,其中有一部是比對report_map_uuid,report_map_uuid是0x2A4B,即在手機BLE調試APP上看到的Report Map特征值。

static void char_discovered_cb(uint8_t status, GSList *chars, void *user_data) { /* ...... */else if (bt_uuid_cmp(&uuid, &report_map_uuid) == 0) {DBG("HoG discovering report map");read_char(hog, hog->attrib, chr->value_handle,report_map_read_cb, hog);discover_external(hog, hog->attrib, start, end, hog);} /* ...... */ }

讀到該特征值后會回調report_map_read_cb函數,該函數會打印設備的報表描述符,并向內核申請創建HID設備。核心代碼如下:

static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen,gpointer user_data) {/* ....... */DBG("Report MAP:");for (i = 0; i < vlen;) {ssize_t ilen = 0;bool long_item = false;if (get_descriptor_item_info(&value[i], vlen - i, &ilen,&long_item)) {/* Report ID is short item with prefix 100001xx */if (!long_item && (value[i] & 0xfc) == 0x84)hog->has_report_id = TRUE;DBG("\t%s", item2string(itemstr, &value[i], ilen));i += ilen;} else {error("Report Map parsing failed at %d", i);/* Just print remaining items at once and break */DBG("\t%s", item2string(itemstr, &value[i], vlen - i));break;}}/* create uHID device */memset(&ev, 0, sizeof(ev));ev.type = UHID_CREATE;bt_io_get(g_attrib_get_channel(hog->attrib), &gerr,BT_IO_OPT_SOURCE, ev.u.create.phys,BT_IO_OPT_DEST, ev.u.create.uniq,BT_IO_OPT_INVALID);/* Phys + uniq are the same size (hw address type) */for (i = 0;i < (int)sizeof(ev.u.create.phys) && ev.u.create.phys[i] != 0;++i) {ev.u.create.phys[i] = tolower(ev.u.create.phys[i]);ev.u.create.uniq[i] = tolower(ev.u.create.uniq[i]);}if (gerr) {error("Failed to connection details: %s", gerr->message);g_error_free(gerr);return;}strncpy((char *) ev.u.create.name, hog->name,sizeof(ev.u.create.name) - 1);ev.u.create.vendor = hog->vendor;ev.u.create.product = hog->product;ev.u.create.version = hog->version;ev.u.create.country = hog->bcountrycode;ev.u.create.bus = BUS_BLUETOOTH;ev.u.create.rd_data = value;ev.u.create.rd_size = vlen;err = bt_uhid_send(hog->uhid, &ev);if (err < 0) return;bt_uhid_register(hog->uhid, UHID_OUTPUT, forward_report, hog);bt_uhid_register(hog->uhid, UHID_GET_REPORT, get_report, hog);err = bt_uhid_register(hog->uhid, UHID_SET_REPORT, set_report, hog);hog->uhid_created = true;DBG("HoG created uHID device"); }

相應串口打印如下:

bluetoothd[536]: profiles/input/hog-lib.c:report_map_read_cb() HoG inspecting report map bluetoothd[536]: profiles/input/hog-lib.c:report_map_read_cb() Report MAP: bluetoothd[536]: profiles/input/hog-lib.c:report_map_read_cb() 05 0d bluetoothd[536]: profiles/input/hog-lib.c:report_map_read_cb() 09 04 bluetoothd[536]: profiles/input/hog-lib.c:report_map_read_cb() a1 01 bluetoothd[536]: profiles/input/hog-lib.c:report_map_read_cb() 85 01 bluetoothd[536]: profiles/input/hog-lib.c:report_map_read_cb() 09 22 /* 太長了,省略大部分 */ bluetoothd[536]: profiles/input/hog-lib.c:report_map_read_cb() 75 08 bluetoothd[536]: profiles/input/hog-lib.c:report_map_read_cb() 09 53 bluetoothd[536]: profiles/input/hog-lib.c:report_map_read_cb() 95 01 bluetoothd[536]: profiles/input/hog-lib.c:report_map_read_cb() b1 02 bluetoothd[536]: profiles/input/hog-lib.c:report_map_read_cb() c0 bluetoothd[536]: profiles/input/hog-lib.c:report_map_read_cb() c0 bluetoothd[536]: profiles/input/hog-lib.c:report_map_read_cb() HoG created uHID device

3.2 內核創建HID設備

正常來說,到現在為止,內核中應該已經創建了藍牙手柄的input設備節點,但實際調試過程發現卻沒有,猜想應該哪里失敗了,因此有必要深入了解下內核對Bluez創建HID設備請求的處理流程。

在內核配置中開啟uhid的支持后,會生成一個/dev/uhid設備節點,用戶層可以通過該文件操作hid操作,Bluez正是通過該文件向內核注冊HID設備。具體來說,report_map_read_cb函數中的bt_uhid_send函數會/dev/uhid寫入一個UHID_CREATE消息,內核驅動中的uhid.c中的uhid_char_write函數將會被調用,對于UHID_CREATE,uhid_char_write函數將會調用uhid_dev_create函數完成hid設備的創建。大致流程如下圖所示。

具體地,uhid_dev_create會喚醒專門添加uhid設備的工作隊列uhid_device_add_worker,該工作隊列會調用hid_add_device嘗試添加HID設備,hid_add_device函數會比對要注冊的設備的VIP和PID是否在已支持的列表中,比對失敗就不會創建,具體函數如下:

int hid_add_device(struct hid_device *hdev)/* ...... */if (hid_ignore_special_drivers) {hdev->group = HID_GROUP_GENERIC;} else if (!hdev->group &&!hid_match_id(hdev, hid_have_special_driver)) {ret = hid_scan_report(hdev);if (ret)hid_warn(hdev, "bad device descriptor (%d)\n", ret);}/* ...... */ }static bool hid_match_one_id(struct hid_device *hdev,const struct hid_device_id *id) {return (id->bus == HID_BUS_ANY || id->bus == hdev->bus) &&(id->group == HID_GROUP_ANY|| id->group == hdev->group) &&(id->vendor == HID_ANY_ID || id->vendor == hdev->vendor) &&(id->product == HID_ANY_ID || id->product == hdev->product); }const struct hid_device_id *hid_match_id(struct hid_device *hdev,const struct hid_device_id *id) {for (; id->bus; id++)if (hid_match_one_id(hdev, id))return id;return NULL; }

hid_have_special_driver是一個很大的數組,里面記錄了當前已支持設備的HID類型(USB還是BLE)、VID、PID。調試過程中之所以創建HID設備失敗就是因為藍牙手柄的VIP和PID不在該設備列表中。修改方法有兩種:一是可以修改hid_have_special_driver數組,添加藍牙手柄的VID和PID;二是修改hid_match_one_id函數,增加HID_GROUP_GENERIC的支持。修改完畢后,內核成功創建手柄HID設備,內核打印如下:

[260283.344921] input: 269 as /devices/virtual/misc/uhid/0005:1949:0402.0001/input/input0 [260283.345556] hid-generic 0005:1949:0402.0001: input,hidraw0: BLUETOOTH HID v0.00 Device [269] on 78:f2:35:0e:d0:46

查看/dev/input目錄,下面多了兩個輸入設備:event0和js0。解析event0即可獲取手柄的數據。

/ # ls /dev/input/ event0 js0 mice/ # cat /proc/bus/input/devices I: Bus=0005 Vendor=1949 Product=0402 Version=0000 N: Name="269" P: Phys=40:24:b2:d1:f2:a8 S: Sysfs=/devices/virtual/misc/uhid/0005:1949:0402.0004/input/input3 U: Uniq=03:21:04:21:29:ad H: Handlers=kbd leds js0 event0 B: PROP=0 B: EV=12001f B: KEY=3007f 0 0 0 0 483ffff 17aff32d bf544446 0 ffff0000 1 130f93 8b17c000 677bfa d9415fed e09effdf 1cfffff ffffffff fffffffe B: REL=40 B: ABS=1 30627 B: MSC=10 B: LED=1f

3.3 input子系統

Linux的input子系統框架如下圖所示,圖中沒有包含Bluetooth HID設備,但實際Bluetooth HID設備也適用于該框架。

當向內核注冊HID設備時,會觸發經典的device和driver匹配機制,probe函數將被調用,具體調用關系如下:

hid_device_probehid_hw_starthid_connecthidinput_connecthidinput_allocate

hid_device_probe函數在注冊HID設備時會被回調,hidinput_allocate函數則申請了input_dev,注冊到input子系統。

整條數據鏈路如下:當手柄的按鍵或搖桿被操作時,bluetoothd進程將收到手柄的notify數據,bluetoothd通過uhid向HID系統發送UHID_INPUT消息,HID驅動會根據Report Map將數據轉換成對應的input_event事件并上報,用戶層解析/dev/input目錄下對應的文件即可獲取手柄的狀態。

四、手柄數據解析

手柄有多種模式:自定義模式和標準模式。在自定義模式下,用戶可以通過專用的APP來設置每個按鍵對應的坐標,以此來靈活適配各種使用場景(例如適配王者榮耀的鍵位或英雄聯盟的鍵位)。在標準模式下,搖桿返回的是坐標值,而按鍵返回的則是按鍵值。

讀取手柄input_event消息并解析即可獲得手柄按鍵的坐標。手柄一共有三種不同的輸入:

  • 搖桿:搖桿一共有左右兩個。搖桿的事件類型為EV_ABS,左搖桿X軸返回ABS_X類型坐標值,左搖桿Y軸返回ABS_Y類型坐標值;右搖桿X軸返回ABS_Z類型坐標值,右搖桿Y軸返回ABS_RZ類型坐標值。搖桿中心的坐標為(128,128),搖桿的左上角為坐標原點(0,0)。
  • 方向鍵:方向鍵共有上下左右4個按鍵。方向鍵事件類型也為EV_ABS,其中左鍵和右鍵返回ABS_HAT0X類型數據,當值為-1時表示左鍵按下,當值為1時表示右鍵按下;當值為0時表示左右鍵沒有被按下;同理,上鍵和下鍵返回ABS_HAT0Y類型數據,當值為-1時表示上鍵按下,當值為1時表示下鍵按下;當值為0時表示上下鍵沒有被按下。
  • 普通按鍵:普通按鍵包含了X、Y、A、B、LB、RB、LT、RT、Select、Start這10個鍵。事件類型為EV_KEY,數據類型即為鍵值,例如0x0130表示A鍵,當值為0時表示該按鍵處于彈起狀態,當值為1時表示該按鍵正在被按下(觸發),當值為2時表示該按鍵處于被長按的狀態。
  • 測試代碼如下:

    #include <stdio.h> #include "string.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <linux/input.h> #include <poll.h> #include <unistd.h> #include "stdint.h"/* 按鍵編碼 */ #define BUTTON_CODE_LB 0x0136 #define BUTTON_CODE_RB 0x0137 #define BUTTON_CODE_LT 0x0138 #define BUTTON_CODE_RT 0x0139 #define BUTTON_CODE_SELECT 0x013A #define BUTTON_CODE_START 0x013B #define BUTTON_CODE_A 0x0130 #define BUTTON_CODE_B 0x0131 #define BUTTON_CODE_X 0x0133 #define BUTTON_CODE_Y 0x0134/* 左搖桿或右搖桿 */ typedef enum {ROCKER_LEFT,ROCKER_RIGHT,ROCKER_MAX, }RockerType;typedef struct {uint8_t x;uint8_t y; }JsRocker;typedef struct {uint16_t button_code;char *button_name; }Button;int main(int argc, char **argv) {struct input_event event_joystick ;struct pollfd pollfds; int fd = -1 ;int i,ret;uint8_t last_code = 0;JsRocker rocker[ROCKER_MAX];const Button button_map[] = {{BUTTON_CODE_LB, "LB"},{BUTTON_CODE_RB, "RB"},{BUTTON_CODE_LT, "LT"},{BUTTON_CODE_RT, "RT"},{BUTTON_CODE_SELECT,"SELECT"},{BUTTON_CODE_START, "START"},{BUTTON_CODE_A, "A"},{BUTTON_CODE_B, "B"},{BUTTON_CODE_X, "X"},{BUTTON_CODE_Y, "Y"}};const char *button_state_table[] = {"release", "press", "hold"};memset(rocker, 0, sizeof(rocker));fd = open("/dev/input/event0",O_RDONLY);if(fd == -1){printf("open joystick event failed\n");return -1;}pollfds.fd = fd;pollfds.events = POLLIN;while(1){ret = poll(&pollfds, 1, -1);if(ret > 0){if(read(fd, &event_joystick, sizeof(event_joystick)) <= 0){close (fd);printf("read err\n");return -1;}switch(event_joystick.type){case EV_SYN:if(last_code == ABS_X || last_code == ABS_Y)printf("lelt rocker x=%d, y =%d\n", rocker[ROCKER_LEFT].x, rocker[ROCKER_LEFT].y);else if(last_code == ABS_Z || last_code == ABS_RZ)printf("right rocker x=%d, y =%d\n", rocker[ROCKER_RIGHT].x, rocker[ROCKER_RIGHT].y);break;case EV_ABS: /* 左搖桿事件,需要等同步事件同時獲取x和y坐標 */if(event_joystick.code == ABS_X)rocker[ROCKER_LEFT].x = event_joystick.value; else if(event_joystick.code == ABS_Y)rocker[ROCKER_LEFT].y = event_joystick.value;/* 右搖桿事件,需要等同步事件同時獲取x和y坐標 */else if(event_joystick.code == ABS_Z)rocker[ROCKER_RIGHT].x = event_joystick.value; else if(event_joystick.code == ABS_RZ)rocker[ROCKER_RIGHT].y = event_joystick.value; /* 方向鍵 X方向有鍵被按下 */else if(event_joystick.code == ABS_HAT0X) {if(event_joystick.value == -1)printf("dir button: left\n");else if(event_joystick.value == 1)printf("dir button: right\n");elseprintf("dir button: none\n");}/* 方向鍵 Y方向有鍵被按下 */else if(event_joystick.code == ABS_HAT0Y) {if(event_joystick.value == -1)printf("dir button: up\n");else if(event_joystick.value == 1)printf("dir button: down\n");elseprintf("dir button: none\n"); }break;case EV_KEY: for(i = 0; i < sizeof(button_map)/ sizeof(button_map[0]); i++){if(event_joystick.code == button_map[i].button_code){printf("button %s %s\n", button_map[i].button_name, button_state_table[event_joystick.value]);}}break;default:break;}last_code = event_joystick.code;}else if(ret == 0){printf("timeout\n");}else{printf("err\n");close (fd);return -1;}}close (fd);return 0; }

    執行測試程序后,隨意撥動手柄的搖桿或按下手柄的按鍵,串口輸出如下:

    lelt rocker x=105, y =124 lelt rocker x=75, y =109 lelt rocker x=62, y =103 lelt rocker x=54, y =105 lelt rocker x=51, y =105 lelt rocker x=50, y =106 lelt rocker x=50, y =109 lelt rocker x=50, y =124 lelt rocker x=50, y =128 lelt rocker x=124, y =128 lelt rocker x=128, y =128 right rocker x=132, y =128 right rocker x=166, y =128 right rocker x=200, y =128 right rocker x=226, y =128 right rocker x=251, y =128 right rocker x=255, y =128 right rocker x=239, y =128 right rocker x=184, y =128 right rocker x=128, y =128 dir button: up dir button: none dir button: left dir button: none dir button: down dir button: none dir button: right dir button: none button X press button X relese button X press button X hold button X hold button X hold button X relese button Y press button Y relese button LT press button LT relese button RT press button RT relese button LB press button LB relese button RB press button RB relese

    總結

    以上是生活随笔為你收集整理的使用BlueZ连接蓝牙手柄的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。