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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux 蓝牙读写,实战Linux Bluetooth编程(三) HCI层编程

發布時間:2024/7/19 linux 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux 蓝牙读写,实战Linux Bluetooth编程(三) HCI层编程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:Sam (甄峰)

(HCI協議簡介,HCI 在BlueZ中的實現以及HCI編程接口)

1. HCI層協議概述:

HCI提供一套統一的方法來訪問Bluetooth底層。如圖所示:

從圖上可以看出,Host Controller Interface(HCI)??就是用來溝通Host和Module。Host通常就是PC, Module則是以各種物理連接形式(USB,serial,pc-card等)連接到PC上的bluetooth Dongle。

在Host這一端:application,SDP,L2cap等協議都是軟件形式提出的(Bluez中是以kernel層程序)。在Module這一端:Link Manager, BB, 等協議都是硬件中firmware提供的。

而HCI則比較特殊,它一部分在軟件中實現,用來給上層協議和程序提供訪問接口(Bluez中,hci.c hci_usb.c,hci_sock.c等).另一部分也是在Firmware中實現,用來將軟件部分的指令等用底層協議明白的方式傳遞給底層。

居于PC的上層程序與協議和居于Modules的下層協議之間通過HCI溝通,有4種不同形式的傳輸:Commands, Event, ACL Data, SCO/eSCO Data。

1.1. HCI Command:

HCI Command是Host向Modules發送命令的一種方式。HCI Command Packet結構如下:

OpCode用來唯一標識HCI Command.它由2部分組成,10bit的Opcode Command. 6bit的Opcode Group。

1.1.1: OpCode Group:

Linux Kernel(BlueZ)中,~/include/net/bluetooth/hci.h中定義了OpCode Group。

#define OGF_LINK_CTL?0x01

#define OGF_LINK_POLICY?0x02

#define OGF_HOST_CTL?0x03

#define OGF_INFO_PARAM?0x04

#define OGF_STATUS_PARAM?0x05

它們代表了不同的Command Group:

OGF_LINK_CTL:?Link control,這個Command Group中的Command允許Host控制與其它bluetooth device 的連接。

OGF_LINK_POLICY?:Link Policy。這個Command Group中的Command允許調整Link Manager control.

OGF_HOST_CTL:?Control and Baseband.

1.1.2: Opcode Command:

用來在同一個Group內唯一識別Command。~/include/net/bluetooth/hci.h中定義。

1.2: HCI Event:

Modules向Host發送一些信息,使用HCI Event。Event Packet結構如下:

HCI Event分3種:Command complete Event, Command States Event,Command Subsequently Completend.

Command complete Event: 如果Host發送的Command可以立刻有結果,則會發送此類Event。也就是說,如果發送的Command只與本地Modules有關,不與remote設備打交道,則使用Command complete Event。例如:HCI_Read_Buffer_Size.

Command States Event:如果Host發送的Command不能立刻得知結果,則發送此類Event。Host發送的Command執行要與Remote設備打交道,則必然無法立刻得知結果,所以會發送Command States Event.例如:

HCI Connect。

Command Subsequently Completend:Command延后完成Event。例如:連接已建立。

下圖是一個Command-Event例子:

從這里可以看出,如果Host發送的Command是與Remote device有關的,則會先發送Command States Event?。等動作真正完成了,再發送?Command Subsequently Completend。

HCI ACL與SCO數據,這里就不多講了。只需要明白,l2cap數據是通過ACL數據傳輸給remote device的。

下圖很明白的展示了l2cap數據如何一步一步轉化為USB數據并傳遞給底層協議的。

很明顯,一個l2cap包會按照規則先切割為多個HCI數據包。HCI數據包再通過HCI-usb這一層傳遞給USB設備。每個包又通過USB driver發送到底層。

2. HCI protocol的實現:

(稍后添加)

3. HCI 層的編程:

正如上一節所說,HCI是溝通上層協議以及程序與底層硬件協議的通道。所以,通過HCI發送的Command都是上層協議或者應用程序發送給Bluetooth Dongle的。它命令Bluetooth Dongle(或其中的硬件協議)去做什么何種動作。

3.0:得到Host上插入Dongle數目以及Dongle信息:

我們先復習一下socket的概念:

使用函數socket()建立一個Socket,就如同你有一部電話.bind()則是把這個電話和某個電話號碼(網絡地址)對應起來。

類似的,我們可以把Host理解為一個房間,這個房間有多部電話(Dongle)。

當使用socket() 打開一個HCI protocol的socket,表明得到這個房間的句柄。HOST可能會有多個Dongle。換句話說,這個房間可以有多個電話號碼。所以HCI會提供一套指令去得到這些Dongle。

// 0.?分配一個空間給?hci_dev_list_req。這里面將放所有Dongle信息。

struct hci_dev_list_req *dl;

struct hci_dev_req *dr;

struct hci_dev_info di;

int i;

if (!(dl = malloc(HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t)))) {

perror("Can't allocate memory");

exit(1);

}

dl->dev_num = HCI_MAX_DEV;

dr = dl->dev_req;

//1. 打開一個HCI socket.此socket相當于一個房間。

if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0) {

perror("Can't open HCI socket.");

exit(1);

}

// 2. 使用HCIGETDEVLIST,得到所有dongle的Device ID。存放在dl中。

if (ioctl(ctl, HCIGETDEVLIST, (void *) dl) < 0) {

perror("Can't get device list");

exit(1);

}

// 3 使用HCIGETDEVINFO,得到對應Device ID的Dongle信息。

di.dev_id = (dr+i)->dev_id;

ioctl(ctl, HCIGETDEVINFO, (void *) &di);

這樣就能得到所有Dongle信息。

struct hci_dev_info {

uint16_t dev_id;???//dongle Device ID

char?????name[8];??//Dongle name

bdaddr_t bdaddr;???//Dongle bdaddr

uint32_t flags;????//Dongle Flags:如:UP,RUNING,Down等。

uint8_t??type;???//Dongle連接方式:如USB,PC Card,UART,RS232等。

uint8_t??features[8];

uint32_t pkt_type;

uint32_t link_policy;

uint32_t link_mode;

uint16_t acl_mtu;

uint16_t acl_pkts;

uint16_t sco_mtu;

uint16_t sco_pkts;

struct???hci_dev_stats stat;??//此Dongle的數據信息,如發送多少個ACL Packet,正確多少,錯誤多少,等等。

};

3.0.1: UP和Down Bluetooth Dongle:

ioctl(ctl, HCIDEVUP, hdev)

ioctl(ctl, HCIDEVDOWN, hdev)

ctl:為使用socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)打開的Socket.

hdev: Dongle Device ID.(所以上面的Socket不需要bind,因為這邊指定了)

3.1 BlueZ提供的HCI編程接口一(針對本地Dongle的API系列):

3.1。1 打開一個HCI Socket---int hci_open_dev(int dev_id):

這個function用來打開一個HCI Socket。它首先打開一個HCI protocol的Socket(房間),并將此Socket與device ID=參數dev_id的Dongle綁定起來。只有bind后,它才將Socket句柄與Dongle對應起來。

注意,所有的HCI Command發送之前,都需要使用?hci_open_dev打開并綁定。

3.1.2: 關閉一個HCI Socket:

int hci_close_dev(int dd) //簡單的關閉使用hci_open_dev打開的Socket。

3.1.3: 向HCI Socket(對應一個Dongle)發送 request:

int hci_send_req(int dd, struct hci_request *r, int to)

BlueZ提供這個function非常有用,它可以實現一切Host向Modules發送Command的功能。

參數1:HCI Socket。

參數2:Command內容。

參數3:以milliseconds為單位的timeout.

下面詳細解釋此function和用法:

當應用程序需要向Dongle(對應為一個bind后的Socket)發送Command時,調用此function.

其中,參數一dd對應一個使用hci_open_dev()打開的Socket(Dongle)。

參數三to則為等待Dongle執行并回復命令結果的timeout.以毫秒為單位。

參數二hci_request * r?最為重要,首先看它的結構:

struct hci_request {

uint16_t ogf;????//Opcode Group

uint16_t ocf;????//Opcode Command

int??????event;??//此Command產生的Event類型。

void?????*cparam; //Command 參數

int??????clen;????//Command參數長度

void?????*rparam;??//Response 參數

int??????rlen;????//Response 參數長度

};

ogf,ocf不用多說,對應前面的圖就明白這是Group Code和Command Code。這兩項先確定下來,然后可以查HCI Spec。察看輸入參數(cparam)以及輸出參數(rparam)含義。至于他們的結構以及參數長度,則在~/include/net/bluetooth/hci.h中有定義。

至于event.如果設置,它會被setsockopt設置于Socket。

例1:得到某個連接的Policy Setting.

HCI Spec以及~/include/net/bluetooth/hci.h中均可看到,OGF=OGF_LINK_POLICY(0x02). OCF=OCF_READ_LINK_POLICY(0x0C).

因為這個Command用來讀取某個ACL連接的Policy Setting。所以輸入參數即為此連接Handle.

返回參數則包含3部分,status(Command是否順利執行), handle(連接Handle)。 policy(得到的policy值)

這就又引入了一個新問題,如何得到某個ACL連接的Handle。

可以使用ioctl HCIGETCONNINFO得到ACL 連接Handle。

ioctl(dd, HCIGETCONNINFO, (unsigned long) cr);

Connect_handle = htobs(cr->conn_info->handle);

所以完整的過程如下:

struct hci_request HCI_Request;

read_link_policy_cp Command_Param;

read_link_policy_rp Response_Param;

// 1.得到ACL Connect Handle

if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0)

{

return?-1;

}

Connect_handle = htobs(cr->conn_info->handle);

memset(&HCI_Request, 0, sizeof(HCI_Request));

memset(&Command_Param, 0 , sizeof(Command_Param));

memset(&Response_Param, 0 , sizeof(Response_Param));

// 2.填寫Command輸入參數

Command_Param.handle = Connect_handle;

HCI_Request.ogf = OGF_LINK_POLICY;??//Command組ID

HCI_Request.ocf = OCF_READ_LINK_POLICY; //Command ID

HCI_Request.cparam = &Command_Param;

HCI_Request.clen = READ_LINK_POLICY_CP_SIZE;

HCI_Request.rparam = &Response_Param;

HCI_Request.rlen = READ_LINK_POLICY_RP_SIZE;

if (hci_send_req(dd, &HCI_Request, to) < 0)

{

perror("\nhci_send_req()");

return -1;

}

//如果返回值狀態不對

if (Response_Param.status) {

return?-1;

}

//得到當前policy

*policy = Response_Param.policy;

3.1.4:幾個更基礎的function:

static inline void bacpy(bdaddr_t *dst, const bdaddr_t *src) //bdaddr copy

static inline int bacmp(const bdaddr_t *ba1, const bdaddr_t *ba2)//bdaddr 比較

3.1.5: 得到指定Dongle BDAddr:

int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to);

參數1:HCI Socket,使用hci_open_dev()打開的Socket(Dongle)。

參數2:輸出參數,其中會放置bdaddr.

參數3:以milliseconds為單位的timeout.

3.1.6: 讀寫Dongle Name:

int hci_read_local_name(int dd, int len, char *name, int to)

int hci_write_local_name(int dd, const char *name, int to)

參數1:HCI Socket,使用hci_open_dev()打開的Socket(Dongle)。

參數2:讀取或設置Name。

參數3:以milliseconds為單位的timeout.

注意:這里的Name與IOCTL HCIGETDEVINFO 得到hci_dev_info中的name不同。

3.1.7:得到HCI Version:

int hci_read_local_version(int dd, struct hci_version *ver, int to)

3.1.8:得到已經UP的Dongle BDaddr:

int hci_devba(int dev_id, bdaddr_t *bdaddr);

dev_id: Dongle Device ID.

bdaddr:輸出參數,指定Dongle如果UP, 則放置其BDAddr。

3.1.9: 得到Dongle Info:

int hci_devinfo(int dev_id, struct hci_dev_info *di)

dev_id: Dongle Device ID.

di: 此Dongle信息。

出錯返回 -1。

注意,這個Function的做法與3.0的方法完全一致。

3.1.10:從hciX中得到X:

int hci_devid(const char *str)

str: 類似 hci0這樣的字串。

如果hciX對應的Device ID(X)是現實存在且UP。則返回此設備Device ID。

3.1.11:得到BDADDR不等于參數bdaddr的Dongle Device ID:

int hci_get_route(bdaddr_t *bdaddr)

查找Dongle,發現Dongle Bdaddr不等于參數bdaddr的第一個Dongle,則返回此Dongle Device ID。

所以,如果: int hci_get_route(NULL),則得到第一個可用的Dongle Device ID。

3.1.12: 將BDADDR轉換為字符串:

int ba2str(const bdaddr_t *ba, char *str)

3.1.13: 將自串轉換為BDADDR:

int str2ba(const char *str, bdaddr_t *ba)

3.2 BlueZ提供的HCI編程接口二(針對Remote Device的API系列):

3.2.1??inquiry 遠程Bluetooth Device:

int hci_inquiry(int dev_id, int len, int nrsp, const uint8_t *lap, inquiry_info **ii, long flags)

hci_inquiry()用來命令指定的Dongle去搜索周圍所有bluetooth device.并將搜索到的Bluetooth Device bdaddr 傳遞回來。

參數1:dev_id:指定Dongle Device ID。如果此值小于0,則會使用第一個可用的Dongle。

參數2:len: 此次inquiry的時間長度(每增加1,則增加1.25秒時間)

參數3:nrsp:此次搜索最大搜索數量,如果給0。則此值會取255。

參數4:lap:BDADDR中LAP部分,Inquiry時這塊值缺省為0X9E8B33.通常使用NULL。則自動設置。

參數5:ii:存放搜索到Bluetooth Device的地方。給一個存放inquiry_info指針的地址,它會自動分配空間。并把那個空間頭地址放到其中。

參數6:flags:搜索flags.使用IREQ_CACHE_FLUSH,則會真正重新inquiry。否則可能會傳回上次的結果。

返回值是這次Inquiry到的Bluetooth Device 數目。

注意:如果*ii不是自己分配的,而是讓hci_inquiry()自己分配的,則需要調用bt_free()來幫它釋放空間。

3.2.2:得到指定BDAddr的reomte device Name:

int hci_read_remote_name(int dd, const bdaddr_t *bdaddr, int len, char *name, int to)

參數1:使用hci_open_dev()打開的Socket。

參數2:對方BDAddr.

參數3:name 長度。

參數4:(out)放置name的位置。

參數5:等待時間。

3.2.3: 讀取連接的信號強度:

int hci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to)

注意,所有對連接的操作,都會有一個參數,handle.這個參數是連接的Handle。前面講過如何得到連接Handle的。

總結

以上是生活随笔為你收集整理的Linux 蓝牙读写,实战Linux Bluetooth编程(三) HCI层编程的全部內容,希望文章能夠幫你解決所遇到的問題。

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