1. 程式人生 > >JavaSE + bluecove 藍芽連線

JavaSE + bluecove 藍芽連線

最近公司準備將舊系統的.NET部分翻版,專案除了有後臺的還有個與裝置對接的客戶端用藍芽連線的,所有這周對相關技術做了一個驗證。

搜了一下Java 藍芽相關資訊,我去資料也太少了,少也就算了連bluecove庫也是有問題的。經過艱難的查詢,最終還是調通了。因為整個過程都是靠大家的資料去解決的,所以本著造福後人的角度我把我的經驗分享一下,讓後人不用想我一樣滿世界去找。

BlueCove框架

首先是Java SE雖然支援藍芽但是支援的並不是很好,反而在Java ME 支援裝置方面較好,但是我桌面平臺也要啊,所有就有框架BlueCove了。BlueCove的API我用了感覺風格整體都和Java ME差不多,所以當你有比較複雜的需求又找不到文件的時候可以參考一下Java ME的資料

BlueCove框架最大的問題就是比較老了, 2.1.1-SNAPSHOT.63 文件最後更新是2010年,提供的jar包居然不支援64位系統!還好有解決方案的,不然我就不用開發了。這裡首先參考這篇部落格 Eclipse + Java + BlueCove + WIN/MAC 藍芽開發 這篇對解決方法的介紹的比較詳細,但是並沒有幫我直接解決問題,因為我順著連結去下載的jar還是不支援64位系統(也可能是我下錯了包了)。不過我知道了是可以解決的,於是各種百度和谷歌終於找到了能在64位系統執行的jar包

64位系無法執行會提示:

Native Library intelbth_x64 not available
Native Library bluecove_x64 not available

官網直接下載的2.1.0版本不支援64位,下面這個支援的版本看路徑也是官方的,竟然不提供!虧我找了不知道多久。
64位支援版本jar包下載地址:

BlueCove還需要Apache的commons-io包,這個順便下就可以的。

測試環境:
系版本
藍芽是與網絡卡一體的 bcm94352

藍芽連線:作為服務端

這三篇都是將PC作為一個服務端,讓手機主動連線。

//本機藍芽裝置
private LocalDevice local = null;
// 流連線
private StreamConnection streamConnection = null;
// 接受資料的位元組流
private byte[] acceptdByteArray = new byte[1024]; // 輸入流 private DataInputStream inputStream; //接入通知 private StreamConnectionNotifier notifier; //執行緒池 private final static ExecutorService service = Executors.newCachedThreadPool(); public BuletoothService() { try { //這兩步不一定要 BluCatUtil.doctorDevice(); // 驅動檢查 RemoteDeviceDiscovery.runDiscovery(); // 搜尋附近所有的藍芽裝置 System.out.println(RemoteDeviceDiscovery.getDevices()); } catch (IOException | InterruptedException e1) { e1.printStackTrace(); } try { local = LocalDevice.getLocalDevice(); if (!local.setDiscoverable(DiscoveryAgent.GIAC)) System.out.println("請將藍芽設定為可被發現"); /** * 作為服務端,被請求 */ String url = "btspp://localhost:" + new UUID(80087355).toString() + ";name=RemoteBluetooth"; notifier = (StreamConnectionNotifier)Connector.open(url); } catch (IOException e) { e.printStackTrace(); } service.submit(this); }

上面的其實就一個Connector.open(url)是重點,service.submit(this); 獲取資料在新執行緒中。
為了能夠手動停止執行緒,做了一些處理。

@Override
    public void run() {
        try {
            String inStr = null;
            streamConnection = notifier.acceptAndOpen();                //阻塞的,等待裝置連線
            inputStream = streamConnection.openDataInputStream();
            int length;
            while (true) {
                if ((inputStream.available()) <= 0) {                   //不阻塞執行緒
                    if (stopFlag)                                       //UI停止後,關閉
                        break;
                    Thread.sleep(800);                                  //資料間隔比較長,手動堵塞執行緒
                } else {
                    length = inputStream.read(acceptdByteArray);
                    if(length>0) {
                        inStr = new String(acceptdByteArray,0,length);
                        System.out.println(inStr);
                    }

                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null)
                    inputStream.close();
                if (streamConnection != null)
                    streamConnection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

核心就是streamConnection = notifier.acceptAndOpen();獲得連線這裡會堵塞執行緒(沒有作停止處理!!!)。有連線後獲得輸入流inputStream = streamConnection.openDataInputStream();接下來就是你自己發揮了。

藍芽連線:作為客戶端

網上關於這個的是真的幾乎沒有!因為需要一個服務端,現在我手頭客戶端是挺多的就是缺個服務端。公司的電腦都是沒藍芽的,過幾天問同事借太筆記本再測試!所以下面的程式碼我也只是提供一個思路,具體你可以測試來告訴我。或者我測試通過再告訴你們。整體都是差不多的,先連線服務端,再獲取輸入流/輸出流。這裡寫了一個工具類先查詢了附近的藍芽裝置(詳見本文藍芽發現小節),因為需要 BluetoothAddress ,如果這個是已知的可以直接寫。

Set<RemoteDevice> devicesDiscovered = RemoteDeviceDiscovery.getDevices();       //附近所有的藍芽裝置,必須先執行 runDiscovery
            if (devicesDiscovered.iterator().hasNext()) {                                   //連線
                RemoteDevice first = devicesDiscovered.iterator().next();
                streamConnection = (StreamConnection) Connector.open("btspp://" + first.getBluetoothAddress() + ":1");
            }
  1. Set<RemoteDevice> devicesDiscovered = RemoteDeviceDiscovery.getDevices()附近可用的藍芽連線
  2. streamConnection = (StreamConnection) Connector.open("btspp://" + first.getBluetoothAddress() + ":1"); 直接連線
  3. url地址 :btspp://<藍芽裝置地址>:<通道號>
  4. 有連線後獲得輸入流inputStream = streamConnection.openDataInputStream();一樣了

有個簡單參考部落格:http://blog.sina.com.cn/s/blog_a861feb40102vppo.html
裡面沒有太多的內容,流程比較清晰。通道號我用pc連線Anrdoid手機的時候,好像不同的通道有不同的功能,有的提示要讀取音訊,有的提示要讀取電話和聯絡人等。可能這個通道在PC上功能又是不一樣的。

2017.8.22 PC讀取藍芽連線的電子秤資料驗證通過,客戶端可行。

藍芽裝置發現

搜尋周邊可用的藍芽是比較方便的。

public final static Set<RemoteDevice> devicesDiscovered = new HashSet<RemoteDevice>();

private static void findDevices() throws IOException, InterruptedException {

    final Object inquiryCompletedEvent = new Object();

    devicesDiscovered.clear();

    DiscoveryListener listener = new DiscoveryListener() {
        public void inquiryCompleted(int discType) {
            System.out.println("#" + "搜尋完成");
            synchronized (inquiryCompletedEvent) {
                inquiryCompletedEvent.notifyAll();
            }
        }

        @Override
        public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass deviceClass) {
            devicesDiscovered.add(remoteDevice);

            try {
                System.out.println("#發現裝置" + remoteDevice.getFriendlyName(false));
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        @Override
        public void servicesDiscovered(int arg0, ServiceRecord[] arg1) {
            System.out.println("#" + "servicesDiscovered");
        }

        @Override
        public void serviceSearchCompleted(int arg0, int arg1) {
            System.out.println("#" + "serviceSearchCompleted");
        }
    };

    synchronized (inquiryCompletedEvent) {

        LocalDevice ld = LocalDevice.getLocalDevice();

        System.out.println("#本機藍芽名稱:" + ld.getFriendlyName());

        boolean started = LocalDevice.getLocalDevice().getDiscoveryAgent().startInquiry(DiscoveryAgent.GIAC,listener);

        if (started) {
            System.out.println("#" + "等待搜尋完成...");
            inquiryCompletedEvent.wait();
                LocalDevice.getLocalDevice().getDiscoveryAgent().cancelInquiry(listener);
            System.out.println("#發現裝置數量:" + devicesDiscovered.size());
        }
    }
}

可以參考Java ME的流程,核心是:
LocalDevice.getLocalDevice().getDiscoveryAgent().startInquiry(DiscoveryAgent.GIAC,listener); (裝置可被發現,搜尋監聽器),監聽器在不同的過程節點提供了回撥方法,裝置發現的時候新增進集合中:

@Override
public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass deviceClass) {
    devicesDiscovered.add(remoteDevice);

    try {
        System.out.println("#發現裝置" + remoteDevice.getFriendlyName(false));
    } catch (IOException e) {
        e.printStackTrace();
    }           
}

擴充套件參考

感謝

Demo下載

沒有程式碼總是很難解決一些問題,