顯示具有 Bluetooth 標籤的文章。 顯示所有文章
顯示具有 Bluetooth 標籤的文章。 顯示所有文章

星期二, 10月 11, 2011

Bluetooth (六)

Android應用程式學習筆記

Managing a Connection

當你已經成功連線兩個或更多裝置,每個裝置都有一個BluetoothSocket。利用BluetoothSocket,輕鬆地通過一般程序傳輸任意數據:

  1. 取得InputStream和OutputStream處理通過Socket的傳輸,分別是getInputStream()和getOutputStream()。
  2. read(byte[])和write(byte[])方法讀或寫數據到資訊流中。
首先,你應該使用專用的執行續處理讀與寫的資訊流。這是非常重要的,因為read(byte[])和write(byte[])是阻絕式呼叫,read(byte[])將會組塞直到有東西從資訊流讀取出,write(byte[])不常阻塞,但是如果遠端裝置不快速呼叫read(byte[])方法及中間的緩衝區滿了就會阻塞,所以在執行續中的主迴圈專用來讀取InputStream,執行續中分開的公開方法可以利用來啟動寫入到OutputStream。

舉例


private class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;
 
    public ConnectedThread(BluetoothSocket socket) {
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = 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;
    }
 
    public void run() {
        byte[] buffer = new byte[1024];  // buffer store for the stream
        int bytes; // 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 */
    public void write(byte[] bytes) {
        try {
            mmOutStream.write(bytes);
        } catch (IOException e) { }
    }
 
    /* Call this from the main Activity to shutdown the connection */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}
建構式取得必須的資訊流,一旦執行,執行續等待數據通過InputStream到來,當read(byte[])回傳來自資訊流的byte數據,利用父類別的Handler將數據寄送到主Activity,然後回到執行續中繼續等待更多數據從資訊流送來。

傳送出數據從主Activity呼叫執行續的write()方法,傳入byte數據到方法中,此方法在呼叫write(byte[])將數據傳送到遠端裝置。

執行續的cancel()是重要的,以便通過BluetoothSocket,在任何時間關閉連線,此方法應該在使用完藍芽連線後呼叫。

Bluetooth (五)

Android應用程式學習筆記

Connecting Devices

為了建立兩個裝置上的應用程式之間的連線,你必須執行伺服器端與客戶端的機制,因為一個裝置必須開啟伺服器插口,另一個必須啟動連線(使用伺服器端的MAC位址建立連線),當它們彼此有已連線的BluetoothSocket在相同的RFCOMM頻道上時,表示伺服器端與客戶端彼此已建立連線,在此點,彼此都獲得輸出與輸入的資訊流然後數據傳輸就可以開始了。此部分會在Managing a Connection中學習到。

伺服器端裝置與客戶端裝置都以不同方式獲得所需的BluetoothSocket,伺服器端會在接受連入的時候獲得,客戶端會在開啟通往伺服器端的RFCOMM頻道時獲得。

有一個技術是自動將每個裝置視為伺服器端,因此每個裝置都有一個開啟的伺服器插口並監聽連線,且裝置可以啟動與其他裝置的連線,成為客戶端,另外,一台裝置可以明確地為"主機"連線,並開放伺服器插口點播,其他裝置可以輕易地實現連線。


Connecting as a server

如果你想要連接兩個裝置,一個裝置必須作為伺服器,持有一個開放的BluetoothServerSocket,伺服器插口的目的是監聽連入的請求,並且當它接受後,提供一個已連線的BluetoothSocket。當BluetoothSocket已經從BluetoothServerSocket收購,BluetoothServerSocket應該被丟棄,除非你想接受更多連線。

以下是設置伺服器插口語接受連線的基本程序:

  1. 呼叫]listenUsingRfcommWithServiceRecord(String , UUID)取得BluetoothServerSocket。
    傳入的字串參數是你的服務的可辨識名稱,系統自動地將字串寫入新的Service Dicovery Protocol(SDP)在裝置的資料庫中。參數UUID也是包括在SDP中並且將是客戶端連線許可的基礎,當客戶端視著與此裝置連線,客戶端會帶著唯一辨識想連線伺服器的UUID,這些UUID必須相匹配,連線才會被接受。
  2. 呼叫accept()方法監聽連線的請求。
    這是阻絕式呼叫,當連線已經被接受時或是異常發生時它回傳。連線被接受只在遠端裝置已發送UUID的連線請求,且UUID與已註冊正在監聽的伺服器插口的UUID相匹配時。當連線成功,accept()方法回傳已連線BluetoothSocket。
  3. 呼叫close()方法,除非你想接受更多連線。
    釋放伺服器插口及所有資源,但不會關閉用accept()方法請求來的已連線的BluetoothSocket。不像TCP/IP、RFCOMM一個頻道同時只允許一個客戶端連線,所以在大部分的案例中,在接受一個已連線的插口後,很直覺的立即呼叫BluetoothServerSocket的close()方法。
呼叫accept()方法不應該在主Activity的UI執行續中執行,因為它是一個阻絕式呼叫且將阻止與應用程式的任何互動,由其他新的執行續完成BluetoothServerSocket或是BluetoothSocket的所有工作是比較合理的,退出阻絕式呼叫,比如accept()方法,從其他執行續呼叫在BluetoothServerSocket的close()方法。

舉例:
以下為伺服器組件及接受連線的執行續:


private class AcceptThread extends Thread {
    private final BluetoothServerSocket mmServerSocket;
 
    public AcceptThread() {
        // Use a temporary object that is later assigned to mmServerSocket,
        // because mmServerSocket is final
        BluetoothServerSocket tmp = 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;
    }
 
    public void run() {
        BluetoothSocket socket = 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 */
    public void cancel() {
        try {
            mmServerSocket.close();
        } catch (IOException e) { }
    }
}
此例中,只需一個連入連線,所以只要連線被接受且BluetoothSocket被收購,應用程式就會寄送BluetoothSocket到另一個執行續,關閉BluetoothServerSocket,結束迴圈。

注意當accept()回傳BluetoothSocket,插口已準備好連線,所以無需呼叫connect()方法。

在應用程式中manageConnectedSocket()是一個虛構的方法,啟動執行續來傳輸資料。

通常應該在完成連入的連線工作後關閉BluetoothServerSocket,在此例中,BluetoothSocket被收購,同時close()方法被呼叫,你也許想在你的執行續提供公開方法關閉私有的BluetoothSocket,在你需要關閉監聽伺服器插口事件上。


Connecting as a client

為了與遠端裝置啟動連線(擁有伺服器插口的裝置),你首先必須獲得代表遠端裝置的BluetoothDevice物件,接著你必須用BluetoothDevice物件收購一個BluetoothSocket,並啟動連線。

以下為基本程序:

  1. 利用BluetoothDevice呼叫createRfcommSocketToServerRecord(UUID)取得BluetoothSocket。
    此步驟初始化與BluetoothDevice連線的BluetoothSocket,此處傳入的參數UUID必須與開啟BluetoothServerSocket的裝置的UUID相匹配。利用相同UUID是在應用程式的簡單UUID字串應編碼,並由伺服器端與客戶端所引用。
  2. 呼叫connect()方法啟動連線。
    在呼叫此方法之上,系統執行SDP查找遠端裝置,為了配對UUID。如果查找成功,遠端裝置接受連線,遠端裝置利用連線分享RFCOMM頻道。此方法是阻絕式呼叫,如果任何原因連線失敗、或是connect()時間到了(超過12秒),它就會丟出意外。
    因為connect()是阻絕式呼叫,所以此連線應該在與主執行續分開的執行續執行。
舉例:
以下為一個簡單的例子執行一個新的執行續與啟動藍芽連線。


private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;
 
    public ConnectThread(BluetoothDevice device) {
        // Use a temporary object that is later assigned to mmSocket,
        // because mmSocket is final
        BluetoothSocket tmp = 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;
    }
 
    public void 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 */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}
注意cancelDiscovery()方法呼叫在連線建立之前,你應該總是在連線之前這樣做,無須檢查它是否運行,呼叫它是安全的。

當你使用BluetoothSocket完後,總要呼叫close()方法清除,立即地關閉連接插口且清除所有資源。

星期一, 10月 10, 2011

Bluetooth (四)

Android應用程式學習筆記

Finding Devices

使用BluetoothAdapter,你可以找到遠端的藍芽裝置,不管是通過裝置探詢或是查詢配對裝置。

裝置探詢(device discovery)是掃描程序,搜尋本地可用的藍芽裝置,然後,一一要求一些資訊。當在本地區的藍芽裝置目前能夠是被發現的,它才會回應探詢的請求。如果裝置是可發現的,它將分享一些資訊回應探尋的請求,比如名稱、類別、位址。使用這些資訊,執行探詢的裝置可以選擇初始化連線的裝置。

一旦與遠端裝置第一次連線,配對要求自動顯示給用戶,當配對成功,裝置的基本資料就會被儲存並且可經由Bluetooth APIs讀取。使用已知的遠端裝置的MAC位址,就能在任何時間執行連線無需在經過探詢。

記住能被配對(paired)與能被連線(connected)是不相同的,能被配對是指兩個裝置知道彼此是存在的,彼此已經分享了link-key用來認證,且彼此之間是有能力建立加密連線。能被連線是指裝置目前分享RFCOMM頻道且可以彼此傳遞數據。目前Android Bluetooth APIs的要求裝置配對之前必須先建立RFCOMM連線。

以下我們就來學習如何找到裝置進行配對或是使用裝置探詢找到新裝置。


Querying paired devices


在執行裝置探詢之前,查詢已配對裝置了解想連線的裝置是否已是我們已配對裝置是值得的,呼叫getBondedDevice()方法,此方法回傳一系列BluetoothDevices顯示配對裝置。例如,你可以查詢所有的已配對裝置,然後顯示出它們的名稱給用戶,使用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位址。在此例中,我們將資訊存在ArrayAdapter中顯示給用戶。MAC位址會在之後要連線時取出(see in Connecting Device)。


Discovering devices

啟動裝置探詢,呼叫startDiscovery(),該程序是異步並且會立刻回傳一個布林值表示探詢工作是否已經成功開始。探詢程序通常涉及調查掃描大約12秒鐘,通過已找到的裝置的每頁掃描取得藍芽名稱。

你的應用程式必須註冊廣播接收器接收ACTION_DOUND意圖,為了接收每個被探詢的裝置資訊。對每個裝置,系統會廣播ACTION_FOUND意圖,意圖夾帶EXTRA_DEVICE和EXTRA_CLASS額外檔案,分別包括一個BluetoothDevice和一個BluetoothClass。舉例,當裝置被探詢時,你可以如何註冊處理廣播。


// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        // When discovery finds a device
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            // Get the BluetoothDevice object from the Intent
            BluetoothDevice device = 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
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy



Enabling discoverablilty

如果你想要使本地裝置能被其他裝置找到,呼叫startActivityForResult(intent , int)方法,ACTION_REQUEST_DISCOVERABLE動作意圖,這將發出一個請求,以便通過系統設定啟用可發現模式,預設情況下,裝置變成可發現的長達120秒,你可以藉著EXTRA_DISCOVERABLE_DURATION意圖定義不同的持續時間,最大可以設定為3600秒,數值0表示裝置永遠是可發現的,任何低於0及高於3600的值都會被設成120秒。以下舉例設定為300秒。


Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);

對話框會顯示,請求用戶權限使裝置為可發現的,如下圖,如果用戶回應"Yes",裝置將變成可發現的時間長達指定的時間,你的Activity接著呼叫onActivityResult()回調方法,如果用戶回應"No"或發生錯誤,結果代碼將是Activity.RESULT_CANCELED。


裝置在配發的時間內靜靜地保持可發現的模式,如果你想要當可發現模式改變時的告知,你可以為ACTION_SCAN_MODE_CHANGE意圖註冊廣播接收器,意圖會包括兩個額外的字段,EXTRA_SCAN_MODE和EXTRA_PREVIOUS_SCAN_MODE,它們分別為新的和舊的掃瞄模式,每個字段的值有可能為SCAN_MODE_CONNECTABLE_DISCOVERABLE、SCAN_MODE_CONNECTABLE、SCAN_MODE_NONE,它們分別表示裝置在可發現的模式,不在可發現模式但是可以接受連線,或是不在可發現模式且無法接受連線。

如果你將啟動與遠端裝置的連線,你無需啟用裝置為可發現。啟用可發現模式只在當你想要你的應用程式是主機伺服器接收傳入的連線才需要,因為遠端裝置必須在連線之前能夠發現裝置。

星期日, 10月 09, 2011

Bluetooth (三)

Android應用程式學習筆記

Setting Up Bluetooth

在你的應用程式用藍芽通訊之前,你需要驗證裝置支援藍芽,然後確認它是否可用。

如果沒支援藍芽,你應該優雅地停止任何藍芽屬性。如果支援藍芽,但停用的,你可以不用離開應用程式的情況下,請求用戶啟用藍芽裝置。啟用的過程由兩個步驟建立,使用BluetoothAdapter。


  1. 取得BluetoothAdapter
    BluetoothAdapter是任何及所有藍芽Activity需要的,呼叫靜態的getDefaultAdapter()方法取得BluetoothAdapter,此方法回傳的BluetoothAdapter表示裝置本身自己的藍芽適配器(藍芽電台),還有一個負責整個系統的藍芽適配器,你的應用程式可以使用回傳的適配器與負責整個系統的適配器互動。如果getDefaultAdapter()回傳直視null,表示裝置不支援藍芽。

    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
        // Device does not support Bluetooth
    }
  2. 啟用藍芽
    接下來就需要確認藍芽適可用的,呼叫isEnable()方法檢驗目前是否有可用的藍芽,如果回傳值為false,藍芽是未啟用的,呼叫startActivityForResult()方法並傳入參數動作值ACTION_REQUEST_ENABLE的意圖物件,請求藍芽功能啟用,這將發出一個請求,通過系統設定啟用藍芽功能。

    if (!mBluetoothAdapter.isEnabled()) {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    }

    對話框顯示請求用戶權限來啟用藍芽功能,如果用戶回應"Yes",系統將開始啟用藍芽功能,程序完成後(或是失敗)焦點會再回到應用程式。

    如果啟用藍芽功能成功,你的Activity就在onActivityResult()回呼方法中接收到RESULT_OK,如果藍芽功能啟用失敗(或是用戶回應"No"),接收到RESULT_CANCELED。
另外可選,你的應用程式也可以監聽ACTION_STATE_CHANGED廣播意圖,藍芽狀態改變時系統將會廣播出去,廣播包括EXTRA_STATE、EXTRA_PREVIOUS_STATE,分別為新的和舊的藍芽狀態,其他可能值有STATE_TURNING_ON、STATE_ON、STATE_TURNING_OFF、STATE_OFF,監聽廣播幫助你的應用程式在運行時偵測藍芽的狀態。


Bluetooth (二)

Android應用程式學習筆記

Bluetooth Permissions

為了在應用程式中使用藍芽,你至少有宣告兩個藍芽權限的其中一個:BLUETOOTH及BLUETOOTH_ADMIN。

執行藍芽通訊必須請求BLUETOOTH權限,比如,請求連線、接收連線、傳輸數據。

啟動裝置探索或操作藍芽設定必須請求BLUETOOTH_ADMIN權限,大部分應用程式需要此權限僅是用來探詢本地藍芽裝置。此權限授予的其他功能應該是用不上,除非應用程式是"power manager",用到修改藍芽設定的請求。注意:如果有BLUETOOTH_ADMIN權限,大部分也有BLUETOOTH權限。

在應用程式的manifest檔案宣告藍芽權限:


<manifest ... >
  <uses-permission android:name="android.permission.BLUETOOTH" />
  ...</manifest>




Bluetooth (一)

Android應用程式學習筆記

Bluetooth

Android平台支援藍芽網路,它允許與其他藍芽裝置連結無線交換數據,應用程式架構通過Android bluetooth APIs提供藍芽存取功能,APIs讓應用程式無線地與其他藍芽裝置連結,可以點對點與多點無線屬性。

有Bluetooth APIs,應用程式可以執行以下功能:
  • 掃描其他藍芽裝置。
  • 查詢本地藍芽適配器去配對藍芽裝置。
  • 建立RFCOMM頻道。
  • 透過裝置探詢與其它裝置連結。
  • 與其他裝置傳輸數據。
  • 管理多個連結。


The basics

此部分要學習如何使用Android bluetooth APIs建立四個主要運用藍芽功能所需的任務:設置藍芽、搜尋本地的配對或是可用的藍芽裝置、連結裝置、傳輸資料。

所有的android藍芽APIs都裝在android.bluetooth包中,這裡總結建立藍芽連線需要的類別與介面:

BluetoothAdapter-表示本地的藍芽適配器(藍芽電台,bluetooth radio)。BlueAdpater是所有藍芽的進入點,使用它,你可以發現其他藍芽裝置,查詢一系列配對裝置,實體化BluetoothDevice得知MAC位址,然後建立BluetoothServerSocket接聽其他裝置的訊息。

BluetoothDevice-表示一個遠程藍芽裝置。使用它能通過BluetoothSocket請求與其它裝置連結或是查詢裝置的資訊,名稱、位址、狀態。

BluetoothSocket-表示藍芽socket的接口。它是連結點,允許應用程式與其他藍芽裝置交換數據,通過InputStream及OutputStream。

BluetoothServerSocket-表示一個開放server socket,接聽傳入的請求。為了連結兩個Android裝置,一個裝置必須開放server socket,當遠端的裝置請求連結此裝置,BluetoothServerSocket回傳BluetoothSocket接受連結的請求。

BluetoothClass-描述連芽裝置的一般特性與能力。這是一個唯讀的屬性,它定義了裝置的主要與次要的裝置類別及它的服務。然而,描述所有藍芽屬性及支援的服務是不可靠的,但是它作為裝置型態的提示是有用的。

BluetoothProfile-顯示藍芽資料的介面。藍芽資料是無線藍芽通訊介面的規範。例如,免提規範。

BluetoothHeadset-提供行動手機支援藍芽耳機。包括藍芽耳機和免提規範。

BluetoothA2dp-定義如何使高品質音頻流通過藍芽連結從一個裝置流到另一個裝置,A2Dp代表了高級音頻傳輸規範。

BluetoothProfile.ServiceListener-告知BluetoothProfile IPC客戶端的介面。當它們已經與其他裝置連結或斷線時。