2015年7月20日 星期一

Bluetooth

Reference:

http://developer.android.com/guide/topics/connectivity/bluetooth.html

Get BluetoothAdapter:

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
   
// Device does not support Bluetooth
}

Enable Bluetooth:


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


Quering paired devices:

MAC address可以用來初始化BluetoothDevice物件

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());
   
}
}


Discovering devices:

只要呼叫 startDiscovery()就可以開始尋找device
應用程式需要註冊一個broadcase receiver來接收搜尋到的device的資訊

// 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


注意:
執行搜尋device會消耗很多資源,所以當找到一個要連接的device,在連接前要先呼叫cancelDiscovery(),停止搜尋device。
另外,當已經在連接中的情況下進行搜尋device,會減少已連接的連線的頻寬,所以在已連接的情況下不要再搜尋device

Get BluetoothDevice:

BluetoothAdapter
public BluetoothDevice  getRemoteDevice (String address)

Valid Bluetooth hardware addresses must be upper case, in a format such as "00:11:22:33:AA:BB". The helper checkBluetoothAddress(String) is available to validate a Bluetooth address.

Parameters

address
valid Bluetooth MAC address

Throws

IllegalArgumentException
if address is invalid 
Ex:
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);

BluetoothSocket
public BluetoothDevice  getRemoteDevice ()

Get the remote device this socket is connecting, or connected, to.

Returns
remote device

Ex:
socket.getRemoteDevice()

Enabling discoverability:

要讓你的Device成為Discoverable,要呼叫StartActivityForResult(Intent,int),Intent要使用ACTION_REQUEST_DISCOVERABLE

Discoverable時間:
Default: 120s
Maximum: 3600s
Set to 0: always
< 0 或 > 3600: 120s

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


無論Discoverable成功或失敗,onActivityResult callback function都會收到呼叫

如果Device的Bluetooth還沒開啟,當Device變成Discoverable時,會自動開啟Bluetooth

若要知道Discoverable mode是否發生改變,可以註冊一個Broadcase receiver監聽ACTION_SCAN_MODE_CHANGED的intent,intent裡面會包含EXTRA_SCAN_MODE 和EXTRA_PREVIOUS_SCAN_MODE
mode分為以下三種:
SCAN_MODE_CONNECTABLE_DISCOVERABLE: 在Discoverable mode
SCAN_MODE_CONNECTABLE: 不在Discoverable mode,但仍可以接收connection
SCAN_MODE_NONE: 不在Disoverable mode,並且也不能接收connection

Connecting Devices:

要在兩台Device之間建立連線,一台Device(Server side)要先打開server socket,另外一台Device(Client side)要初始化connection(使用server device的MAC address)。當兩台Device在同一個RFCOMM channel上都有了已連接的BluetoothSocket,就可以取得input和output stream,進行資料交換

Server和Client取得Bluetooth的方法如下:
Server: when an incoming connection is accepted
Client: when it opens an RFCOMM channel to the server

實作方法:
法一:每一台device都打開server socket,等待device接收connection,當有一台server初始化connection,就變成client
法二:某一台device打開server socket,負責管理connection,其他device只要負責初始化connection

注意:
RFCOMM connection attempt會block住直到配對成功、使用者拒絕配對、配對失敗或time out

Connecting as a server:

server socket的目的是監聽connection request,當接收到request後,就會提供一個BluetoothSocket,當從BlutoothServerSocket取得BluetoothSocket後,除非想要接收更多的connection,否則就應該把BlutoothServerSocket關閉

設定和接受connection的步驟如下:
1. 取得 BluetoothServerSocket:
    呼叫 listenUsingRfcommWithServiceRecord(String, UUID)

    BluetoothAdapter
    public BluetoothServerSocket  listenUsingRfcommWithServiceRecord (String name, UUID uuid)

    Parameters


    name
    service name for SDP record


    uuid
    uuid for SDP record

    Returns
    a listening RFCOMM BluetoothServerSocket

    Throws

    IOException
    on error, for example Bluetooth not available, or insufficient permissions, or channel in use. 

2. 監聽connection request:
    呼叫 accept()

    BluetoothServerSocket
    public BluetoothSocket  accept ()

    Returns
    a connected BluetoothSocket

    Throws

    IOException
    on error, for example this call was aborted, or timeout 

3. 釋放 server socket (除非想要接收額外的connection)
    呼叫 close()

    BluetoothServerSocket
    public void  close ()

    Throws
    IOException

注意:
不應該在 Activity main UI thread呼叫 accept(),因為它是個 blocking call,會把 Application給block住。在使用BluetoothServerSocket和 BluetoothSocket時,應該在新的thread裡執行。

Connecting as a client

要初始化和remote device(擁有 open server socket)之間的connection,先要取得 BluetoothDevice物件代表這個 remote device

步驟:
1. 取得 BluetoothSocket
    呼叫createRfcommSocketToServiceRecord(UUID uuid)
    uuid必須符合server service在開啟BluetoothServerSocket (with   listenUsingRfcommWithServiceRecord(String, UUID))時所用uuid

    BluetoothDevice
    public BluetoothSocket  createRfcommSocketToServiceRecord (UUID uuid)

   
Parameters

    uuid
    service record uuid to lookup RFCOMM channel

    Returns
    a RFCOMM BluetoothServerSocket ready for an outgoing connection

    Throws

    IOException
    on error, for example Bluetooth not available, or insufficient permissions

2. 初始化connection
    呼叫connect()

    BluetoothSocket

    public void  connect ()

    Throws

    IOException
    on error, for example connection failure

    呼叫了connect(),system會查詢 remote device是否有符合的uuid,如果查詢成功,remote
    device會接受這個connection,並分享RFCOMM channel。
    connect()是一個blocking call,當connection fail或 connect()發生time out(約12秒),就會丟出
    exception

注意:
當呼叫 connect時,避免 device正在執行device discovery,否則 connection attempt會變得很慢,並且可能會fail

Managing a connction:

傳送任意資料的步驟:
1. 取得 InputStream和 OutputStream來處理資料傳輸
     呼叫 getInputStream()和 getOutputStream
2. 讀取 steam的資料
    呼叫 read(byte[])和 write(byte[])

注意:
因為 read(byte[])和 write(byte[])是 blocking call,所以應該create新的thread來讀取stream。
read(byte[])會 block住直到 stream有資料可以讀取;只有當device的讀取速度不夠快,導致bufer滿了,write(byte[])才會 block住

沒有留言: