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

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

问题描述

我使用LeScanCallback(不能使用较新的扫描方法,因为我正在开发api18.没关系,因为android 5.0+ api也不提供此功能)来检测附近的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.

棘手的部分是android设备将要移动,因此当前附近的蓝牙设备可能不在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秒钟,然后将收集的数据发送到Web服务.
这听起来很脏而且冒险,因为您永远无法确定是否在指定的时间内发现了所有附近的设备,因此,我非常想避免那样做.

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.

还有另一种方法吗?

修改
某些设备会连续报告发现附近的蓝牙设备,即使以前已发现它们也是如此.如果该功能具有通用性,那么我可以解决我的问题,但这是特定于设备的.

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天全站免登陆