web-dev-qa-db-ja.com

BLEデバイスが範囲内にないことを検出する方法は?

私はLeScanCallbackを使用しています(API 18用に開発しているため、新しいスキャンメソッドを使用できません。Android 5.0+ APIもこの機能を提供していないため)近くのBLEデバイスが検出されたときに検出します。

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

    @Override
    public void onLeScan(BluetoothDevice bluetoothDevice, int i, byte[] bytes) {
        discoveredDevices.add(bluetoothDevice);
    }
};

必要がないため、デバイスをペアリングまたは接続していません。近くにあるデバイスを確認したいだけです。

5分ごとにWebサーバーを呼び出して、その時点で近くにあるデバイスを更新するサービスを作成しようとしています。

トリッキーな部分は、Androidデバイスが移動するため、現在近くにあるBluetoothデバイスが5分以内にない可能性があることです。その場合はdiscoveredDevices

理想的には、Bluetoothデバイスが以前は範囲内にあったが、現在は範囲内にないときにコールバックを受信したいと思います。ただし、このコールバックは存在しません。

(私はAndroid.bluetooth.device.action.ACL_CONNECTEDAndroid.bluetooth.device.action.ACL_DISCONNECTEDのブロードキャストを知っていますが、これらはBluetoothデバイスに接続するためのものであり、私は望んでいません。)

オプションとして、5分ごとに新しいスキャンを実行することもできますが、近くのすべてのデバイスがいつ検出されたかがわからないため、時間指定のスキャンを実行する必要があります。 5秒間スキャンしてから、収集したデータをWebサービスに送信します。
割り当てられた時間内にすべての近くのデバイスが発見されたことを確実に知ることはできないため、これは汚くて危険に聞こえます。そのようなことは避けたいと思います。

これを行う別の方法はありますか?


編集
一部のデバイスは、以前にすでに検出されていたとしても、近くのBluetoothデバイスの検出を継続的に報告します。その機能が普遍的であれば、問題を解決できますが、これはデバイス固有です。

たとえば、私の電話のBluetoothアダプターは、近くのデバイスを1回だけ検出します。私がテストした他のいくつかのデバイスは、同じ近くのデバイスを継続的に報告しますが、すべてのデバイスが報告するわけではないので、残念ながらそれを信頼することはできません。

22
Tim

割り当てられた時間内にすべての近くのデバイスが発見されたことを確実に知ることはできないため、これは汚くて危険に聞こえます、それで私はそれを避けたいと思いますそのように。

それは合理的な仮定のように聞こえますが、それは間違っています。

Bluetooth Low Energyは特定の方法で機能し、BLEデバイスにはいくつかの制限があります。たとえば、20ミリ秒から10.24秒の範囲で、0.625ミリ秒のステップで可能な広告頻度の固定範囲があります。詳細については、 ここ および ここ を参照してください。

これは、デバイスが新しいアドバタイズメントパッケージをブロードキャストするまでに最大10.24秒かかる可能性があることを意味します。 BLEデバイスは、常にではないにしても、一般的に、所有者が広告の頻度を調整する方法を提供するため、頻度はもちろん変化する可能性があります。

あなたのような近くのデバイスに関するデータを定期的に収集している場合は、一定の制限時間でスキャンを使用し、そのデータをどこかに保存し、スキャンを再開し、新しいデータを収集し、古いデータと比較する->結果を得るのは問題ありません。

たとえば、デバイスがスキャン1で検出されたが、スキャン2では検出されなかった場合、そのデバイスは範囲内にあったが、もう存在しないと結論付けることができます。
同じことが逆に当てはまります。スキャン4でデバイスが見つかったが、スキャン3では見つからなかった場合、それは新しく検出されたデバイスです。
最後に、デバイスがスキャン5で検出された場合、スキャン6では検出されなかったが、スキャン7でも検出された場合、デバイスは再検出され、必要に応じてそのように処理できます。


ここでは自分の質問に答えているので、これを実装するために使用したコードを追加します。

バックグラウンドサービスでスキャンを実行し、BroadcastReceiversを使用してアプリの他の部分と通信します。 Assetは、いくつかのデータを保持する私のカスタムクラスです。 DataManagerは私のカスタムクラスであり、データを管理します。

public class BLEDiscoveryService extends Service {

    // Broadcast identifiers.
    public static final String EVENT_NEW_ASSET = "EVENT_NEW_ASSET ";
    public static final String EVENT_LOST_ASSET = "EVENT_LOST_ASSET ";

    private static Handler handler;
    private static final int BLE_SCAN_TIMEOUT = 11000; // 11 seconds

    // Lists to keep track of current and previous detected devices.
    // Used to determine which are in range and which are not anymore.
    private List<Asset> previouslyDiscoveredAssets;
    private List<Asset> currentlyDiscoveredAssets;

    private BluetoothAdapter bluetoothAdapter;

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

        @Override
        public void onLeScan(BluetoothDevice bluetoothDevice, int i, byte[] bytes) {

            Asset asset = DataManager.getAssetForMACAddress(bluetoothDevice.getAddress());
            handleDiscoveredAsset(asset);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();

        BluetoothManager manager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
        bluetoothAdapter = manager.getAdapter();

        previouslyDiscoveredAssets = new ArrayList<>();
        currentlyDiscoveredAssets = new ArrayList<>();

        handler = new Handler();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // Start scanning.
        startBLEScan();

        // After a period of time, stop the current scan and start a new one.
        // This is used to detect when assets are not in range anymore.
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                performRepeatingTask();

                // Repeat.
                handler.postDelayed(this, BLE_SCAN_TIMEOUT);
            }
        }, BLE_SCAN_TIMEOUT);

        // Service is not restarted if it gets terminated.
        return Service.START_NOT_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        handler.removeCallbacksAndMessages(null);
        stopBLEScan();

        super.onDestroy();
    }

    private void startBLEScan() {
        bluetoothAdapter.startLeScan(BLECallback);
    }

    private void stopBLEScan() {
        bluetoothAdapter.stopLeScan(BLECallback);
    }

    private void handleDiscoveredAsset(Asset asset) {
        currentlyDiscoveredAssets.add(asset);

        // Notify observers that we have a new asset discovered, but only if it was not
        // discovered previously.
        if (currentlyDiscoveredAssets.contains(asset) &&
                !previouslyDiscoveredAssets.contains(asset)) {
            notifyObserversOfNewAsset(asset);
        }
    }

    private void performRepeatingTask() {
        // Check if a previously discovered asset is not discovered this scan round,
        // meaning it's not in range anymore.
        for (Asset asset : previouslyDiscoveredAssets) {
            if (!currentlyDiscoveredAssets.contains(asset)) {
                notifyObserversOfLostAsset(asset);
            }
        }

        // Update lists for a new round of scanning.
        previouslyDiscoveredAssets.clear();
        previouslyDiscoveredAssets.addAll(currentlyDiscoveredAssets);
        currentlyDiscoveredAssets.clear();

        // Reset the scan.
        stopBLEScan();
        startBLEScan();
    }

    private void notifyObserversOfNewAsset(Asset asset) {
        Intent intent = new Intent();
        intent.putExtra("macAddress", asset.MAC_address);
        intent.setAction(EVENT_NEW_ASSET);

        sendBroadcast(intent);
    }

    private void notifyObserversOfLostAsset(Asset asset) {
        Intent intent = new Intent();
        intent.putExtra("macAddress", asset.MAC_address);
        intent.setAction(EVENT_LOST_ASSET);      

        sendBroadcast(intent);
    }
}

このコードは完全ではなく、バグがあるかもしれませんが、少なくともこれを実装する方法のアイデアや例を提供します。

15
Tim

私はこのアプローチをお勧めできます:

_Map<BluetoothDevice, Long>_構造体を使用して、検出されたデバイスを格納します。ここで、Longはデバイスの検出時刻です(たとえば、System.currentTimeMillis()など)。

次に、サービスで(質問から理解できる限り、ある種の繰り返しタスクが実装されます)、検出された時間に基づいて実際のデバイスを抽出するだけです。

そして、あなたは絶対に正しいです、すべての近くのデバイスが割り当てられた時間内に発見されたという保証はありません。特にこれはAndroidデバイスの場合です。iOSデバイスには別の問題があります。明らかな外部原因なしに実行時にBluetoothDeviceのアドレスを変更できます。これにより、時間を節約できることを願っています。デバッグ。


編集

このトピックの調査の結果、このディスカッションは code.google.com で見つかりました。

問題はまだ未解決であり、ハードウェア機能に関連しているようで、プログラムで修正することはできません。また、システムアップデート後も問題のあるデバイスにバグが残っているようです。したがって、スキャンを定期的に再開することは、この場合の回避策として受け入れられる可能性があります。

2
DmitryArc