1. 程式人生 > >Android(Java)開發之獲取BLE廣播包(掃描後獲取:廣播資料+掃描應答資料+RSSI)

Android(Java)開發之獲取BLE廣播包(掃描後獲取:廣播資料+掃描應答資料+RSSI)

一、安卓BLE的廣播包資料從哪獲取?

通常,安卓APP讀寫BLE裝置的資料都是建立連線後通過GATT獲取或修改。但是,BLE裝置向外廣播時本身會攜帶一部分有用資訊,如將感測資料存放到廣播包的自定義資料段,最近接觸的一個iBeacon/EddyStone整合專案便是類似,因此為了提取廣播包進行解析,首要問題就是安卓APP如何獲取廣播資料。

      其實,安卓藍芽在掃描裝置後,回撥方法 onLeScan(...)中的引數 scanRecord 就是廣播資料,這裡同時包含廣播資料掃描應答資料(均為31位元組),所以長度一般就是 62 位元組,BLE4.0規定,如果廣播包和掃描應答包不足位元組,則以0補齊。如下:

 private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {

        @Override
        public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
                 ...//scanRecord存放廣播和掃描應答資料,rssi存放掃描獲取的rssi值
        }
    };


二、如何儲存廣播包資料並解析?

1.目標:掃描的裝置顯示List中除了顯示裝置名、Mac地址外,還加入完整的scanRecord資料包顯示和rssi的顯示。

2.步驟:(基於官方BLE Demo)

(1)在ListView的介面卡建立中加入RSSI和Record列表

 //ListView Adapter,用於在listview裡管理掃描到的裝置
    private class LeDeviceListAdapter extends BaseAdapter {
        private ArrayList<BluetoothDevice> mLeDevices;
        private ArrayList<Integer> mRSSIs;//新加
        private ArrayList<byte[]> mRecords;//新加
        private LayoutInflater mInflator;

        public LeDeviceListAdapter() {
            super();

            mLeDevices = new ArrayList<BluetoothDevice>();
            mRSSIs = new ArrayList<Integer>();//新加
            mRecords = new ArrayList<byte[]>();//新加

            mInflator = DeviceScanActivity.this.getLayoutInflater();
        }
        ...
}

(2)改寫LeDeviceListAdapter介面卡中的addDevice方法
        public void addDevice(BluetoothDevice device,int rssi,byte[] scanRecord) {
            if(!mLeDevices.contains(device)) {
                mLeDevices.add(device);
                mRSSIs.add(rssi);//新加
                mRecords.add(scanRecord);//新加
            }
        }
(3)BluetoothAdapter.LeScanCallback回撥方法onLeScan()中呼叫新的addDevice方法
    // Device scan callback.
    private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {

        @Override
        public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {

            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mLeDeviceListAdapter.addDevice(device,rssi,scanRecord);
                    mLeDeviceListAdapter.notifyDataSetChanged();

                }
            });
        }
    }
(4)ViewHolder和XML佈局中加入對應的TextView,並改寫LeDeviceListAdapter介面卡中的getView()方法:用於支援scanRecord和rssi的列表化顯示
    static class ViewHolder {
        TextView deviceName;
        TextView deviceAddress;
        TextView deviceBroadcastPack;//加入廣播包資料
        TextView deviceRssi;//加入RSSI
    }

       @Override
        public View getView(int i, View view, ViewGroup viewGroup) {
            ViewHolder viewHolder;
            // General ListView optimization code.
            if (view == null) {
                view = mInflator.inflate(R.layout.listitem_device, null);
                viewHolder = new ViewHolder();
                viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address);
                viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name);
                viewHolder.deviceBroadcastPack = (TextView) view.findViewById(R.id.device_broadcastPack);
                viewHolder.deviceRssi = (TextView) view.findViewById(R.id.device_rssi);
                
                view.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) view.getTag();
            }
            //獲取裝置列表、RSSI列表和廣播包列表專案
            BluetoothDevice device = mLeDevices.get(i);
            int rssi = mRSSIs.get(i);
            byte[] scanRecord = mRecords.get(i);
  
            //提取資訊
            final String deviceName = "裝置名:" + device.getName();
            final String deviceAddr = "Mac地址:" + device.getAddress();
            final String broadcastPack ="廣播包:" + bytesToHex(scanRecord);//此處呼叫了格式轉換方法bytesToHex()將十六進位制序列轉String
            final String rssiString = "RSSI:" + String.valueOf(rssi) + "dB";//此處呼叫了String的格式轉換方法valueOf()將數值型別轉String
         
            //顯示資料
            if (deviceName != null && deviceName.length() > 0)//顯示裝置名
                viewHolder.deviceName.setText(deviceName);
            else
                viewHolder.deviceName.setText(R.string.unknown_device);
            viewHolder.deviceAddress.setText(deviceAddr);//顯示裝置地址
            viewHolder.deviceBroadcastPack.setText(broadcastPack);//顯示廣播包
            viewHolder.deviceRssi.setText(rssiString);//顯示RSSI
           
            return view;
        }

(5)實現scanRecord的格式轉換方法:bytesToHex()---廣播和掃描應答資料以十六進位制格式儲存在bytes[]陣列中,需要轉換為String後顯示
    //scanRecords的格式轉換
    static final char[] hexArray = "0123456789ABCDEF".toCharArray();
    private static String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

三、效果展示

實現的基本效果如圖,掃描到的裝置列表加入了RSSI和完整廣播包的顯示


後注:獲取到scanRecord後就能根據協議解析廣播包了,根據需求提取對應欄位。scanRecord的資料必須進行格式轉換,否則列表顯示會出現亂碼,格式不匹配!