1. 程式人生 > >基於藍芽介面卡的PC與Android端通訊

基於藍芽介面卡的PC與Android端通訊

Demo

首先,直接給Demo,對於只想使用的朋友,直接下載使用即可。Demo其實也是從網上爬來的,之後做了各種除錯和修改。

原有Demo程式碼下載,可見地址

修改後Demo效果如下。效果不太清晰,見諒。

(1)PC端


(2)Android端


細節實現

Android端

android端做為客戶端要與PC通訊,需要完成以下幾步。

新增藍芽許可權

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

註冊廣播接收器

個人認為該步驟可選。但最好存在。
原因在於,雖然藍芽介面卡可以獲取周圍藍芽裝置的列表,但對於周圍藍芽裝置的掃描比較耗時。
返回藍芽裝置列表時,可能仍處於搜尋過程中。
當掃描完成時,廣播接收器將受到action為ACTION_DISCOVERY_FINISHED的廣播。此時,獲取裝置列表,將比較全面。

建議接收器監聽,以下三個action。

    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(BluetoothDevice.ACTION_FOUND);//發現裝置
    intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//掃描完畢
    intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);//掃描結束
    registerReceiver(broadcastReceiver, intentFilter);

開啟藍芽

    if (!bluetoothAdapter.isEnabled()) {
        bluetoothAdapter.enable();
    }
開啟介面卡後,android將開始遍歷周圍可以被訪問的藍芽裝置。該動作將觸發廣播。

當然,也可以通過觸發bluetoothAdapter.startDiscovery()重新掃描。

選定目標藍芽裝置

由介面卡獲取裝置列表,並根據PC的bluetooth MAC地址,選定目標藍芽裝置。SERVICE_ADDRESS為PC端藍芽mac地址,格式為XX:XX:XX:XX:XX:XX

    Set<BluetoothDevice> mySet = bluetoothAdapter.getBondedDevices();
    for (BluetoothDevice device : mySet) {
        if (device.getAddress().equalsIgnoreCase(SERVICE_ADDRESS) ) {
            service = device;
            break;
        }
    }

建立RfcommSocket並建立連線

    private static final String serverUUID = "00001101-0000-1000-8000-00805F9B34FB"
    private BluetoothSocket bluetoothSocket;
    bluetoothSocket = service.createRfcommSocketToServiceRecord(UUID.fromString(serverUUID));
    bluetoothSocket.connect();
這裡需要說明的有兩點。
1.連線操作比較耗時,視具體裝置不同。所以,需要將連線操作放到子執行緒中完成。

2.在Android端,需要使用UUID,完成與其他藍芽裝置的連線。關於UUID,之後小結會提到。

收發資料

收發資料也較為耗時,需要放在子執行緒實現。

    OutputStream outputStream;
    try {
        outputStream = bluetoothSocket.getOutputStream();
        outputStream.write("A message from android device".getBytes());
        showMessage("Successfully send message");
    } catch (IOException e) {
        showMessage(e.getMessage()+", during output");
    }
    InputStream inputStream;
    try {
        inputStream = bluetoothSocket.getInputStream();
        byte[] buffer = new byte[200];
        inputStream.read(buffer);

        showMessage("Concurrently receive message : " + new String(buffer));

    } catch (IOException e) {
        showMessage(e.getMessage()+", during get input");
    }

關閉RfcommSocket

bluetoothSocket.close();

PC端

設定PC藍芽裝置可見

LocalDevice.getLocalDevice().setDiscoverable(DiscoveryAgent.GIAC);

建立連線流監聽器

private StreamConnectionNotifier streamConnectionNotifier;
streamConnectionNotifier = (StreamConnectionNotifier) Connector.open("btspp://localhost:" + SERVER_UUID.toString());
注意,此處使用的UUID,必須與android端的UUID一致。

開啟監聽

StreamConnection streamConnection = null;
streamConnection = streamConnectionNotifier.acceptAndOpen();
acceptAndOpen()方法呼叫後,將進入等待。

獲取輸入流和輸出流

while (isListening) {
	if ((inputStream.available()) <= 0) {
		Thread.sleep(1000);
	}
	System.out.println("message is comming");
                
    outputStream.write("hello android BT".getBytes());

    inputStream.read(buffer);
    String message = new String(buffer);
    System.out.println("Receive message : " + message);
                
    if (message.contains("EXIT_APP")) {
        System.out.println("Listener closed");
        isListening = false;
    }
}

關閉連線

inputStream.close();
outputStream.close();
streamConnection.close();

問題

BUG

java.io.IOException: read failed, socket might closed or timeout, read ret: -1
在連線時,常會遇到該BUG。網上很多方法說,可以通過修改UUID的方式,來FIX該BUG。

但翻看createRfcommSocketToServiceRecord方法的註釋,發現該UUID不能隨便修改。

     * <p>Hint: If you are connecting to a Bluetooth serial board then try
     * using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB.
     * However if you are connecting to an Android peer then please generate
     * your own unique UUID.

因此,若是採用串列埠通訊,必須使用“00001101-0000-1000-8000-00805F9B34FB”。

造成連線失敗的另一原因,可能是channel ID的問題。
在建立Socket時,createRfcommSocketToServiceRecord使用UUID作為唯一傳參,而預設channel為-1。

網上建議,通過反射使用BluetoothDevice的隱藏public createRfcommSocket方法,利用傳參指定的channel值,建立Socket。


channel的取值範圍為1至30.

Demo中給出了工具方法。

    public BluetoothSocket cretateBluetoothSocketbyChannel(BluetoothDevice Device,int channel,boolean autoForward){
        BluetoothSocket socket=null;
        try {
            showMessage("Trying fallback on channel "+channel);
            socket =(BluetoothSocket) Device.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(Device,channel);
            socket.connect();
            Log.d(TAG,"createRfcommSocket on channel "+channel);
            showMessage("Successfully connect");
        } catch (Exception e) {
            showMessage(e.getMessage());
            if(channel<30){
                if(autoForward){
                    socket=cretateBluetoothSocketbyChannel(Device,channel+1,autoForward);
                }else {
                    showMessage("Connect Failed");
                }
            }
        }
        return socket;
    }

另外,就該問題,吐槽一下介面卡。介面卡的效能也是參差不齊。此前摁著綠聯的藍芽介面卡,試了3天。同樣的程式碼,問題百出。後來轉用奧視通(ost108),幾分鐘便過了。不管是新增裝置時的認證過程的人性化設計,還是裝置服務驅動的安裝速度,天壤之別~

驅動

Demo在實現時,遇到找不到裝置的情況。個人感覺是由於介面卡所提供的驅動問題造成的。在解除安裝後,使用通用驅動可以解決該問題。


Bluecove版本

在64位OS下開發,需要使用Bluecove 64bit版本,本Demo使用為64bit。下載地址

參考文獻

在開發過程中,以下文章給予了很多幫助,一併列下。

https://blog.csdn.net/tingfengzheshuo/article/details/45292201

http://royal2xiaose.iteye.com/blog/1420138

https://blog.csdn.net/old_me_mory/article/details/18962701

https://blog.csdn.net/peceoqicka/article/details/51979469(著重感謝)

結語

第一次涉及藍芽專案,耗時較長,且深入不夠,若有疏漏,還望提出。