如何检测 BLE 设备何时不在范围内? [英] How to detect when a BLE device is not in range anymore?

查看:21
本文介绍了如何检测 BLE 设备何时不在范围内?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用 LeScanCallback(无法使用较新的扫描方法,因为我正在为 api 18 开发.没关系,因为 android 5.0+ apis 也不提供此功能)来检测附近的 BLE 设备检测到:

I use a LeScanCallback (can not use the newer scan methods because I'm developing for api 18. Not that it matters, since the android 5.0+ apis don't offer this functionality either) to detect when a nearby BLE device is detected:

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

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

我没有与设备配对或连接,因为这不是必需的,我只是想看看附近有哪些设备.

I am not pairing or connecting with the devices because that's not required, I simply want to see which devices are nearby.

我正在尝试提供一种服务,每隔 5 分钟左右,就会调用网络服务器来更新当时附近的设备.

I'm trying to make a service that, every 5 mins or so, calls a webserver to update which devices are nearby at that moment.

棘手的部分是安卓设备会移动,所以现在附近的蓝牙设备可能不会在 5 分钟内出现.在这种情况下,我需要从 discoveredDevices 中删除它.

Tricky part is that the android device will be moving, so a bluetooth device that is nearby right now, might not be in 5 mins. In that case I need to remove it from discoveredDevices.

理想情况下,我希望在蓝牙设备之前在范围内时收到回调,但现在不在范围内.不过这个回调不存在.

Ideally, I would like to receive a callback when a bluetooth device was in range before, but is not anymore. This callback doesn't exist though.

(我知道 android.bluetooth.device.action.ACL_CONNECTEDandroid.bluetooth.device.action.ACL_DISCONNECTED 广播,但这些是用于何时您连接到我不想要的蓝牙设备.)

(I'm aware of the android.bluetooth.device.action.ACL_CONNECTED and android.bluetooth.device.action.ACL_DISCONNECTED broadcasts, but those are for when you connect to a bluetooth device, which I don't want.)

一种选择是每 5 分钟进行一次新扫描,但您无法判断附近所有设备何时被发现,因此您必须进行定时扫描,例如扫描 5 秒,然后将收集到的数据发送到网络服务.
这听起来既肮脏又冒险,因为您永远无法确定在规定的时间内发现了附近的所有设备,因此我非常希望避免这样做.

An option is to do a fresh scan every 5 mins, but you can't tell when all nearby devices have been discovered, so you would have to do a timed scan, e.g. scan for 5 seconds and then send the collected data to the webservice.
This sounds dirty and risky because you can never know for sure all nearby devices were discovered within the allotted time, so I would very much like to avoid doing it like that.

还有其他方法可以做到这一点吗?

Is there another way to do this?

编辑
一些设备不断报告发现附近的蓝牙设备,即使它们之前已经被发现.如果该功能是通用的,我可以解决我的问题,但这是特定于设备的.

Edit
Some devices continuously report discovery of nearby bluetooth devices, even if they were already discovered before. If that functionality was universal I could solve my problem, however this is device specific.

例如,我手机的蓝牙适配器只能发现附近的设备一次.我测试过的其他一些设备确实会持续报告附近相同的设备,但并非所有设备都会报告,因此很遗憾我不能依赖这一点.

My phone's bluetooth adapter for example only discovers nearby devices once. Some other devices I have tested with do continuously report the same nearby devices, but not all devices do, so I can't rely on that unfortunately.

推荐答案

这听起来既肮脏又冒险,因为您永远无法确定在规定的时间内发现了附近的所有设备,因此我非常希望避免这样做.

This sounds dirty and risky because you can never know for sure all nearby devices were discovered within the allotted time, so I would very much like to avoid doing it like that.

这听起来像是一个合理的假设,但它是错误的.

That sounds like a reasonable assumption, but it's wrong.

低功耗蓝牙以特定方式工作,BLE 设备有一些限制.例如,它们具有固定范围的可能广告频率,范围从 20 毫秒到 10.24 秒,步长为 0.625 毫秒.请参阅此处此处 了解更多详细信息.

Bluetooth low energy works in a particular way and BLE devices have some limits. For instance, they have a fixed range of possible advertising frequencies, ranging from 20 milliseconds to 10.24 seconds, in steps of 0.625 milliseconds. See here and here for more detailed information.

这意味着在设备播放新的广告包之前最多 10.24 秒.BLE 设备通常(如果并非总是如此)为其所有者提供一种调整其广告频率的方式,因此频率当然可以变化.

This means that it can take at most 10.24 seconds before a device will broadcast a new advertisement package. BLE devices generally, if not always, provide a way for their owner to adjust their advertising frequency, so the frequency can of course vary.

如果您定期收集有关附近设备的数据(例如您的设备),可以使用具有固定时间限制的扫描,将数据保存在某处,重新开始扫描,收集新数据,与旧数据进行比较——> 得到结果.

In cases where you are periodically collecting data about nearby devices, like yours, it is fine to use a scan with a fixed time limit, save that data somewhere, restart the scan, collect new data, compare with old data --> get results.

例如,如果在扫描 1 中找到了设备,但在扫描 2 中未找到,您可以得出结论,该设备在范围内,但不再存在.
反之亦然:如果在扫描 4 中找到设备但在扫描 3 中未找到,则它是新发现的设备.
最后,如果某个设备在第 5 次扫描中找到,但在第 6 次扫描中未找到,但在第 7 次扫描中再次找到,则它会被重新发现,并且可以根据需要进行处理.

For example, if a device was found in scan 1 but not in scan 2, you can conclude that the device was in range, but is not anymore.
Same goes for the other way around: if a device was found in scan 4 but not in scan 3, it is a newly discovered device.
Finally, if a device was found in scan 5, was not found in scan 6, but was again found in scan 7, it is rediscovered and can be handled as such if need be.

因为我在这里回答我自己的问题,所以我将添加我用来实现它的代码.

Because I'm answering my own question here, I'll add the code that I used to implement this.

我在后台服务中完成了扫描,并使用 BroadcastReceivers 与应用程序的其他部分进行通信.Asset 是我的一个自定义类,用于保存一些数据.DataManager 是我的一个自定义类 - 你怎么猜到的 - 管理数据.

I have the scanning done in a background service, and communicate to other parts of the app using BroadcastReceivers. Asset is a custom class of mine that holds some data. DataManager is a custom class of mine that - how did you guess it - manages data.

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);
    }
}

这段代码并不完美,甚至可能有问题,但它至少可以为您提供一个如何实现的想法或示例.

This code is not perfect and might even be buggy, but it will at least give you an idea or example of how this can be implemented.

这篇关于如何检测 BLE 设备何时不在范围内?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆