tengo - detectar dispositivos bluetooth android studio
¿Cómo detectar cuándo un dispositivo BLE ya no está dentro del alcance? (2)
Uso un LeScanCallback (no puedo usar los métodos de exploración más nuevos porque estoy desarrollando para la API 18. No es que importe, ya que las apis de Android 5.0+ tampoco ofrecen esta funcionalidad) para detectar cuándo se detecta un dispositivo BLE cercano:
private BluetoothAdapter.LeScanCallback bleCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice bluetoothDevice, int i, byte[] bytes) {
discoveredDevices.add(bluetoothDevice);
}
};
No estoy emparejando o conectándome con los dispositivos porque no es necesario, simplemente quiero ver qué dispositivos están cerca.
Estoy tratando de hacer un servicio que, cada 5 minutos aproximadamente, llame a un servidor web para actualizar qué dispositivos están cerca en ese momento.
La parte difícil es que el dispositivo Android se moverá, por lo que un dispositivo Bluetooth que esté cerca en este momento podría no estar en 5 minutos. En ese caso, necesito quitarlo de los discoveredDevices
.
Idealmente, me gustaría recibir una devolución de llamada cuando un dispositivo Bluetooth estaba dentro del alcance antes, pero ya no lo está. Sin embargo, esta devolución de llamada no existe.
(Soy consciente de las transmisiones android.bluetooth.device.action.ACL_CONNECTED
y android.bluetooth.device.action.ACL_DISCONNECTED
, pero son para cuando te conectas a un dispositivo bluetooth, que no quiero).
Una opción es hacer un nuevo escaneo cada 5 minutos, pero no puede saber cuándo se han descubierto todos los dispositivos cercanos, por lo que tendría que hacer un escaneo cronometrado, por ejemplo, escanear durante 5 segundos y luego enviar los datos recopilados al servicio web .
Esto suena sucio y arriesgado porque nunca puede estar seguro de que todos los dispositivos cercanos se descubrieron dentro del tiempo asignado, por lo que me gustaría mucho evitar hacerlo así.
Hay otra manera de hacer esto?
Editar
Algunos dispositivos informan continuamente el descubrimiento de dispositivos Bluetooth cercanos, incluso si ya se descubrieron antes. Si esa funcionalidad fuera universal, podría resolver mi problema, sin embargo, esto es específico del dispositivo.
El adaptador bluetooth de mi teléfono, por ejemplo, solo descubre dispositivos cercanos una vez. Algunos otros dispositivos con los que he probado informan continuamente sobre los mismos dispositivos cercanos, pero no todos los dispositivos, por lo que no puedo confiar en eso desafortunadamente.
Esto suena sucio y arriesgado porque nunca puede estar seguro de que todos los dispositivos cercanos se descubrieron dentro del tiempo asignado , por lo que me gustaría mucho evitar hacerlo así.
Eso suena como una suposición razonable, pero está mal.
La baja energía de Bluetooth funciona de una manera particular y los dispositivos BLE tienen algunos límites. Por ejemplo, tienen un rango fijo de posibles frecuencias de publicidad, que van desde 20 milisegundos a 10.24 segundos, en pasos de 0.625 milisegundos. Consulte here y here para obtener información más detallada.
Esto significa que puede tomar un máximo de 10.24 segundos antes de que un dispositivo transmita un nuevo paquete de publicidad. Por lo general, los dispositivos BLE, si no siempre, proporcionan una forma para que su propietario ajuste su frecuencia de publicidad, por lo que la frecuencia puede variar.
En los casos en los que periódicamente recopila datos sobre dispositivos cercanos, como el suyo, está bien usar un escaneo con un límite de tiempo fijo, guardar esos datos en algún lugar, reiniciar el escaneo, recopilar datos nuevos, compararlos con datos antiguos -> obtener resultados .
Por ejemplo, si se encontró un dispositivo en el escaneo 1 pero no en el escaneo 2, puede concluir que el dispositivo estaba dentro del alcance, pero ya no lo está.
Lo mismo ocurre al revés: si se encontró un dispositivo en la exploración 4 pero no en la exploración 3, se trata de un dispositivo recién descubierto.
Finalmente, si se encontró un dispositivo en el escaneo 5, no se encontró en el escaneo 6, pero se encontró de nuevo en el escaneo 7, se vuelve a descubrir y se puede manejar como tal si es necesario.
Como estoy respondiendo a mi propia pregunta aquí, agregaré el código que usé para implementar esto.
Realizo el escaneo en un servicio en segundo plano y me comunico con otras partes de la aplicación utilizando BroadcastReceivers. Asset
es una clase de mina personalizada que contiene algunos datos. DataManager
es una clase mía personalizada que, como lo adivinó, administra los datos.
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);
}
}
Este código no es perfecto e incluso podría tener errores, pero al menos le dará una idea o ejemplo de cómo se puede implementar esto.
Puedo recomendar este enfoque:
Use la estructura Map<BluetoothDevice, Long>
para almacenar los dispositivos descubiertos, donde Long
es el tiempo de detección del dispositivo (puede ser System.currentTimeMillis()
por ejemplo).
Luego, en su servicio (por lo que entiendo de la pregunta, se implementará algún tipo de tarea repetida) simplemente extraiga los dispositivos reales según el momento de su detección.
Y tiene toda la razón, no hay garantía de que todos los dispositivos cercanos hayan sido descubiertos dentro del tiempo asignado. Especialmente esto es real para los dispositivos Android. Los dispositivos iOS a su vez tienen otro problema: pueden cambiar la dirección de su dispositivo Bluetooth en tiempo de ejecución sin una causa externa aparente. Espero que esto te ayude a ahorrar tiempo durante la depuración.
Editar
Como resultado de la investigación de este tema, se encontró esta discusión en code.google.com
El problema aún está abierto y parece que está relacionado con las características del hardware y no se puede solucionar mediante programación. Además, parece que el error permanecerá en los dispositivos problemáticos incluso después de que se actualice un sistema. Por lo tanto, reiniciar el análisis periódicamente puede ser una solución aceptable para este caso.