9

我有 2 部 Android 5.0.2 的手机,它们都安装了最新的 Radius Beacon 的应用程序:Locate Beacon,同时,我打开了 2 个 IBeacon 发送器,并且可以看到两部手机的 RSSI不断变化

但是当我尝试编写一些示例代码来模拟上述情况时,我发现 ble scan 回调总是在调用 2 或 3 次后停止被调用,我最初怀疑 'Locate Beacon' 可能使用不同的方式,所以我尝试了 2 种API,一个是旧的4.4,另一个是android 5中引入的新方式,但两者的行为相同(但都在android 5上运行)。

4.4 一:

public class MainActivity extends Activity {
private BluetoothAdapter mBluetoothAdapter;
private static final String LOG_TAG = "BleCollector";
private TextView calledTimesTextView = null;
private int calledTimes = 0;
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi,
            byte[] scanRecord) {
        calledTimes++;
        runOnUiThread(new Runnable() {
            @Override
            public void run() {

                calledTimesTextView.setText(Integer.toString(calledTimes));
            }
        });
        Log.e(LOG_TAG, "in onScanResult, " + " is coming...");
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    calledTimesTextView = (TextView) findViewById(R.id.CalledTimes);
    mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
            .getAdapter();
    mBluetoothAdapter.startLeScan(mLeScanCallback);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();
    if (id == R.id.action_settings) {
        return true;
    }
    return super.onOptionsItemSelected(item);
}}

和 5.0.2:

public class MainActivity extends Activity {
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothLeScanner mLescanner;
private ScanCallback mLeScanCallback;
private static final String LOG_TAG = "BleFingerprintCollector";
private TextView calledTimesTextView = null;
private int calledTimes = 0;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    calledTimesTextView = (TextView) findViewById(R.id.CalledTimes);
    this.mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
            .getAdapter();
    this.mLescanner = this.mBluetoothAdapter.getBluetoothLeScanner();

    ScanSettings bleScanSettings = new ScanSettings.Builder().setScanMode(
            ScanSettings.SCAN_MODE_LOW_LATENCY).build();

    this.mLeScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            calledTimes++;
            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                    calledTimesTextView.setText(Integer
                            .toString(calledTimes));
                }
            });
            Log.e(LOG_TAG, "in onScanResult, " + " is coming...");
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {

        }

        @Override
        public void onScanFailed(int errorCode) {
        }
    };
    this.mLescanner.startScan(null, bleScanSettings, this.mLeScanCallback);

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();
    if (id == R.id.action_settings) {
        return true;
    }
    return super.onOptionsItemSelected(item);
}}

它们非常简单,只是在 UI 中显示一个计数器,最终证明总是停在 2 或 3。

我之前在带有 android 4.4 设备的三星 note 2 上播放过这个 ble 广告接收,它运行良好,每秒调用一次回调。那么有人可以帮忙吗?为什么 Radius 的 Locate Beacon 在这里工作得很好

4

2 回答 2

20

不同的 Android 设备在扫描可连接的BLE 广告时表现不同。 在某些设备(例如 Nexus 4)上,扫描 API 每次扫描仅针对发送可连接广告的发射机获得一个回调,而对于不可连接广告的每个广告,它们都会获得一个扫描回调。其他设备(例如 Nexus 5)在每个广告都提供扫描回调,无论它是否可连接。

您提到的Locate 应用程序使用开源Android Beacon Library来检测信标。它建立在您在问题中显示的相同扫描 API 之上,但它通过定义扫描周期(在前台默认为 1.1 秒)并在此间隔停止和重新启动扫描来解决此问题。 停止并重新开始扫描会导致 Android 发送新的回调。

这里还有一些其他注意事项:

  • 为可连接设备获取多个扫描回调的问题适用于 4.x 和 5.x 扫描 API。

  • 目前尚不清楚在不同设备上为可连接广告提供扫描回调的差异是由于 Android 固件差异还是蓝牙硬件芯片组差异。

  • 似乎没有办法检测设备是否需要重新启动扫描以获取可连接广告的额外回调,因此如果您的目标是多种设备,则需要计划停止并重新启动扫描。

  • 使用 Android 的原始扫描 API 是了解 BLE 信标如何工作的好方法。但是使用 BLE 信标有很多复杂性(这只是一个示例),这就是为什么使用像 Android 信标库这样的 SDK 是一个不错的选择,可以让您免于烦恼。

全面披露:我是Android Beacon Library开源项目的首席开发人员中Locate 应用程序的作者。

于 2015-04-19T15:15:34.847 回答
-1

大卫 - 你确定每个不可连接的广告都会调用扫描回调。我有一部小米 Redmi 3 和另一部运行 Android 6.0 的 Nexus 5 手机。我有一个 BLE 传感器,每隔 1 分钟发送一次数据。这些作为中央 BLE 设备出现的手机应该接收和处理来自传感器的数据。我可以从无线 (OTA) BLE 捕获设备中看到,传感器每 1 分钟发送一次数据。然而,两部手机似乎都以 1 分钟的间隔处理数据几分钟,但之后停止处理 4 - 6 分钟,然后再次开始处理enter code here。电话处理的时间间隔看起来像这样 1 分钟,2 分钟,3 分钟,8 分钟,9 分钟,10 分钟,11 分钟 因此,在以 1 分钟的间隔处理 3 个数据包后,任一电话将停止处理 4 -6 分钟。

这是进行处理的代码。

public class BluetoothDataReader {
    private final Context context;

    public BluetoothDataReader(Context context) {
        this.context = context;
    }

    public void startReading() {
        BluetoothAdapter btAdapter = getBluetoothAdapter();
        if (btAdapter == null) return;

        BluetoothLeScanner scanner = btAdapter.getBluetoothLeScanner();
        ScanSettings settings = new ScanSettings.Builder()
                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                .build();
        scanner.startScan(Collections.<ScanFilter>emptyList(), settings, new ScanRecordReader());
    }

    public void uploadScanBytes(SensorDataUploader sensorDataUploader, int count) {
        BluetoothAdapter btAdapter = getBluetoothAdapter();
        if (btAdapter == null) return;

        BluetoothLeScanner scanner = btAdapter.getBluetoothLeScanner();
        ScanSettings settings = new ScanSettings.Builder()
                .setScanMode(ScanSettings.SCAN_MODE_BALANCED)
                .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
                .build();
 //       scanner.startScan(Arrays.asList(new ScanFilter.Builder().setDeviceAddress("26:50:26:50:26:50").build()), settings, new LimitedScanRecordReader(sensorDataUploader, count, scanner));
           scanner.startScan(Collections.<ScanFilter>emptyList(), settings, new LimitedScanRecordReader(sensorDataUploader, count, scanner));
    }

    @Nullable
    private BluetoothAdapter getBluetoothAdapter() {
        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
        if(btAdapter == null){
            Log.i(BluetoothDataReader.class.getName(), "No bluetooth adapter available");
            return null;
        }

        if(!btAdapter.isEnabled()){
            Log.i(BluetoothDataReader.class.getName(), "Enable bluetooth adapter");
            Intent enableBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            context.startActivity(enableBluetooth);
        }
        return btAdapter;
    }

    private class LimitedScanRecordReader extends ScanCallback {
        private final int limit;
        private final BluetoothLeScanner scanner;


        private int scanRecordRead = 0;
        private final SensorDataUploader sensorDataUploader;

        private LimitedScanRecordReader( SensorDataUploader sensorDataUploader, int limit, BluetoothLeScanner scanner) {
            this.limit = limit;
            this.scanner = scanner;
            this.sensorDataUploader = sensorDataUploader;
        }

        @Override
        public void onScanResult(int callbackType, ScanResult result) {
//            if(scanRecordRead++ < limit) {
   //         if(result.getDevice().getAddress().equals("A0:E6:F8:01:02:03")) {
   //         if(result.getDevice().getAddress().equals("C0:97:27:2B:74:D5")) {

            if(result.getDevice().getAddress().equals("A0:E6:F8:01:02:03")) {
                long timestamp = System.currentTimeMillis() -
                        SystemClock.elapsedRealtime() +
                        result.getTimestampNanos() / 1000000;



                byte[] rawBytes = result.getScanRecord().getBytes();
                Log.i(DataTransferService.class.getName(), "Raw bytes: " + byteArrayToHex(rawBytes));
                sensorDataUploader.upload(timestamp, rawBytes);
            }
//            }else {
//                scanner.stopScan(this);
//            }
        }
        public String byteArrayToHex(byte[] a) {
            StringBuilder sb = new StringBuilder(a.length * 2);
            for(byte b: a)
                sb.append(String.format("%02x", b & 0xff));
            return sb.toString();
        }

        public void onScanFailed(int errorCode) {
            Log.i(DataTransferService.class.getName(), "Error code is:" + errorCode);
        }

        public void onBatchScanResults(java.util.List<android.bluetooth.le.ScanResult> results) {
            Log.i(DataTransferService.class.getName(), "Batch scan results");
        }
    }

    private class ScanRecordReader extends ScanCallback {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            byte []rawBytes = result.getScanRecord().getBytes();
            Log.i(DataTransferService.class.getName(), "Raw bytes: " + byteArrayToHex(rawBytes ));
//            Map<ParcelUuid, byte[]> serviceData = result.getScanRecord().getServiceData();
//            for(ParcelUuid uuid : serviceData.keySet()) {
//                Log.i(DataTransferService.class.getName(), uuid.toString() + ":" +  byteArrayToHex(serviceData.get(uuid)));
//            }
//            Log.i(DataTransferService.class.getName(),result.toString());
        }
        public String byteArrayToHex(byte[] a) {
            StringBuilder sb = new StringBuilder(a.length * 2);
            for(byte b: a)
                sb.append(String.format("%02x", b & 0xff));
            return sb.toString();
        }

        public void onScanFailed(int errorCode) {
            Log.i(DataTransferService.class.getName(), "Error code is:" + errorCode);
        }

        public void onBatchScanResults(java.util.List<android.bluetooth.le.ScanResult> results) {
            Log.i(DataTransferService.class.getName(), "Batch scan results");
        }
    }
}
于 2017-01-23T07:38:33.663 回答