Подтвердить что ты не робот

Как определить, когда устройство BLE больше не находится в зоне действия?

Я использую LeScanCallback (не могу использовать более новые методы сканирования, потому что я разрабатываю для api 18. Не то чтобы это важно, так как apis 5.0 + apis также не предлагает эту функцию), чтобы обнаружить, когда соседнее устройство BLE:

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

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

Я не подключаюсь и не подключаюсь к устройствам, потому что это не требуется, я просто хочу посмотреть, какие устройства находятся поблизости.

Я пытаюсь сделать службу, которая каждые 5 минут вызывает веб-сервер, чтобы обновлять, какие устройства находятся поблизости в этот момент.

Трудная часть заключается в том, что устройство Android будет двигаться, поэтому устройство Bluetooth, находящееся поблизости прямо сейчас, может не быть в течение 5 минут. В этом случае мне нужно удалить его из discoveredDevices.

В идеале, я хотел бы получить обратный вызов, когда устройство Bluetooth находилось в диапазоне раньше, но больше не существует. Этот обратный вызов не существует, хотя.

(Я знаю трансляции android.bluetooth.device.action.ACL_CONNECTED и android.bluetooth.device.action.ACL_DISCONNECTED, но это касается того, когда вы подключаетесь к Bluetooth-устройству, чего я не хочу.)

Опция состоит в том, чтобы делать новое сканирование каждые 5 минут, но вы не можете сказать, когда все обнаруженные поблизости устройства были обнаружены, поэтому вам нужно будет выполнить временное сканирование, например. сканировать в течение 5 секунд, а затем отправить собранные данные в веб-службу.
Это звучит грязно и рискованно, потому что вы не можете точно знать, что все находящиеся поблизости устройства были обнаружены в течение отведенного времени, поэтому я очень хотел бы избежать этого.

Есть ли другой способ сделать это?


Edit
Некоторые устройства постоянно сообщают об обнаружении близлежащих устройств Bluetooth, даже если они уже были обнаружены ранее. Если бы эта функциональность была универсальной, я мог бы решить мою проблему, однако это специфично для устройства.

Мой адаптер Bluetooth для телефона, например, только один раз обнаруживает соседние устройства. Некоторые другие устройства, с которыми я тестировал, постоянно сообщают о тех же ближайших устройствах, но не все устройства, поэтому я не могу полагаться на это, к сожалению.

4b9b3361

Ответ 1

Это звучит грязно и рискованно, потому что вы не можете точно знать, что все находящиеся поблизости устройства были обнаружены в течение выделенного времени, поэтому я очень хотел бы избежать этого.

Это звучит как разумное предположение, но это неправильно.

Низкая энергия Bluetooth работает определенным образом, а устройства 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 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);
    }
}

Этот код не идеален и может быть даже ошибкой, но он по крайней мере даст вам представление или пример того, как это можно реализовать.

Ответ 2

Я могу рекомендовать этот подход:

Используйте структуру Map<BluetoothDevice, Long> для хранения обнаруженных устройств, где Long - время обнаружения устройства (например, может быть System.currentTimeMillis()).

Тогда в вашей службе (насколько я понимаю из вопроса будет реализована какая-то повторяющаяся задача), просто извлекайте фактические устройства в зависимости от времени их обнаружения.

И вы абсолютно правы, нет гарантии, что все ближайшие устройства были обнаружены в течение отведенного времени. Особенно это актуально для устройств Android. У устройств iOS в этом есть еще одна проблема - они могут изменить свой адрес BluetoothDevice во время выполнения без видимой внешней причины. Надеюсь, это поможет вам сэкономить время во время отладки.


Изменить

В результате исследования этой темы было найдено это обсуждение code.google.com

Проблема все еще открыта и кажется, что она связана с аппаратными функциями и не может быть исправлена ​​программно. Более того, похоже, что ошибка будет оставаться на проблемных устройствах даже после обновления системы. Поэтому повторное сканирование периодически может быть приемлемым для этого случая.