天天看點

手機藍牙程式設計簡介

手機藍牙程式設計簡介

一、什麼是藍牙技術

藍牙是一種低成本、短距離的無線通信技術。對于那些希望建立個人區域網路(PANs )的人們來說,藍牙技術已經越來越流行了。每個個人區域網路都在獨立裝置的周圍被動态地建立,并且為蜂窩 式電話和PDA 等裝置提供了自動連接配接和即時共享資料的能力。為了在Java 平台上開發支援藍牙技術的軟體,JCP 定義了JSR82 标準--Java 藍牙無線技術APIs(JABWT) 。

當藍牙裝置互相連接配接時,他們将組成一個微微網(piconet ), 即以一個主裝置和最大7 個從裝置的形式動态建立網絡。藍牙也支援piconet 網之間的連接配接:當一個piconet 中的 主裝置成為另一個piconet 的從裝置時,piconet 與piconet 間将形成橋接。

二、藍牙協 議棧

藍牙協定棧允許采用 多種方法,包括 RFCOMM 和 Object Exchange (OBEX ), 在裝置之間發送和接收檔案。如果想發送和接收流資料(而且想采用傳統的序列槽應用程式,并給它加上藍牙支援),那麼 RFCOMM 更好。反過來,如果想發送對象資料以及關于負載的上下文和中繼資料,則 OBEX 最好。圖 1 顯示了協定棧的細節。

圖1 藍牙協定棧 ,如下 :

手機藍牙程式設計簡介
  • 棧的最底層是 HCI ,即主機控制器接口 (Host Controller Interface )。這一層顧名思義就是主機(計算機)和控制器(藍牙裝置)之間的接口。可以看到,其 他所有的層都要經過 HCI 。
  • HCI 上面的一層是L2CAP , 即邏輯連結控制器适配協定(Logical Link Controller Adaptation Protocol )。這一層充當其他所有層的資料多路複用器。
  • 接下來一層是 BNEP ,即藍牙網絡封裝 協定(Bluetooth Network Encapsulation Protocol )。使用 BNEP , 可以在藍牙上運作其他網絡協定,例如 IP 、TCP 和 UDP 。
  • RFCOMM 稱作虛拟序列槽協定(virtual serial port protocol ) ,因為它允許藍牙裝置模拟序列槽的功能。
  • OBEX 協定層是在RFCOMM 層 上面實作的,如果想把資料以對象(例如檔案)的形式傳輸,那麼OBEX 很有用。
  • SDP 是服務發現協定(Service Discovery Protocol )層,用于在遠端藍牙裝置上尋找服務。
  • 最後兩層是 AVCTP 和 AVDTP ,用于藍牙上音頻和視訊的控制 和 釋出 。AVCTP 和 AVDTP 是藍牙協定中增加的相對較新的層;如果想控制媒體播放器的功能或者想以立體聲播放音頻流,則要使用它們。

三、藍牙規範用例

初始化-- 所有具備藍牙功能的應用程式必須先要初始化藍牙 棧。

服 務器:建立一個服務,然後等待一個用戶端來連接配接。

客 戶端:搜尋服務,然後嘗試與伺服器建立連接配接。

圖2 一個藍牙規範用例圖,如下:

手機藍牙程式設計簡介

四、用例中參與活動的圖表

圖3 藍牙應用程式活動圖,如下:

手機藍牙程式設計簡介

五、JSR-82 API 簡介

JSR-82 是用于藍牙無線技術的官方Java API 。可使用這個API 建立可執行以下功能 的應用程式:

  • 判斷和檢測自己的藍牙裝置的屬性
  • 發現裝置通信範圍内的藍牙裝置
  • 在遠端藍牙裝置上搜尋服務
  • 建立可以與遠端藍牙伺服器通信的藍牙客戶機應用程式
  • 建立能夠為藍牙客戶機的請求提供服務的藍牙伺服器應用程式

JSR-82 包含兩個包,即javax.bluetooth 和javax.obex 。

圖4 顯示了在MIDlet 中一個典型藍牙功能應用程式中的 一些元素,如下:

1 、本地裝置類: 

手機藍牙程式設計簡介

圖5 :LocalDevice 類

本地裝置提供了方法來傳回關于本地裝置的資訊,并且能夠進入 Bluetooth manager :

    .getBluetoothAddress() 傳回藍牙裝置位址。

    .getDeviceClass() 傳回裝置類。

    .getFriendlyName() 傳回裝置友好名稱,藍牙裝置名通常是使用者在藍牙控制中心為其設定的我們将會在後面看到。

    .getRecord() 傳回一個指定藍牙連接配接的服務記錄。

    .updateRecord() 方法用來為指定的ServiceRecord 更 新SDDB 服務記錄。

    .getDiscoverable() 傳回裝置的可發現狀态。

    .setDiscoverable() 設定裝置的可發現狀态。

    .getDiscoveryAgent() 傳回一個參考給發現代理。

    .getProperty() 傳回一個裝置的藍牙屬性

通過 調用getProperty() 方法你可以得到的屬性包括:

    .bluetooth.api.version ,藍牙API 版本

    .bluetooth.sd.attr.retrievable.max ,一次性能夠被獲得的服務記錄屬性的最大值

    .bluetooth.connected.devices.max ,支援的連接配接裝置的最大值

    .bluetooth.sd.trans.max ,同時發生的服務發現處理的最大值

    .bluetooth.l2cap.receiveMTU.max ,L2CAP 最 大發射單元

你可以在Javadoc 文檔中或是規範中學習更多的有 關藍牙屬性的内容。

2 、遠端裝置類:

手機藍牙程式設計簡介

圖6 :RemoteDevice 類

遠端裝置(RemoteDevice ) 提供的方法中,有些很類似于本地裝置 (LocalDevice )裡提供的方法:

    .getBluetoothAddress() 傳回藍牙位址。

    .getFriendlyName() 傳回藍牙裝置名。

    .getRemoteDevice() 傳回相應的被指定藍牙連接配接的遠端裝置。

    .authenticate() 嘗試識别驗證遠端裝置。

    .authorize() 為指定的藍牙連接配接去嘗試準許遠端裝置通路本地裝置。

    .encrypt() 嘗試為指定的藍牙連接配接開啟或關閉加密。

    .isAuthenticated() 測試是否遠端裝置可以被驗證。

    .isAuthorized() 測試是否遠端裝置已經被藍牙控制中心授權通路本地裝置以進行藍牙連接配接。

    .isEncrypted() 測試是否本地裝置和遠端裝置之間的通信被加密。

    .isTrustedDevice() 測試是否遠端裝置被藍牙控制中心指定為可信任的。

3 、DeviceClass 類

    一個DeviceClass 對象代表一個裝置的裝置類(CoD ), 例如一個列印機或者一部電話。CoD 包括一個主類,一個輔的類,和服務類型或服務類。 DeviceClass 提供了如下方法:

    .getMajorDeviceClass() 方法擷取裝置的主類。

    .getMinorDeviceClass() 方法擷取裝置的輔類。

    .getServiceClasses() 擷取裝置的服務類。

當 一個裝置被發現,同時他的類也會被發現;當發現代理調用deviceDiscovered() 時, 其中一個參數就是DeviceClass 。你可以通過它 的getDeviceClass() 方 法找到本地裝置的CoD 。

4 、DiscoveryAgent 類是個有幫助的類,它讓您可以發現附近的遠端藍牙裝置,并為 區域内的每個藍牙裝置傳回一個 RemoteDevice 。也可以使用javax.bluetooth.DiscoveryAgent 在已經發現的遠端裝置上搜尋服務。如果想在發生發現事件的時候得到通知,則需要實作 DiscoveryListener 接口的方 法。見圖7 DiscoveryAgent 類和DiscoveryListener 接 口,如下:

手機藍牙程式設計簡介

5 、裝置發現API

你使用DiscoveryAgent 類的" 裝置發現" 方法來開始和取消裝置發現:

.retrieveDevices() 重新獲得已經發現或者附近的已知裝置

.startInquiry() 啟動發現附近裝置,也叫inquiry

.cancelInquiry() 取消目前進行的任何請求

藍牙發現代理在請求階段的不同時候會分别調用DiscoveryListener (發 現監聽器)不同的回調方法:

.deviceDiscovered() 指出是否有裝置被發現。

.inquiryCompleted() 指出是否請求已經成功、觸發一個錯誤或已被取消。

在圖8 中的狀态圖表闡明了裝置發現的狀态改變結束于相應的回調方法的傳回。

手機藍牙程式設計簡介

圖 8: 裝置發現狀态表

裝置發現以調用startInquiry() 函數開始。在請求進行時,藍牙發現代理會在适當的時候調用回調方法DeviceDiscovered() 和 inquiryCompleted() 。

6 、服務發現API

你可以使用發現代理的服務發現方法來開始或取消服務發現:

. selectService() 啟動服務發現搜尋。(根據API 手冊應為嘗試定 位一個服務)

. searchServices() 啟動服務發現搜尋。

. cancelServiceSearch() 取消在正在進行中的任何的服務發現搜尋操作。藍牙發現代理在服務發現階段的不同時候會分别 調用 DiscoveryListener 的服務發現回調方法:

. servicesDiscovered() 表示是否服務已被發現。

. serviceSearchCompleted() 表示服務發現是否已經完成。

圖9 闡明了服務發現的狀态改變結束于DiscoveryListener 的 回調方法的傳回。

手機藍牙程式設計簡介

圖 9: 服務發現狀态圖表

服務發現開始于對searchServices() 的調用。當服務搜尋進行時,藍牙發現代理會在适當的時候回調servicesDiscovered() 和 serviceSearchCompleted() 方法。

除了DiscoveryAgent 和DiscoveryListener 了, 你在服務發現過程中還要使用到的類有UUID ,ServiceRecord 以 及DataElement 等。

7 、UUID 類

    在藍牙中,每個服務和服 務屬性都唯一地由" 全球唯一辨別符" (UUID )來校驗。正如它的名字所暗示的,每一個這樣的辨別符都要在時空上保證唯一。UUID 類可表現為短整形(16 或32 位)和長整形(128 位)UUID 。他提供了分别利用String 和16 位或32 位數值來建立類的構造函數,提供了一個可以 比較兩個UUID (如果兩個都是128 位) 的方法,還有一個可以轉換一個UUID 為一個字元串的方法。UUID 實 例是不可改變的(immutable ),隻有被UUID 标 示的服務可以被發現。

    在Linux 下你用一個指令uuidgen -t 可以生成一個UUID 值;在Windows 下 則執行指令uuidgen 。UUID 看起來 就像如下的這個形式:2d266186-01fb-47c2-8d9f-10b8ec891363 。 當使用生成的UUID 去建立一個 UUID 對 象,你可以去掉連字元。

8 、SDDB 和ServiceRecord 接口

    在服務發現的中心是服務發現資料庫(SDDB) 和服務發現協定(SDP )。SDDB 由藍牙實作負責維護的資料庫。它包含了服務記錄(service records ),後者代表了對用戶端有效的服務。SDP 對于基于JABWT (Java 藍 牙無線技術APIs )應用程式來說是透明的;可以這麼說,SDP 是用于服務發現的。為重新擷取服務紀錄,一個本地裝置SDP 客 戶端會向一個遠端裝置上SDP 伺服器送出請求。

手機藍牙程式設計簡介

                      圖 10: SDDB

每一筆服務記錄都會由一個ServiceRecord 的 執行個體來表現。這個記錄包含了描述服務細節的屬性。這個類提供了幾種有用的方法:

    .getAttributeIDs() 和 getAttributeValue() 方法傳回服務記錄的屬性。

    .getConnectionURL() 方法擷取連結的URL 位址給伺服器 主機來收集服務記錄。

    .getHostDevice() 方法擷取提供服務的遠端裝置。

    .populateRecord() 和 setAttributeValue() 方法用來設定裝置記錄的屬性。

    .setDeviceServiceClasses() 方法設定服務的類。

圖11 顯示了藍牙本地裝置和遠端裝置,以及SDDB 還 有服務記錄之間的關系:

手機藍牙程式設計簡介

       圖 11: 使 用遠端裝置,SDDB 和服務記錄進行服務發現

    為使服務端可以被用戶端來使用,服務應用程式要通過如下方法建立一個 服務記錄,首先要建立一個連接配接通知器(connection notifier ),然後由調用連接配接 通知器的acceptAndWait() 方法來向SDDB 中 插入記錄。服務端程式能夠在适當的時候獲得記錄和更新。用戶端應用程式向遠端SDDB 請求可以使用 的服務,會發現服務記錄。

六、如何建立一個藍牙服務端

具 體代碼步驟:

1、 得到本地裝置。

try {

       this . localDevice = LocalDevice.getLocalDevice ();

       this . localDevice .setDiscoverable(DiscoveryAgent. GIAC );

    } catch (BluetoothStateException ex) {

       ex.printStackTrace();

    }

2、 生成用戶端連接配接通告。

try {

   String url = "btspp://localhost:F0E0D0C0B0A000908070605040302010;name=BTServer;authorize=false";

       notifier = (StreamConnectionNotifier) Connector.open ( url );

    } catch (IOException ex1) {

       ex1.printStackTrace();

    }

3、 獲得服務記錄。

serviceRecord = localDevice.getRecord(notifier);

4、 從通告獲得遠端藍牙裝置的連接配接。

streamConnection = notifier.acceptAndOpen();

    dataOutputStream = streamConnection.openDataOutputStream();

    dataInputStream = streamConnection.openDataInputStream();

然 後下面就是等待用戶端來連接配接了。

七、如何建立一個藍牙用戶端

1 、搜尋本地裝置

try {

      localDevice = LocalDevice.getLocalDevice ();

      // 獲得發 現代理

      discoverAgent = localDevice .getDiscoveryAgent();

      discoverAgent .startInquiry(DiscoveryAgent. GIAC , this );

    }

    catch (BluetoothStateException ex){

      ex.printStackTrace();

}

  // 裝置發現過程中的回調(用來查找每一個可用的裝置)

  public void deviceDiscovered (RemoteDevice remoteDevice,DeviceClass deviceClass){

    if ( device .indexOf(remoteDevice) == -1){

      device .addElement(remoteDevice);

    }

}

    // 裝置發 現完成回調(用來辨別裝置查找是否完成)

  public void inquiryCompleted( int _int){

    synchronized ( this ){

      notify();

    }

  }

2 、 搜尋遠端裝置提供的服務

uuid = new UUID[2];

    uuid [0] = new UUID(0x1101);

    uuid [1] = new UUID( "F0E0D0C0B0A000908070605040302010" , false );

    for ( int i = 0; i< device .size(); i++){ // 取出已經搜到的遠端裝置

      remoteDevice = (RemoteDevice) device .elementAt(i);

      try { // 搜尋該裝置上的服務

        discoverAgent .searchServices( null , uuid , remoteDevice , this );

      } catch (BluetoothStateException ex){

          ex.printStackTrace();

      }

}

  // 服務發現回調(用來發現遠端裝置上的可用服務)

  public void servicesDiscovered ( int _int, ServiceRecord[] serviceRecordArray){

    for ( int i = 0; i < serviceRecordArray. length ; i++){

      service .addElement(serviceRecordArray[i]);

      serviceRecord = serviceRecordArray[i];

    }

}

  // 服務發現回調(用來辨別服務搜尋是否完成)

  public void serviceSearchCompleted( int _int, int _int1){

    synchronized ( this ){

      notify();

    }

  }

3 、與遠端裝置建立連接配接

String url = serviceRecord .getConnectionURL(ServiceRecord. NOAUTHENTICATE_NOENCRYPT , false );

    try {

      streamConnection = (StreamConnection)Connector.open ( url );

      dataOutputStream = streamConnection .openDataOutputStream();

      dataInputStream = streamConnection .openDataInputStream();

    } catch (IOException ex1) {

        ex1.printStackTrace();

}

經過以上步驟,伺服器端和用戶端就建立起了連接配接,互相之間就可以互相發送資料了。