Android之藍牙驅動開發總結
原文鏈接:http://blog.csdn.net/manshq163com/article/details/18550767
一 Bluetooth基本概念
藍牙是無線數據和語音傳輸的開放式標准,它將各種通信設備、計算機及其終端設備、各種數字數據系統、甚至家用電器采用無線方式聯接起來。它的傳輸距離為10cm~10m,如果增加功率或是加上某些外設便可達到100m的傳輸距離。它采用2.4GHzISM頻段和調頻、跳頻技術,使用權向糾錯編碼、ARQ、TDD和基帶協議。TDMA每時隙為0.625μs,基帶符合速率為1Mb/s。藍牙支持64kb/s實時語音傳輸和數據傳輸,語音編碼為CVSD,發射功率分別為1mW、2.5mW和100mW,並使用全球統一的48比特的設備識別碼。由於藍牙采用無線接口來代替有線電纜連接,具有很強的移植性,並且適用於多種場合,加上該技術功耗低、對人體危害小,而且應用簡單、容易實現,所以易於推廣。
藍牙技術的系統結構分為三大部分:底層硬件模塊、中間協議層和高層應用。底層硬件部分包括無線跳頻(RF)、基帶(BB)和鏈路管理(LM)。無線跳頻層通過2.4GHz無需授權的ISM頻段的微波,實現數據位流的過濾和傳輸,本層協議主要定義了藍牙收發器在此頻帶正常工作所需要滿足的條件。基帶負責跳頻以及藍牙數據和信息幀的傳輸。鏈路管理負責連接、建立和拆除鏈路並進行安全控制。
二 AndroidBluetooth架構
2.1 Bluetooth架構圖
Android藍牙系統分為四個層次,內核層、BlueZ庫、BlueTooth的適配庫、BlueTooth的JNI部分、Java框架層、應用層。下面先來分析Android的藍牙協議棧。
圖1面向庫的架構視圖
Linuxkernel層:
bluez協議棧、uart驅動,h4協議,hci,l2cap, sco, rfcomm
bluez層:
這是bluez用戶空間的庫,開源的bluetooth代碼,包括很多協議,生成libbluetooth.so。
library層:
libbluedroid.so等
framework層:
實現了Headset/Handsfree 和A2DP/AVRCPprofile,但其實現方式不同Handset/Handfree是直接在bluez的RFCOMMSocket上開發的,沒有利用bluez的audioplugin,而A2DP/AVRCP是在bluez的audioplugin基礎上開發的,大大降低了實現的難度。
Android的藍牙協議棧采用BlueZ來實現,BlueZ分為兩部分:內核代碼和用戶態程序及工具集。
圖2面向進程的架構視圖
2.2 Bluetooth代碼層次結構
(1)JAVA層
frameworks/base/core/java/android/bluetooth/
包含了bluetooth的JAVA類。
(2)JNI層
frameworks/base/core/jni/android_bluetooth_開頭的文件
定義了bluez通過JNI到上層的接口。
frameworks/base/core/jni/android_server_bluetoothservice.cpp
調用硬件適配層的接口system/bluetooth/bluedroid/bluetooth.c
(3)bluez庫
external/bluez/
這是bluez用戶空間的庫,開源的bluetooth代碼,包括很多協議,生成libbluetooth.so。
(4)硬件適配層
system/bluetooth/bluedroid/bluetooth.c
包含了對硬件操作的接口
system/bluetooth/data/*一些配置文件,復制到/etc/bluetooth/。
還有其他一些測試代碼和工具。
內核代碼主要由BlueZ核心協議和驅動程序組成;藍牙協議實現在內核源代碼net/bluetooth中,驅動程序位於內核源代碼目錄driver/bluetooth中。用戶態程序及工具集主要包括應用程序接口和BlueZ工具集,位於Android源代碼目錄externel/bluetooth(注:Android版本不一樣,有的在externel/bluez目錄下)中。
三 Bluetooth協議棧分析
3.1 藍牙協議棧
藍牙協議棧的體系結構由底層硬件模塊、中間協議層和高端應用層三部分組成。
(1)底層硬件模塊
組成:
鏈路管理協議(LinkManagerProtocol,LMP);
基帶(BaseBand,BB);
射頻(RadioFrequency,RF)。
功能:
射頻(RF)通過2.4GHz的ISM頻段實現數據流的過濾和傳輸。
基帶(BB)提供兩種不同的物理鏈路,即同步面向連接鏈路(SynchronousConnection Oriented,SCO)和異步無連接鏈路(AsynchronousConnectionLess,ACL),負責跳頻和藍牙數據,及信息幀的傳輸,且對所有類型的數據包提供不同層次的前向糾錯碼(FrequencyError Correction,FEC)或循環冗余度差錯校驗(CyclicRedundancyCheck,CRC)。
鏈路管理協議(LMP)負責兩個或多個設備鏈路的建立和拆除,及鏈路的安全和控制,如鑒權和加密、控制和協商基帶包的大小等,它為上層軟件模塊提供了不同的訪問入口。
主機控制器接口(HostControllerInterface,HCI)是藍牙協議中軟硬件之間的接口,提供了一個調用下層BB、LMP、狀態和控制寄存器等硬件的統一命令,上下兩個模塊接口之間的消息和數據的傳遞必須通過HCI的解釋才能進行。
(2)中間協議層
組成:
邏輯鏈路控制和適配協議(LogicalLinkControl and Adaptation Protocol,L2CAP);
服務發現協議(ServiceDiscoveryProtocol,SDP);
串口仿真協議(或稱線纜替換協議RFCOMM);
二進制電話控制協議(TelephonyControlprotocolSpectocol,TCS)。
功能:
L2CAP位於基帶(BB)之上,向上層提供面向連接的和無連接的數據服務,它主要完成數據的拆裝、服務質量控制、協議的復用、分組的分割和重組,及組提取等功能。
SDP是一個基於客戶/服務器結構的協議,它工作在L2CAP層之上,為上層應用程序提供一種機制來發現可用的服務及其屬性,服務的屬性包括服務的類型及該服務所需的機制或協議信息。
RFCOMM是一個仿真有線鏈路的無線數據仿真協議,符合ETSI標准的TS07.10串口仿真協議,它在藍牙基帶上仿真RS-232的控制和數據信號,為原先使用串行連接的上層業務提供傳送能力。
TCS定義了用於藍牙設備之間建立語音和數據呼叫的控制信令(CallControl Signalling),並負責處理藍牙設備組的移動管理過程。
(3)高端應用層
組成:
點對點協議(Point-to-PointProtocol,PPP);
傳輸控制協議/網絡層協議(TCP/IP);
用戶數據包協議(UserDatagramProtocol,UDP);
對象交換協議(ObjectExchangProtocol,OBEX);
無線應用協議(WirelessApplicationProtocol,WAP);
無線應用環境(WirelessApplicationEnvironment,WAE);
功能:
PPP定義了串行點對點鏈路應當如何傳輸因特網協議數據,主要用於LAN接入、撥號網絡及傳真等應用規范。
TCP/IP、UDP定義了因特網與網絡相關的通信及其他類型計算機設備和外圍設備之間的通信。
OBEX支持設備間的數據交換,采用客戶/服務器模式提供與HTTP(超文本傳輸協議)相同的基本功能。可用於交換的電子商務卡、個人日程表、消息和便條等格式。
WAP用於在數字蜂窩電話和其他小型無線設備上實現因特網業務,支持移動電話瀏覽網頁、收取電子郵件和其他基於因特網的協議。
WAE提供用於WAP電話和個人數字助理(PersonalDigitalAssistant,PDA)所需的各種應用軟件。
3.2 Android與藍牙協議棧的關系
藍牙系統的核心是BlueZ,因此JNI和上層都圍繞跟BlueZ的溝通進行。JNI和Android應用層,跟BlueZ溝通的主要手段是D-BUS,這是一套被廣泛采用的IPC通信機制,跟Android框架使用的Binder類似。BlueZ以D-BUS為基礎,給其他部分提供主要接口。
四 Bluetooth之HCI層分析
藍牙系統的HCI層是位於藍牙系統的L2CAP(邏輯鏈路控制與適配協議)層和LMP(鏈路管理協議)層之間的一層協議。HCI為上層協議提供了進入LM的統一接口和進入基帶的統一方式。在HCI的主機(Host)和HCI主機控制器(HostController)之間會存在若干傳輸層,這些傳輸層是透明的,只需完成傳輸數據的任務,不必清楚數據的具體格式。目前,藍牙的SIG規定了四種與硬件連接的物理總線方式:USB、RS232、UART和PC卡。其中通過RS232串口線方式進行連接具有差錯校驗。藍牙系統的協議模型如圖3所示。
圖3 Bluetooth協議模型
4.1 HCI層與基帶的通信方式
HCI是通過包的方式來傳送數據、命令和事件的,所有在主機和主機控制器之間的通信都以包的形式進行。包括每個命令的返回參數都通過特定的事件包來傳輸。HCI有數據、命令和事件三種包,其中數據包是雙向的,命令包只能從主機發往主機控制器,而事件包始終是主機控制器發向主機的。主機發出的大多數命令包都會觸發主機控制器產生相應的事件包作為響應。
圖4底層協議通信圖
命令包分為六種類型:
*鏈路控制命令;
*鏈路政策和模式命令;
*主機控制和基帶命令;
*信息命令;
*狀態命令;
*測試命令。
事件包也可分為三種類型:
*通用事件,包括命令完成包(CommandComplete)和命令狀態包(CommandStatus);
*測試事件;
*出錯時發生的事件,如產生丟失(FlushOccured)和數據緩沖區溢出(DataBuffer Overflow)。
數據包則可分為ACL和SCO的數據包。包的格式如圖5所示。
圖5HCI包格式
4.2 包的分析及研究
命令包:命令包中的OCF(OpcodeCommand Field)和OGF(OpcodeGroup Field)是用於區分命令種的。ParameterLength表示所帶參數的長度,以字節數為單位,隨后就是所帶的參數列表。下面以Inquiry命令為例對HCI的命令包做具體說明。
在Inquiry命令中,OGF=0x01表示此命令屬於鏈路控制命令,同時OCF=0x0001則表示此命令為鏈路控制命令中的Inquiry命令。OCF與OGF共占2字節,又由於底位字節在前,則它們在命令包為0x0104。在Inquiry命令中,參數ParameterLength為5。Inquiry命令帶3個參數,第一個參數為LAP(lowaddress part), 它將用來產生Baseband中查詢命令包的包頭中的AccessCode。第二個參數為Inquiry_Length,它時表示在Inquiry命令停止前所定義的最大時間,超過此時間,Inquiry命令將終止。第三個參數為NUM_Response,它的值為0X00表示設備響應數不受限制,只為0x00-0xff則表示在Inquiry命令終止前最大的設備響應數。因此,若LAP=0x9e8b00,Inquiry_Length=0x05,NUM_Response=0x05,則協議上層調用Inquiry命令是HCI向基帶發的明令包將為:0x0104 05 00 8b 9e 05 05。
事件包:事件包的EventCode用來區分不同的事件包,ParameterLength表示所帶參數的長度,以字節數為單位,隨后就是所帶的參數列表。以CommandStatus Event事件包為例對HCI的事件包進行具體說明。
當主機控制器收到主機發來的如上面所提到的Inquiry命令包並開始處理時,它就會向主機發送CommandStatus Event事件包,此事件包為:0x0f04 00 0a 01 04。0xOf表示此事件包為CommandStatusEvent事件包,0x04表示此事件包帶4字節長度的參數,0x00為此事件包的第一個參數即Status,表示命令包正在處理。0x0a為事件包的第二個參數NUM_HCI_Command_Packets,表示主機最多可在向主機控制器發10個命令包。0x0104 為第三個參數Command_Opcode,表示此事件包是對Inquiry命令包的響應。
數據包:ACL和SCO數據包中的ConnectionHandle即連接句柄是一個12比特的標志符,用於唯一確認兩台藍牙設備間的數據或語音連接,可以看作是兩台藍牙設備間唯一的數據通道的標識。兩台設備間只能有一條ACL連接,也就是只有一個ACL的連接句柄,相應L2CAP的信道都是建立在這個連接句柄表示的數據通道上;兩台設備間可以有多個SCO的連接,則一對設備間會有多個SCO的連接句柄。連接句柄在兩設備連接期間一直存在,不管設備處於什么狀態。在ACL數據包中,Flags分為PBFlag和BCFlag,PBFlag為包的界限標志,PBFlag=0x00表示此數據包為上層協議包(如L2CAP包)的起始部分;PBFlag=0x01表示此數據包為上層協議包(如L2CAP包)的后續部分。BCFlag為廣播發送的標志,BCFlag=0x00表示無廣播發送,只是點對點的發送;BCFlag=0x01表示對所有處於激活狀態的從設備進行廣播發送,BCFlag=0x02表示對所有的從設備包括處於休眠狀態的從設備進行廣播發送。ACL和SCO數據包中的DataTotal Length 都表示所載荷的數據的長度,以字節位單位。
4.3 通信過程的研究與分析
當主機與基帶之間用命令的方式進行通信時,主機向主機控制器發送命令包。主機控制器完成一個命令,大多數情況下,它會向主機發出一個命令完成事件包(CommandComplete Packet),包中攜帶命令完成的信息。有些命令不會收到命令完成事件,而會收到命令狀態事件包(CommandStatusPacket),當收到該事件則表示主機發出的命令已經被主機控制器接收並開始處理,過一段時間該命令被執行完畢時,主機控制器會向主機發出相應的事件包來通知主機。如果命令參數有誤,則會在命令狀態事件中給出相應錯誤碼。假如錯誤出現在一個返回CommandComplete事件包的命令中,則此CommandComplete事件包不一定含有此命令所定義的所有參數。狀態參數作為解釋錯誤原因同時也是第一個返回的參數,總是要返回的。假如緊隨狀態參數之后是連接句柄或藍牙的設備地址,則此參數也總是要返回,這樣可判別出此CommandComplete事件包屬於那個實例的一個命令。在這種情況下,事件包中連接句柄或藍牙的設備地址應與命令包種的相應參數一致。假如錯誤出現在一個不返回CommandComplete事件包的命令中,則事件包包含的所有參數都不一定是有效的。主機必須根據於此命令相聯系的事件包中的狀態參數來決定它們的有效性。
五 Bluetooth之編程實現
5.1 HCI層編程
HostController Interface(HCI)是用來溝通Host和Module。Host通常就是PC,Module則是以各種物理連接形式(USB,serial,pc-card等)連接到PC上的bluetoothDongle。
在Host這一端:application,SDP,L2cap等協議都是軟件形式提出的(Bluez中是以kernel層程序)。在Module這一端:LinkManager, BB, 等協議都是硬件中firmware提供的。
而HCI則比較特殊,它一部分在軟件中實現,用來給上層協議和程序提供訪問接口(Bluez中,hci.chci_usb.c,hci_sock.c等).另一部分也是在Firmware中實現,用來將軟件部分的指令等用底層協議明白的方式傳遞給底層。
居於PC的上層程序與協議和居於Modules的下層協議之間通過HCI溝通,有4種不同形式的傳輸:Commands,Event, ACL Data, SCO/eSCO Data。
-
打開一個HCISocket---int hci_open_dev(int dev_id):
這個function用來打開一個HCISocket。它首先打開一個HCIprotocol的Socket(房間),並將此Socket與deviceID=參數dev_id的Dongle綁定起來。只有bind后,它才將Socket句柄與Dongle對應起來。
注意,所有的HCICommand發送之前,都需要使用hci_open_dev打開並綁定。
-
關閉一個HCISocket--- int hci_close_dev(int dd)
簡單的關閉使用hci_open_dev打開的Socket。
-
向HCISocket發送 request---inthci_send_req(int dd, struct hci_request *r, int to)
BlueZ提供這個function非常有用,它可以實現一切Host向Modules發送Command的功能。
參數1:HCISocket。
參數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不用多說,對應前面的圖就明白這是GroupCode和CommandCode。這兩項
先確定下來,然后可以查HCISpec。察看輸入參數(cparam)以及輸出參數(rparam)
含義。至於他們的結構以及參數長度,則在~/include/net/bluetooth/hci.h中有定義。
至於event.如果設置,它會被setsockopt設置於Socket。
-
得到指定DongleBDAddr---int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to);
參數1:HCISocket,使用hci_open_dev()打開的Socket(Dongle)。
參數2:輸出參數,其中會放置bdaddr.
參數3:以milliseconds為單位的timeout.
-
讀寫DongleName:
inthci_read_local_name(int dd, int len, char *name, int to)
inthci_write_local_name(int dd, const char *name, int to)
參數1:HCISocket,使用hci_open_dev()打開的Socket(Dongle)。
參數2:讀取或設置Name。
參數3:以milliseconds為單位的timeout.
注意:這里的Name與IOCTLHCIGETDEVINFO 得到hci_dev_info中的name不同。
-
得到HCIVersion:
inthci_read_local_version(int dd, struct hci_version *ver, int to)
-
得到已經UP的DongleBDaddr--- int hci_devba(int dev_id, bdaddr_t *bdaddr);
dev_id:Dongle Device ID.
bdaddr:輸出參數,指定Dongle如果UP,則放置其BDAddr。
-
inquiry遠程BluetoothDevice:
inthci_inquiry(int dev_id, int len, int nrsp, const uint8_t *lap,inquiry_info **ii, long flags)
hci_inquiry()用來命令指定的Dongle去搜索周圍所有bluetoothdevice.並將搜索到的BluetoothDevice bdaddr 傳遞回來。
參數1:dev_id:指定DongleDevice ID。如果此值小於0,則會使用第一個可用的Dongle。參數2:len:此次inquiry的時間長度(每增加1,則增加1.25秒時間)
參數3:nrsp:此次搜索最大搜索數量,如果給0。則此值會取255。
參數4:lap:BDADDR中LAP部分,Inquiry時這塊值缺省為0X9E8B33.通常使用NULL。則自動設置。
參數5:ii:存放搜索到BluetoothDevice的地方。給一個存放inquiry_info指針的地址,它會自動分配空間。並把那個空間頭地址放到其中。
參數6:flags:搜索flags.使用IREQ_CACHE_FLUSH,則會真正重新inquiry。否則可能會傳回上次的結果。
返回值是這次Inquiry到的BluetoothDevice 數目。
注意:如果*ii不是自己分配的,而是讓hci_inquiry()自己分配的,則需要調用bt_free()來幫它釋放空間。
-
得到指定BDAddr的reomtedevice Name:
inthci_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:等待時間。
-
讀取連接的信號強度:
inthci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to)
注意,所有對連接的操作,都會有一個參數,handle.這個參數是連接的Handle。前面講過如何得到連接Handle的。
5.2 L2CAP層編程
邏輯連接控制和適配協議(L2CAP)為上層協議提供面向連接和無連接的數據服務,並提供多協議功能和分割重組操作。L2CAP充許上層協議和應用軟件傳輸和接收最大長度為64K 的L2CAP 數據包。
L2CAP基於 通道(channel)的概念。 通道 (Channel)是位於基帶 (baseband)連接之上的邏輯連接。每個通道以多對一的方式綁定一個單一協議(singleprotocol)。多個通道可以綁定同一個協議,但一個通道不可以綁定多個協議。每個在通道里接收到的 L2CAP數據包被傳到相應的上層協議。多個通道可共享同一個基帶連接。
L2CAP處於Bluetooth協議棧的位置如下:
圖6L2CAP協議
L2CAP使用L2CAP連接請求(ConnectionRequest )命令中的PSM字段實現協議復用。L2CAP可以復用發給上層協議的連接請求,這些上層協議包括服務發現協議SDP(PSM= 0x0001)、RFCOMM(PSM= 0x0003)和電話控制(PSM= 0x0005)等。
圖7PSM協議字段
L2CAP編程非常重要,它和HCI基本就是LinuxBluetooth編程的基礎了。幾乎所有協議的連接,斷連,讀寫都是用L2CAP連接來做的
-
創建L2CAPSocket:
socket(PF_BLUETOOTH,SOCK_RAW, BTPROTO_L2CAP);
domain=PF_BLUETOOTH,type可以是多種類型。protocol=BTPROTO_L2CAP
-
綁定
memset(&addr,0, sizeof(addr));
addr.l2_family= AF_BLUETOOTH;
bacpy(&addr.l2_bdaddr,&bdaddr); //bdaddr為本地DongleBDAddr
if(bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0)
{
perror("Can'tbind socket");
gotoerror;
}
-
連接
memset(&addr,0, sizeof(addr));
addr.l2_family= AF_BLUETOOTH;
bacpy(addr.l2_bdaddr,src);
addr.l2_psm= xxx;
if(connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0)
{
perror("Can'tconnect");
gotoerror;
}
注意:
structsockaddr_l2
{
sa_family_tl2_family; //必須為 AF_BLUETOOTH
unsignedshort l2_psm; //與前面PSM對應,這一項很重要
bdaddr_tl2_bdaddr; //Remote Device BDADDR
unsignedshort l2_cid;
};
-
發送數據到RemoteDevice:
send()或write()都可以。
-
接收數據
revc()或read()都可以
5.3 SDP層編程
服務發現協議(SDP或BluetoothSDP)在藍牙協議棧中對藍牙環境中的應用程序有特殊的含意,發現哪個服務是可用的和確定這些可用服務的特征。SDP定義了bluetoothclient發現可用bluetoothserver服務和它們的特征的方法。這個協議定義了客戶如何能夠尋找基於特定屬性的服務而不讓客戶知道可用服務的任何知識。SDP提供發現新服務的方法,在當客戶登錄到正在操作的藍牙服務器的一個區域時是可用的時。
Servicediscovery機制提供client應用程序偵測server應用程序提供的服務的能力,並且能夠得到服務的特性。服務的品質包含服務type或服務class.
SDP也提供SDPserver與SDPclient之間的通訊。SDPserver維護着一個服務條目(servicerecord)列表.每個服務條目描述一個單獨的服務屬性。SDPclient可以通過發送SDPrequest來得到服務條目。
如果一個client或者依附於client之上的應用程序決定使用某個service.它創建一個單獨的連接到service提供者。SDP只提供偵測Service的機制,但不提供如何利用這些Service的機制。Sam覺得,這里其實是說:SDP只提供偵測Service的辦法,但如何用,SDP不管。
每個BluetoothDevice最多只能擁有一個SDPServer。如果一個BluetoothDevice只擔任Client,那它不需要SDPServer。但一個BluetoothDevice可以同時擔當SDPServer和SDPclient.
(1)ServiceRecord(Service 條目):
一個service是一個實體為另一個實體提供信息,執行動作或控制資源。一個service可以由軟件,硬件或軟硬件結合提供。
圖8Service Record
所有的Service信息都包含於一個ServiceRecord內。一個ServiceRecord 包含一個Serviceattribute(Service屬性)list.
(2)ServiceAttribute(Service 屬性):
每個Service屬性描述servcie的特性.一個ServiceAttribute由2部分:
AttributeID + Attribute Value。
圖9Service Attribute
(3)ServiceClass:
每個Service都是某個ServiceClass的實例.Service Class定義了ServiceRecord中包含的Service屬性。屬性ID,屬性值都被定義好了。
每個ServiceClass也有一個獨特ID。這個ServiceClass標識符包含在屬性值ServiceClassIDList屬性中。並描繪為UUID。自從ServiceRecord中的屬性格式以及含義依賴於ServiceClass后,ServiceClassIDList屬性變得非常重要。
(4)SearchingFor Service:
ServiceSearch transaction(事務?)允許client得到ServiceRecord Handle。一旦SDPClient得到ServiceRecord Handle,它就可以請求這個Record內具體屬性的值。
如果某個屬性值UUID,則可以通過查找UUID查到這個屬性。
UUID:universally uniqueidentifier.(唯一性標識符)
總之,DP協議棧使用request/response模式工作,每個傳輸過程包括一個requestprotocol data unit(PDU)和一個responsePDU. SDP使用L2CAP連接傳輸數據。在發送RequestPDU但未收到ResponsePDU之前,不能向同一個server再發送RequestPDU。
六 Bluetooth之啟動過程實現
對於藍牙無論最底層的硬件驅動如何實現,都會在HCI層進行統一。也就是說,HCI在主機端的驅動主要是為上層提供統一接口,讓上層協議不依賴於具體的硬件實現。HCI在硬件中的固件與HCI在主機端的驅動通信方式有多種,比如UART,USB和SDIO等。
HCI層在所有的設備面前都被抽象為一個hci_dev結構體,因此,無論實際的設備是哪種藍牙設備、通過什么方式連接到主機,都需要向HCI層和藍牙核心層注冊一個hci_dev設備,注冊過程由hci_registe_dev()函數來完成,同時也可以通過hci_unregister_dev()函數卸載一個藍牙設備。
具體的藍牙驅動有很多,常用的在linux內核都自帶有驅動。比如:hci_vhci.c為藍牙虛擬主控制器驅動程序,hci_uart.c(或者hci_ldisc.c)為串口接口主控制器驅動程序,btusb.c為USB接口主控制器驅動程序,btsdio.c為SDIO主控制器驅動程序。
6.1 Bluetooth啟動步驟
(1)串口驅動必須要先就緒(uart藍牙而言),這是cpu和藍牙模塊之間的橋梁。
(2)藍牙初始化,模塊上電和PSKEY的設置。
(3)通過hciattach建立串口和藍牙協議層之間的數據連接通道。
6.2 Bluetooth啟動流程
(1)打開藍牙電源,通過rfkill來enable;(system/bluetooth/bluedroid/bluetooth.c)
(2)啟動servicehciattch -n -s 115200 /dev/ttyS2 bcm2035 115200;
(3)檢測HCI是否成功(接受HCIDEVUPsocket來判斷或hciconfighci0 up);
(4)hcid deamon start up。
6.3 Bluetooth數據流向
(1)uart口取得藍牙模塊的數據;
(2)uart口通過ldisc傳給hci_uart;
(3)hci_uart傳給在其上的bcsp;
(4)bcsp傳給hci層;
(5)hci層傳給l2cap層
(6)l2cap層再傳給rfcomm;
藍牙模塊上電:
一般是通過一個GPIO來控制的,通常是先高再低再高;
PSKEY的設置:
通過串口發送命令給藍牙模塊,對於串口必須要知道的是要能通訊,必須得設好波特率,另外一方面藍牙模塊的晶振頻率也必須要設,否則它不知道該怎么跳了;當然不同的芯片可能初始化的過程也不一樣,也許還要下載firmware等等,一般是通過bccmd來完成的。
經過上面的設置基本上藍牙模塊以及可以正常工作了;
但是還沒有和上面的協議層建立紐帶關系,也就是說從uart收到的數據還沒有傳給hci層;如何把uart也就是藍牙模塊傳上來的數據交給hci層,在驅動里面是通過一個叫做disc的機制完成的,這個機制本意是用來做過濾或者限制收上來的字符的,但是在藍牙驅動里面則直接把數據傳給了藍牙協議層,再也不回到串口的控制了;
6.4 Bluez控制流程
classbluetoothsetting是UI的入口,通過按buttonscan進入搜索狀態,
applicaton層調用bluetoothdevice,接着就是bluetoothservice的調用,
bluetoothservice調用native方法,到此全部的java程序結束了。
下面的調用都是JNI,cpp實現的。android_server_bluetoothservice.cpp里面實現了native
方法,最終通過dbus封裝,調用HCID deamon 的functionDiscoverDevice。
6.5 Bluetooth啟動過程分析
1.各協議層的注冊
(1)在af_bluetooth.c中首先調用bt_init()函數完成初始化,打印信息Bluetooth:Core ver 2.16隨后調用函數sock_register()注冊sock,打印信息Bluetooth:HCI device and connection manager initialized
(2)接着調用函數hci_sock_init(),l2cap_init(),sco_init()實現各個協議的初始化
(3)在hci_sock.c中,注冊bt_sock協議,打印信息Bluetooth:HCI socket layer initialized
(4)在L2cap_core.c中,調用l2cap_init_socks()函數,完成初始化,打印信息Bluetooth:L2CAP socket layer initialized
(5)在sco.c中,注冊BTPROTO_SCO協議,完成初始化,打印信息Bluetooth:SCO socket layer initialized
2.各硬件的初始化
(1)在Hci_ldisc.c中,完成hci_uart_init的初始化,打印信息Bluetooth:HCI UART driver ver 2.2
(2)在Hci_h4.c中,完成h4_init的初始化,打印信息Bluetooth:HCI H4 protocol initialized
(3)在Hci_ath.c中,完成ath_Init的初始化,打印信息Bluetooth:HCIATH3Kprotocol initialized
(4)在hci_ibs.c中,完成ibs_init的初始化,打印信息Bluetooth:HCI_IBSprotocol initialized
(5)在Core.c中,完成rfcomm_init的初始化,其中調用函數rfcomm_init_ttys()、rfcomm_init_sockets(),實現rfcomm的初始化,打印信息Bluetooth:RFCOMMver 1.11
(6)在TTY.C中,完成rfcomm_init_ttys的初始化,實現rfcomm_tty_driver的注冊,打印信息Bluetooth:RFCOMMTTY layer initialized
(7)在Sock.c中,完成rfcomm_init_sockets的初始化,實現BTPROTO_RFCOMM的注冊,打印信息Bluetooth:RFCOMMsocket layer initialized
(以上通過rfcomm完成uart串口的初始化,在G:\M8960\M8960AAAAANLYA1023\kernel\net\bluetooth\rfcomm)
3. bnep-藍牙網絡封裝協議
在Core.c中,完成bnep_init的初始化,實現bnep的初始化,打印信息Bluetooth:BNEP(Ethernet Emulation) ver 1.3/Bluetooth:BNEP filters: protocol
(以上完成藍牙網絡封裝協議的初始化G:\M8960\M8960AAAAANLYA1023\kernel\net\bluetooth\bnep)
七 Bluetooth之驅動移植
7.1 android系統配置
build\target\board\generic下面的generic.mk增加:
BOARD_HAVE_BLUETOOTH:= true
這個是由於編譯相關藍牙代碼時需要這個宏,請看:\system\bluetooth\android.mk
ifeq($(BOARD_HAVE_BLUETOOTH),true)
include$(all-subdir-makefiles)
endif
在 external\bluetooth也同樣存在此宏起作用
7.2 啟動項修改
system\core\rootdir下init.rc文件增加:
servicehciattach /system/bin/hciattach -n -s 115200 /dev/ttyS2 bcm2035115200
userbluetooth
groupbluetooth net_bt_admin
disabled
oneshot
請放在 servicebluetoothd /system/bin/bluetoothd -n 類似這種語句的后面任意位置即可。
7.3 電源管理rfkill驅動
Kernel/driver/bluetooth/bluetooth-power.c
高通的這個文件基本上不用動。
在kernel\arch\arm\mach_msm7x27.c:static int bluetooth_power(int on)中
實現:上電:把bt_resetpin 和bt_reg_onpin 拉低
mdelay(10);
把bt_resetpin和bt_reg_onpin 拉高
mdelay(150)
下電:把bt_resetpin 和bt_reg_onpin 拉低
7.4 Rebuild Androidimage and reboot
命令行測試:
echo0 >/sys/class/rfkill/rfkill0/state //BT下電
echo1 >/sys/class/rfkill/rfkill0/state //BT上電
brcm_patchram_plus-d--patchram/etc/firmware/BCM4329B1_002.002.023.0061.0062.hcd/dev/ttyHS0
hciattach-s115200 /dev/ttyHS0 any
沒任何錯誤提示是可以用以下測試
hciconfighci0up
hcitoolscan
7.5 實現BT睡眠喚醒機制
Kernel\drivers\bluetooth\bluesleep.c一般來說這個文件改動比較少,但可能邏輯上會有些問題。需要小的改動。
在kernel\arch\arm\mach_xxx/board_xxx.c:bluesleep_resources中定義gpio_host_wake(BT喚醒host腳)、gpio_ext_wake(host喚醒BT腳)、host_wake(BT喚醒host的中斷號)。
注:各個平台的board_xxx.c文件名字不同,請客戶確認
7.6 系統集成
1)在init.qcom.rc中確認有下面的內容:
servicehciattach/system/bin/sh /system/etc/init.qcom.bt.sh
userbluetooth
groupqcom_oncrpc bluetooth net_bt_admin
disabled
oneshot
2)修改init.qcom.bt.sh
確認有:
BLUETOOTH_SLEEP_PATH=/proc/bluetooth/sleep/proto
echo1 >$BLUETOOTH_SLEEP_PATH
/system/bin/hciattach-n/dev/ttyHS0 any 3000000 flow & 改為:
./brcm_patchram_plus--enable_lpm–enable_hci --patchram /system/etc/wifi/BCM4329BT.hcd--baudrate3000000 /dev/ttyHS0 &
注掉:高通下載firmware的命令。
最后,重新編譯system。此時BT應該能運行了。
八 Bluetooth之調試與編譯
8.1 Bluetooth驅動調試
調試你的藍牙實現,可以通過讀跟藍牙相關的logs(adblogcat)和查找ERROR和警告消息。Android使用Bluez,同時會帶來一些有用的調式工具。下面的片段為了提供一個建議的例子:
1 hciconfig -a # print BT chipset address and features.Useful to check if you can communicate with your BT chipset.
2 hcidump -XVt # print live HCI UART traffic.
3 hcitool scan # scan for local devices. Useful to check ifRX/TX works.
4 l2ping ADDRESS # ping another BT device. Useful to check ifRX/TX works.
5 sdptool records ADDRESS # request the SDP records of another BTdevice.
守護進程日志
hcid(STDOUT)和hciattach(STDERR)的守護進程日志缺省是被寫到/dev/null。編輯init.rc和init.PLATFORM.rc在logwrapper下運行這些守護進程,把它們輸出到logcat。
hciconfig-a 和 hcitool
如果你編譯你自己的system.img,除了hcitool掃描不行,hciconfig-a是可以工作的,嘗試安裝固件到藍牙芯片。
8.2 Bluetooth 調試工具
BlueZ為調試和與藍牙子系統通信提供很多設置命令行工具,包含下面這些:
(1)hciconfig
(2)hcitool
(3)hcidump
(4)sdptool
(5)dbus-send
-
dbus-monitor
九 Bluetooth之應用程序開發
9.1 Bluetooth的API開發
Android平台包含了對Bluetooth協議棧的支持,允許機器通過Bluetooth設備進行無線數據交換。應用框架通過AndroidBluetoothAPI訪問Bluetooth功能模塊。這些API能讓應用無線連接其他Bluetooth設備,實現點對點和多點之間的通信。
運用藍牙API,Android應用程序可以完成如下操作:
(1)掃描其他Bluetooth設備。
(2)查詢配對Bluetooth設備的本地Bluetooth適配器。
(3)建立RFCOMM通道。
(4)通過服務探索連接到其他設備。
(5)與其他設備進行數據傳輸。
(6)管理多個連接
9.2 The Basics開發
本文描述如何使用AndroidBluetoothAPIs完成Bluetooth通訊的4個必要任務:設置Bluetooth,搜尋本地配對或者可用的Bluetooth設備,連接Bluetooth設備,與Bluetooth設備進行數據傳輸。
所有可用的BluetoothAPIs都包含在android.bluetooth包中。下面是建立Bluetooth連接需要用到的類和接口的總結:
(1)BluetoothAdapter
描述本地Bluetooth適配器(Bluetooth接收器)。BluetoothAdapter是所有Bluetooth相關活動的入口。運用BluetoothAdapter可以發現其他Bluetooth設備,查詢連接(或配對)的設備列表,用已知MAC地址實例化一個BluetoothDevice對象,創建一個BluetoothServerSocket對象偵聽其他設備的通信。
(2)BluetoothDevice
描述一個遠程Bluetooth設備。可以用它通過一個BluetoothSocket請求一個遠程設備的連接,或者查詢遠程設備的名稱、地址、類、連接狀態等信息。
(3)BluetoothSocket
描述一個BluetoothSocket接口(類似於TCPSocket)。應用通過InputStream和OutputStream與另外一個Bluetooth設備交換數據,即它是應用與另外一個設備交換數據的連接點。
(4)BluetoothServerSocket
BluetoothServerSocket是一個開放的socket服務器,用來偵聽連接進來的請求(類似於RCPServerSocket)。為了連接兩個Android設備,一個設備必須使用該類來開啟一個socket做服務器,當另外一個設備對它發起連接請求時並且請求被接受時,BluetoothServerSocket會返回一個連接的BluetoothSocket對象。
(5)BluetoothClass
BluetoothClass是用來定義設備類和它的服務的只讀屬性集。然而,它並不是可靠的描述設備支持的所有Bluetooth配置和服務,而只是一些設備類型的有用特征。
(6)BluetoothProfile
BluetoothProfile是兩個設備基於藍牙通訊的無線接口描述。Profile定義了設備如何實現一種連接或者應用,你可以把Profile理解為連接層或者應用層協議。比如,如果一家公司希望它們的Bluetooth芯片支援所有的Bluetooth耳機,那么它只要支持HeadSetProfile即可,而無須考慮該芯片與其它Bluetooth設備的通訊與兼容性問題。如果你想購買Bluetooth產品,你應該了解你的應用需要哪些Profile來完成,並且確保你購買的Bluetooth產品支持這些Profile。
(7)BluetoothHeadset
提供移動電話的Bluetooth耳機支持。包括Bluetooth耳機和Hands-Free(v1.5) profiles。
(8)BluetoothA2dp
定義兩個設備間如何通過Bluetooth連接進行高質量的音頻傳輸。A2DP(AdvancedAudio Distribution Profile):高級音頻傳輸模式。
(9)BluetoothProfile.ServiceListener
一個接口描述,在與服務連接或者斷連接的時候通知BluetoothProfileIPC(這是內部服務運行的一個特定的模式<profile>)。
9.3 BluetoothPermissions開發
要使用Bluetooth功能,至少需要2個Bluetooth權限:BLUETOOTH和BLUETOOTH_ADMIN.
BLUETOOTH:用來授權任何Bluetooth通信,如請求連接,接受連接,傳輸數據等。
BLUETOOTH_ADMIN:用來授權初始化設備搜索或操作Bluetooth設置。大多數應用需要它的唯一場合是用來搜索本地Bluetooth設備。本授權的其他功能不應該被使用,除非是需要修改Bluetooth設置的“powermanager(電源管理)”應用。
注意:需要BLUETOOTH_ADMIN權限的場合,BLUETOOTH權限也是必需的。需要在manifest文件中聲明Bluetooth權限,示例如下:
<manifest... >
<uses-permissionandroid:name="android.permission.BLUETOOTH" />
...
</manifest>
9.4 Setting UpBluetooth服務
在用Bluetooth通訊之前,需要確認設備是否支持Bluetooth,如果支持,還得確保Bluetooth是可用的。
如果設備不支持Bluetooth,需要優雅的將Bluetooth置為不可用。如果支持Bluetooth,但沒有開啟,可以在應用中請求開啟Bluetooth。該設置使用BluetoothAdapter.通過兩個步驟完成。
(1)獲取BluetoothAdapter
BluetoothAdapter是每個Bluetooth的Activity都需要用到的。用靜態方法getDefaultAdapter()獲取BluetoothAdapter,返回一個擁有Bluetooth適配器的BluetoothAdapter對象。如果返回null,說明設備不支持Bluetooth,關於Bluetooth的故事到此就結束了(因為你干不了什么了)。示例:
BluetoothAdaptermBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(mBluetoothAdapter == null) {
//Device does not support Bluetooth
}
(2)EnableBluetooth
接下來,就是確保Bluetooth功能是開啟的。調用isEnabled()來檢查Bluetooth當前是否是開啟的。用ACTION_REQUEST_ENABLEactionIntent調用startActivityForResult()來請求開啟Bluetooth,這會通過系統設置發出一個Bluetooth使能請求(並且不會停止本應用程序)。示例:
if(!mBluetoothAdapter.isEnabled()) {
IntentenableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent,REQUEST_ENABLE_BT);
}
用戶請求使能Bluetooth時,會顯示一個對話框。選擇“Yes”,系統會使能Bluetooth,並且焦點會返回你的應用程序。
如果使能Bluetooth成功,你的Activity會在onActivityResult()回調函數中收到RESULT_OK的結果碼。如果Bluetooth使能因發生錯誤(或用戶選擇了“No”)而失敗,收到的結果碼將是RESULT_CANCELED。
作為可選項,應用也可以偵聽ACTION_STATE_CHANGEDbroadcastIntent,這樣無論Bluetooth狀態何時被改變系統都會發出broadcast(廣播)。該廣播包含附加的字段信息EXTRA_STATE和EXTRA_PREVIOUS_STATE分別代表新的和舊的Bluetooth狀態,該字段可能的值為STATE_TURNING_ON,STATE_ON, STATE_TURNING_OFF,和STATE_OFF。應用運行時,偵聽ACTION_STATE_CHANGED廣播來檢測Bluetooth狀態的改變是很有用的。
提示:啟用Bluetooth可被發現功能能夠自動開啟Bluetooth。如果在完成Activity之前需要持續的使能Bluetooth可被發現功能,那么上面的第2步就可以忽略。
9.5 Finding Devices服務
使用BluetoothAdapter可以通過設備搜索或查詢配對設備找到遠程Bluetooth設備。
Devicediscovery(設備搜索)是一個掃描搜索本地已使能Bluetooth設備並且從搜索到的設備請求一些信息的過程(有時候會收到類似“discovering”,“inquiring”或“scanning”)。但是,搜索到的本地Bluetooth設備只有在打開被發現功能后才會響應一個discovery請求,響應的信息包括設備名,類,唯一的MAC地址。發起搜尋的設備可以使用這些信息來初始化跟被發現的設備的連接。
一旦與遠程設備的第一次連接被建立,一個pairing請求就會自動提交給用戶。如果設備已配對,配對設備的基本信息(名稱,類,MAC地址)就被保存下來了,能夠使用BluetoothAPI來讀取這些信息。使用已知的遠程設備的MAC地址,連接可以在任何時候初始化而不必先完成搜索(當然這是假設遠程設備是在可連接的空間范圍內)。
需要記住,配對和連接是兩個不同的概念:
配對意思是兩個設備相互意識到對方的存在,共享一個用來鑒別身份的鏈路鍵(link-key),能夠與對方建立一個加密的連接。連接意思是兩個設備現在共享一個RFCOMM信道,能夠相互傳輸數據。
目前AndroidBluetooth API's要求設備在建立RFCOMM信道前必須配對(配對是在使用BluetoothAPI初始化一個加密連接時自動完成的)。
下面描述如何查詢已配對設備,搜索新設備。
注意:Android的電源設備默認是不能被發現的。用戶可以通過系統設置讓它在有限的時間內可以被發現,或者可以在應用程序中要求用戶使能被發現功能。
(1)Queryingpaired devices
在搜索設備前,查詢配對設備看需要的設備是否已經是已經存在是很值得的,可以調用getBondedDevices()來做到,該函數會返回一個描述配對設備BluetoothDevice的結果集。例如,可以使用ArrayAdapter查詢所有配對設備然后顯示所有設備名給用戶:
Set<BluetoothDevice>pairedDevices = mBluetoothAdapter.getBondedDevices();
//If there are paired devices
if(pairedDevices.size() > 0) {
//Loop through paired devices
for(BluetoothDevice device : pairedDevices) {
//Add the name and address to an array adapter to show in a ListView
mArrayAdapter.add(device.getName()+ "\n" + device.getAddress());
}
}
BluetoothDevice對象中需要用來初始化一個連接唯一需要用到的信息就是MAC地址。
(2)Discoveringdevices
要開始搜索設備,只需簡單的調用startDiscovery()。該函數時異步的,調用后立即返回,返回值表示搜索是否成功開始。搜索處理通常包括一個12秒鍾的查詢掃描,然后跟隨一個頁面顯示搜索到設備Bluetooth名稱。
應用中可以注冊一個帶CTION_FOUNDIntent的BroadcastReceiver,搜索到每一個設備時都接收到消息。對於每一個設備,系統都會廣播ACTION_FOUNDIntent,該Intent攜帶着而外的字段信息EXTRA_DEVICE和EXTRA_CLASS,分別包含一個BluetoothDevice和一個BluetoothClass。下面的示例顯示如何注冊和處理設備被發現后發出的廣播:
//Create a BroadcastReceiver for ACTION_FOUND
privatefinal BroadcastReceiver mReceiver = new BroadcastReceiver() {
publicvoid onReceive(Context context, Intent intent) {
Stringaction = intent.getAction();
//When discovery finds a device
if(BluetoothDevice.ACTION_FOUND.equals(action)) {
//Get the BluetoothDevice object from the Intent
BluetoothDevicedevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//Add the name and address to an array adapter to show in a ListView
mArrayAdapter.add(device.getName()+ "\n" + device.getAddress());
}
}
};
//Register the BroadcastReceiver
IntentFilterfilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver,filter); // Don't forget to unregister during onDestroy
警告:完成設備搜索對於Bluetooth適配器來說是一個重量級的處理,要消耗大量它的資源。一旦你已經找到一個設備來連接,請確保你在嘗試連接前使用了cancelDiscovery()來停止搜索。同樣,如果已經保持了一個連接的時候,同時執行搜索設備將會顯著的降低連接的帶寬,所以在連接的時候不應該執行搜索發現。
(3)Enablingdiscoverability
如果想讓本地設備被其他設備發現,可以帶ACTION_REQUEST_DISCOVERABLEaction Intent調用startActivityForResult(Intent,int)方法。該方法會提交一個請求通過系統剛設置使設備出於可以被發現的模式(而不影響應用程序)。默認情況下,設備在120秒后變為可以被發現的。可以通過額外增加EXTRA_DISCOVERABLE_DURATIONIntent自定義一個值,最大值是3600秒,0表示設備總是可以被發現的(小於0或者大於3600則會被自動設置為120秒)。下面示例設置時間為300:
IntentdiscoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,300);
startActivity(discoverableIntent);
詢問用戶是否允許打開設備可以被發現功能時會顯示一個對話框。如果用戶選擇“Yes”,設備會在指定時間過后變為可以被發現的。Activity的onActivityResult()回調函數被調用,結果碼等於設備變為可以被發現所需時長。如果用戶選擇“No”或者有錯誤發生,結果碼會是Activity.RESULT_CANCELLED。
提示:如果Bluetooth沒有啟用,啟用Bluetooth可被發現功能能夠自動開啟Bluetooth。
在規定的時間內,設備會靜靜的保持可以被發現模式。如果想在可以被發現模式被更改時受到通知,可以用ACTION_SCAN_MODE_CHANGEDIntent注冊一個BroadcastReceiver,包含額外的字段信息EXTRA_SCAN_MODE和EXTRA_PREVIOUS_SCAN_MODE分別表示新舊掃描模式,其可能的值為SCAN_MODE_CONNECTABLE_DISCOVERABLE(discoverablemode),SCAN_MODE_CONNECTABLE(notin discoverable mode but still able to receiveconnections),SCAN_MODE_NONE(notin discoverable mode and unable to receive connections)。
如果只需要連接遠程設備就不需要打開設備的可以被發現功能。只在應用作為一個服務器socket的宿主用來接收進來的連接時才需要使能可以被發現功能,因為遠程設備在初始化連接前必須先發現了你的設備。
9.6 ConnectingDevices服務
為了建立兩個設備之間的應用的連接,需要完成服務器端和客戶端,因為一個設備必須打開一個服務器socket而另外一個設備必須初始化連接(用服務器端的MAC地址)。服務器和客戶端在各自獲得一個基於同一個RFCOMM信道的已連接的BluetoothSocket對象后就被認為連接已經建立。這個時候,雙方設備可以獲取輸入輸出流,數據傳輸可以開始了。本節描述如何在兩個設備之間初始化連接。
服務器設備和客戶端設備用不同的方式獲取各自需要的BluetoothSocket對象。服務器端的在接收一個進來的連接時獲取到,客戶端的在打開一個與服務器端的RFCOMM信道的時候獲取到。
一個實現技巧是自動把每個設備作為服務器,這樣就擁有了一個打開的socket用來偵聽連接。然后任一設備就能夠發起與另一個設備的連接,並成為客戶端。另外,一個設備也可以明確的成為“host”,並打開一個服務端socket,另一個設備可以簡單的發起連接。
注意:如果兩個設備之前沒有配對,那么在連接處理過程中Android應用框架會自動顯示一個配對請求的通知或對話框給用戶。因此,當嘗試連接設備時,應用不需要關心設備是否已經配對。RFCOMM連接會阻塞直到用戶成功將設備配對(如果用戶拒絕配對或者配對超時了連接會失敗)。
(1)Connectingas a server
如果要連接兩個設備,其中一個必須充當服務器,通過持有一個打開的BluetoothServerSocket對象。服務器socket的作用是偵聽進來的連接,如果一個連接被接受,提供一個連接好的BluetoothSocket對象。從BluetoothServerSocket獲取到BluetoothSocket對象之后,BluetoothServerSocket就可以(也應該)丟棄了,除非你還要用它來接收更多的連接。
下面是建立服務器socket和接收一個連接的基本步驟:
①通過調用listenUsingRfcommWithServiceRecord(String,UUID)得到一個BluetoothServerSocket對象。
該字符串為服務的識別名稱,系統將自動寫入到一個新的服務發現協議(SDP)數據庫接入口到設備上的(名字是任意的,可以簡單地是應用程序的名稱)項。UUID也包括在SDP接入口中,將是客戶端設備連接協議的基礎。也就是說,當客戶端試圖連接本設備,它將攜帶一個UUID用來唯一標識它要連接的服務,UUID必須匹配,連接才會被接受。
② 通過調用accept()來偵聽連接請求。
這是一個阻塞的調用,知道有連接進來或者產生異常才會返回。只有遠程設備發送一個連接請求,並且攜帶的UUID與偵聽它socket注冊的UUID匹配,連接請求才會被接受。如果成功,accept()將返回一個連接好的BluetoothSocket對象。
③ 除非需要再接收另外的連接,否則的話調用close()。
close()釋放serversocket和它的資源,但不會關閉連接accept()返回的連接好的BluetoothSocket對象。與TCP/IP不同,RFCOMM同一時刻一個信道只允許一個客戶端連接,因此大多數情況下意味着在BluetoothServerSocket接受一個連接請求后應該立即調用close()。
accept()調用不應該在主ActivityUI線程中進行,因為這是個阻塞的調用,會妨礙其他的交互。經常是在在一個新線程中做BluetoothServerSocket或BluetoothSocket的所有工作來避免UI線程阻塞。注意所有BluetoothServerSocket或BluetoothSocket的方法都是線程安全的。
示例:
下面是一個簡單的接受連接的服務器組件代碼示例:
privateclass AcceptThread extends Thread {
privatefinal BluetoothServerSocket mmServerSocket;
publicAcceptThread() {
//Use a temporary object that is later assigned to mmServerSocket,
//because mmServerSocket is final
BluetoothServerSockettmp = null;
try{
//MY_UUID is the app's UUID string, also used by the client code
tmp= mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME,MY_UUID);
}catch (IOException e) { }
mmServerSocket= tmp;
}
publicvoid run() {
BluetoothSocketsocket = null;
//Keep listening until exception occurs or a socket is returned
while(true) {
try{
socket= mmServerSocket.accept();
}catch (IOException e) {
break;
}
//If a connection was accepted
if(socket != null) {
//Do work to manage the connection (in a separate thread)
manageConnectedSocket(socket);
mmServerSocket.close();
break;
}
}
}
/**Will cancel the listening socket, and cause the thread to finish */
publicvoid cancel() {
try{
mmServerSocket.close();
}catch (IOException e) { }
}
}
本例中,僅僅只接受一個進來的連接,一旦連接被接受獲取到BluetoothSocket,就發送獲取到的BluetoothSocket給一個單獨的線程,然后關閉BluetoothServerSocket並跳出循環。
注意:accept()返回BluetoothSocket后,socket已經連接了,所以在客戶端不應該呼叫connnect()。
manageConnectedSocket()是一個虛方法,用來初始化線程好傳輸數據。
通常應該在處理完偵聽到的連接后立即關閉BluetoothServerSocket。在本例中,close()在得到BluetoothSocket后馬上被調用。還需要在線程中提供一個公共的方法來關閉私有的BluetoothSocket,停止服務端socket的偵聽。
(2)Connectingas a client
為了實現與遠程設備的連接,你必須首先獲得一個代表遠程設備BluetoothDevice對象。然后使用BluetoothDevice對象來獲取一個BluetoothSocket來實現來接。
下面是基本的步驟:
①用BluetoothDevice調用createRfcommSocketToServiceRecord(UUID)獲取一個BluetoothSocket對象。
這個初始化的BluetoothSocket會連接到BluetoothDevice。UUID必須匹配服務器設備在打開BluetoothServerSocket時用到的UUID(用listenUsingRfcommWithServiceRecord(String,UUID))。可以簡單的生成一個UUID串然后在服務器和客戶端都使用該UUID。
② 調用connect()完成連接
當調用這個方法的時候,系統會在遠程設備上完成一個SDP查找來匹配UUID。如果查找成功並且遠程設備接受連接,就共享RFCOMM信道,connect()會返回。這也是一個阻塞的調用,不管連接失敗還是超時(12秒)都會拋出異常。
注意:要確保在調用connect()時沒有同時做設備查找,如果在查找設備,該連接嘗試會顯著的變慢,慢得類似失敗了。
實例:
下面是一個完成Bluetooth連接的樣例線程:
privateclass ConnectThread extends Thread {
privatefinal BluetoothSocket mmSocket;
privatefinal BluetoothDevice mmDevice;
publicConnectThread(BluetoothDevice device) {
//Use a temporary object that is later assigned to mmSocket,
//because mmSocket is final
BluetoothSockettmp = null;
mmDevice= device;
//Get a BluetoothSocket to connect with the given BluetoothDevice
try{
//MY_UUID is the app's UUID string, also used by the server code
tmp= device.createRfcommSocketToServiceRecord(MY_UUID);
}catch (IOException e) { }
mmSocket= tmp;
}
publicvoid run() {
//Cancel discovery because it will slow down the connection
mBluetoothAdapter.cancelDiscovery();
try{
//Connect the device through the socket. This will block
//until it succeeds or throws an exception
mmSocket.connect();
}catch (IOException connectException) {
//Unable to connect; close the socket and get out
try{
mmSocket.close();
}catch (IOException closeException) { }
return;
}
//Do work to manage the connection (in a separate thread)
manageConnectedSocket(mmSocket);
}
/**Will cancel an in-progress connection, and close the socket */
publicvoid cancel() {
try{
mmSocket.close();
}catch (IOException e) { }
}
}
注意到cancelDiscovery()在連接操作前被調用。在連接之前,不管搜索有沒有進行,該調用都是安全的,不需要確認(當然如果有要確認的需求,可以調用isDiscovering())。
manageConnectedSocket()是一個虛方法,用來初始化線程好傳輸數據。
在對BluetoothSocket的處理完成后,記得調用close()來關閉連接的socket和清理所有的內部資源。
9.7 Managing aConnection服務
如果已經連接了兩個設備,他們都已經擁有各自的連接好的BluetoothSocket對象。那就是一個有趣的開始,因為你可以在設備間共享數據了。使用BluetoothSocket,傳輸任何數據通常來說都很容易了:
(1)通過socket獲取輸入輸出流來處理傳輸(分別使用getInputStream()和getOutputStream())。
(2)用read(byte[])和write(byte[])來實現讀寫。
僅此而已。
當然,還是有很多細節需要考慮的。首要的,需要用一個專門的線程來實現流的讀寫。只是很重要的,因為read(byte[])和write(byte[])都是阻塞的調用。read(byte[])會阻塞直到流中有數據可讀。write(byte[])通常不會阻塞,但是如果遠程設備調用read(byte[])不夠快導致中間緩沖區滿,它也可能阻塞。所以線程中的主循環應該用於讀取InputStream。線程中也應該有單獨的方法用來完成寫OutputStream。
示例:
下面是一個如上面描述那樣的例子:
privateclass ConnectedThread extends Thread {
privatefinal BluetoothSocket mmSocket;
privatefinal InputStream mmInStream;
privatefinal OutputStream mmOutStream;
publicConnectedThread(BluetoothSocket socket) {
mmSocket= socket;
InputStreamtmpIn = null;
OutputStreamtmpOut = null;
//Get the input and output streams, using temp objects because
//member streams are final
try{
tmpIn= socket.getInputStream();
tmpOut= socket.getOutputStream();
}catch (IOException e) { }
mmInStream= tmpIn;
mmOutStream= tmpOut;
}
publicvoid run() {
byte[]buffer = new byte[1024]; // buffer store for the stream
intbytes; // bytes returned from read()
//Keep listening to the InputStream until an exception occurs
while(true) {
try{
//Read from the InputStream
bytes= mmInStream.read(buffer);
//Send the obtained bytes to the UI Activity
mHandler.obtainMessage(MESSAGE_READ,bytes, -1, buffer)
.sendToTarget();
}catch (IOException e) {
break;
}
}
}
/*Call this from the main Activity to send data to the remote device */
publicvoid write(byte[] bytes) {
try{
mmOutStream.write(bytes);
}catch (IOException e) { }
}
/*Call this from the main Activity to shutdown the connection */
publicvoid cancel() {
try{
mmSocket.close();
}catch (IOException e) { }
}
}
構造函數中得到需要的流,一旦執行,線程會等待從InputStream來的數據。當read(byte[])返回從流中讀到的字節后,數據通過父類的成員Handler被送到主Activity,然后繼續等待讀取流中的數據。
向外發送數據只需簡單的調用線程的write()方法。
線程的cancel()方法時很重要的,以便連接可以在任何時候通過關閉BluetoothSocket來終止。它應該總在處理完Bluetooth連接后被調用。
9.8 Working withProfiles服務
從Android3.0開始,BluetoothAPI就包含了對Bluetoothprofiles的支持。Bluetoothprofile是基於藍牙的設備之間通信的無線接口規范。例如Hands-Freeprofile(免提模式)。如果移動電話要連接一個無線耳機,他們都要支持Hands-Freeprofile。
你在你的類里可以完成BluetoothProfile接口來支持某一Bluetoothprofiles。AndroidBluetooth API完成了下面的Bluetoothprofile:
Headset:Headsetprofile提供了移動電話上的Bluetooth耳機支持。Android提供了BluetoothHeadset類,它是一個協議,用來通過IPC(interprocesscommunication)控制BluetoothHeadset Service。BluetoothHeadset既包含BluetoothHeadset profile也包含Hands-Freeprofile,還包括對AT命令的支持。
A2DP:AdvancedAudio Distribution Profile (A2DP)profile,高級音頻傳輸模式。Android提供了BluetoothA2dp類,這是一個通過IPC來控制BluetoothA2DP的協議。
下面是使用profile的基本步驟:
① 獲取默認的Bluetooth適配器。
②使用getProfileProxy()來建立一個與profile相關的profile協議對象的連接。在下面的例子中,profile協議對象是BluetoothHeadset的一個實例。
③設置BluetoothProfile.ServiceListener。該listener通知BluetoothProfileIPC客戶端,當客戶端連接或斷連服務器的時候。
④在onServiceConnected()內,得到一個profile協議對象的句柄。
⑤一旦擁有了profile協議對象,就可以用它來監控連接的狀態,完成於該profile相關的其他操作。
例如,下面的代碼片段顯示如何連接到一個BluetoothHeadset協議對象,用來控制Headsetprofile:
BluetoothHeadsetmBluetoothHeadset;
//Get the default adapter
BluetoothAdaptermBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//Establish connection to the proxy.
mBluetoothAdapter.getProfileProxy(context,mProfileListener, BluetoothProfile.HEADSET);
privateBluetoothProfile.ServiceListener mProfileListener = newBluetoothProfile.ServiceListener() {
publicvoid onServiceConnected(int profile, BluetoothProfile proxy) {
if(profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset= (BluetoothHeadset) proxy;
}
}
publicvoid onServiceDisconnected(int profile) {
if(profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset= null;
}
}
};
//... call functions on mBluetoothHeadset
//Close proxy connection after use.
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);
十 總結與疑問
1.藍牙驅動的上下電是怎么完成的?是通過操作寄存器,還是其他的方式?
2.bccmd命令主要用來初始化藍牙,比如說上電,設置波特率,下載firmware,它是如何實現的?
3.如何設置PSKEY鍵值?
4.藍牙協議棧bluez是如何通過dbus總線與framework層進行數據交互的?