linux下bluetooth程式設計(三)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下bluetooth程式設計(八)SDP層程式設計Linux程式設計
- linux下bluetooth程式設計(四)L2CAP層程式設計Linux程式設計
- linux下bluetooth程式設計(六)L2CAP層程式設計例項Linux程式設計
- linux下bluetooth程式設計(五)bluetooth與socketLinux程式設計
- linux下bluetooth程式設計(一)基礎概念Linux程式設計
- linux下bluetooth程式設計(七)SDP協議Linux程式設計協議
- linux下bluetooth程式設計(二)blueZ協議棧Linux程式設計協議
- 程式設計師程式設計能力層次模型程式設計師模型
- Linux程式設計之三(轉)Linux程式設計
- 程式碼分層設計
- (整合)Linux下的多程式程式設計Linux程式設計
- linux下的SHELL程式設計Linux程式設計
- Linux 程式設計之Shell程式設計(轉)Linux程式設計
- Linux的shell程式設計(三)(轉)Linux程式設計
- Linux入門---(三)Shell程式設計Linux程式設計
- MFC程式設計(三)C程式程式設計
- linux下使用makefile方式程式設計主程式Linux程式設計
- 程式、程式設計與三論程式設計
- Linux下的OpenGL程式設計(轉)Linux程式設計
- Linux程式設計Linux程式設計
- 《Go 語言程式設計》讀書筆記(十一)底層程式設計Go程式設計筆記
- Linux Shell程式設計(1)——shell程式設計簡介Linux程式設計
- 程式碼分層的設計之道
- 程式設計師的十層樓程式設計師
- Linux下串列埠程式設計基礎Linux串列埠程式設計
- linux程式設計下signal()函式Linux程式設計函式
- Linux下TCP網路程式設計流程LinuxTCP程式設計
- Linux下C語言程式設計(轉)LinuxC語言程式設計
- linux下TCP socket程式設計初步(1)LinuxTCP程式設計
- Linux環境下的Socket程式設計Linux程式設計
- Linux程式控制程式設計Linux程式設計
- Linux Bash程式設計Linux程式設計
- Linux jpeg程式設計Linux程式設計
- linux shell 程式設計Linux程式設計
- linux Socket 程式設計Linux程式設計
- linux shell程式設計Linux程式設計
- Linux module 程式設計Linux程式設計
- Android Bluetooth HCI log 詳解Android