BluetoothManager 是一个负责与 ble 设备交互的类。
它作为单例注入 RxBleClient 并开始扫描片段/服务的启动方法。有时扫描会自动停止,有时几秒钟后我找不到原因。
问题是设备仍在广播它的包,但我的应用程序无法接收它,因为 BluetoothAdapter 已经停止了扫描。
以下是在服务中扫描的日志:
03-09 11:17:58.526 10196-10196/com.turboegg.storm D/BluetoothManager *** 212: Clear scan
03-09 11:17:58.527 10196-10196/com.turboegg.storm D/BluetoothManager *** 72: Start scanning
03-09 11:17:58.579 10196-10196/com.turboegg.storm I/AppCompatViewInflater: app:theme is now deprecated. Please move to using android:theme instead.
03-09 11:17:58.589 10196-10196/com.turboegg.storm I/AppCompatViewInflater: app:theme is now deprecated. Please move to using android:theme instead.
03-09 11:17:58.602 10196-10196/com.turboegg.storm I/AppCompatViewInflater: app:theme is now deprecated. Please move to using android:theme instead.
03-09 11:17:58.604 10196-10196/com.turboegg.storm I/AppCompatViewInflater: app:theme is now deprecated. Please move to using android:theme instead.
03-09 11:18:01.592 10196-10196/com.turboegg.storm D/BluetoothAdapter: startLeScan(): null
03-09 11:18:01.596 10196-10196/com.turboegg.storm D/BluetoothAdapter: STATE_ON
03-09 11:18:01.600 10196-10207/com.turboegg.storm D/BluetoothLeScanner: onClientRegistered() - status=0 clientIf=8
03-09 11:18:01.656 10196-10196/com.turboegg.storm D/BluetoothManager *** 79: Device C6:86:B6:35:FF:00
03-09 11:18:01.784 10196-10196/com.turboegg.storm D/BluetoothManager *** 79: Device EE:AC:C7:32:9A:F6
03-09 11:18:02.643 10196-10196/com.turboegg.storm D/BluetoothManager *** 79: Device C6:86:B6:35:FF:00
03-09 11:18:02.835 10196-10196/com.turboegg.storm D/BluetoothManager *** 79: Device EE:AC:C7:32:9A:F6
03-09 11:18:03.659 10196-10196/com.turboegg.storm D/BluetoothManager *** 79: Device C6:86:B6:35:FF:00
03-09 11:18:03.796 10196-10196/com.turboegg.storm D/BluetoothManager *** 79: Device EE:AC:C7:32:9A:F6
03-09 11:18:04.663 10196-10196/com.turboegg.storm D/BluetoothManager *** 79: Device C6:86:B6:35:FF:00
03-09 11:18:04.785 10196-10196/com.turboegg.storm D/BluetoothManager *** 79: Device EE:AC:C7:32:9A:F6
03-09 11:18:05.648 10196-10196/com.turboegg.storm D/BluetoothManager *** 79: Device C6:86:B6:35:FF:00
03-09 11:18:05.809 10196-10196/com.turboegg.storm D/BluetoothManager *** 79: Device EE:AC:C7:32:9A:F6
03-09 11:18:06.817 10196-10196/com.turboegg.storm D/BluetoothManager *** 79: Device EE:AC:C7:32:9A:F6
03-09 11:18:07.672 10196-10196/com.turboegg.storm D/BluetoothManager *** 79: Device C6:86:B6:35:FF:00
03-09 11:18:07.827 10196-10196/com.turboegg.storm D/BluetoothManager *** 79: Device EE:AC:C7:32:9A:F6
03-09 11:18:08.682 10196-10196/com.turboegg.storm D/BluetoothManager *** 79: Device C6:86:B6:35:FF:00
03-09 11:18:08.826 10196-10196/com.turboegg.storm D/BluetoothManager *** 79: Device EE:AC:C7:32:9A:F6
03-09 11:18:09.009 10196-10196/com.turboegg.storm D/BluetoothAdapter: stopLeScan()
03-09 11:18:09.011 10196-10196/com.turboegg.storm D/BluetoothAdapter: STATE_ON
这是蓝牙管理器:
public class BluetoothManager {
private Context context;
private RxBleClient rxBleClient;
private StorageManager storageManager;
private Subscription scanSubscription, connectionSubscription, notificationSubscription, writeSubscription, keepAwakenSubscription;
private RxBleDevice connectedDevice;
private RxBleConnection activeConnection;
public BluetoothManager(Context context, RxBleClient rxBleClient, StorageManager storageManager) {
this.context = context;
this.rxBleClient = rxBleClient;
this.storageManager = storageManager;
}
public void onDestroy() {
stop();
storageManager.setAllSensorsDisconnectible();
storageManager.clearResources();
}
public void stop() {
clearScan();
disconnect();
}
public void stopScan() {
clearScan();
}
public void disconnect() {
clearConnection();
clearNotification();
clearWrite();
clearKeepAwaken();
}
public void startScan() {
clearScan();
Timber.d("Start scanning");
scanSubscription = Observable.timer(3, TimeUnit.SECONDS)
.flatMap(ignored -> rxBleClient.scanBleDevices())
.filter(rxBleScanResult -> CommonUtils.isStormSensor(rxBleScanResult.getBleDevice()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(rxBleScanResult -> {
RxBleDevice device = rxBleScanResult.getBleDevice();
Timber.d("Device %s", device.getMacAddress());
if (device.getMacAddress() != null && !storageManager.containsDiscoveredDevice(device.getMacAddress())) {
Timber.d("Found device, %s %s", device.getName(), device.getMacAddress());
storageManager.addDiscoveredSensor(device.getMacAddress(), device.getName());
EventBus.getDefault().post(new DeviceDiscovered(device.getMacAddress(), device.getName()));
}
storageManager.setSensorConnectible(device.getMacAddress(), Calendar.getInstance().getTime());
}, this::onBleFailure);
}
public void connectDevice(String macAddress, String outInDoor) {
disconnect();
connectedDevice = rxBleClient.getBleDevice(macAddress);
if (connectedDevice != null) {
Timber.d("Start connecting to %s", macAddress);
connectionSubscription = connectedDevice.establishConnection(context, false)
.subscribe(connection -> {
// Need to delay write operations due to sensor processor limitations
writeCharacteristic(connection, CommonUtils.getBleTime())
.delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
writeCharacteristic(connection, AppConstants.BLUETOOTH_SET_BUZZER_OFF)
.delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
writeCharacteristic(connection, AppConstants.BLUETOOTH_SET_MASK)
.delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
writeCharacteristic(connection, AppConstants.BLUETOOTH_CLEAR_STATS)
.delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
writeCharacteristic(connection, AppConstants.BLUETOOTH_CLEAR_MEM)
.delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
writeCharacteristic(connection, outInDoor)
.delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
writeCharacteristic(connection, AppConstants.BLUETOOTH_CLEAR_MASK)
.delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
writeCharacteristic(connection, AppConstants.BLUETOOTH_SET_MASK)
.delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
activeConnection = connection;
getBasicParams(connection, macAddress);
subscribeForNotifications(connection, macAddress);
}, throwable -> {
Timber.e("Error while connecting " + throwable.toString());
handleConnectionError(macAddress);
disconnect();
});
}
}
public void writeOutInDoor(String outInDoor) {
clearWrite();
writeSubscription = Observable.combineLatest(
writeCharacteristic(activeConnection, AppConstants.BLUETOOTH_CLEAR_STATS)
.delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS),
writeCharacteristic(activeConnection, AppConstants.BLUETOOTH_CLEAR_MEM)
.delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS),
writeCharacteristic(activeConnection, outInDoor)
.delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS),
writeCharacteristic(activeConnection, AppConstants.BLUETOOTH_CLEAR_MASK)
.delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS),
writeCharacteristic(activeConnection, AppConstants.BLUETOOTH_SET_MASK)
.delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS),
(bytes, bytes2, value, bytes4, bytes5) -> value)
.subscribe(value -> Timber.d("Written value %s", new String(value)),
throwable -> Timber.e("Error writing outInDoor: %s", throwable.toString())
);
}
public void handleDeviceStatus(boolean checked) {
if (checked) {
keepDeviceAwaken();
} else {
clearKeepAwaken();
}
}
public void handleDeviceDisconnection(String macAddress) {
if (connectedDevice != null && connectedDevice.getMacAddress().equals(macAddress)) {
disconnect();
}
}
/**
* Private methods
*/
private void getBasicParams(RxBleConnection connection, String macAddress) {
Observable.combineLatest(
connection.readCharacteristic(AppConstants.BLUETOOTH_UUID_BATTERY),
connection.readCharacteristic(AppConstants.BLUETOOTH_UUID_FIRMWARE),
(bytes, bytes2) -> new DeviceConnected(bytes, bytes2, macAddress))
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(deviceConnected -> {
Timber.d("Bat %s", deviceConnected.getBattery());
Timber.d("Fmw %s", deviceConnected.getFirmware());
storageManager.updateSensorInfo(macAddress, deviceConnected.getBattery(), deviceConnected.getFirmware());
storageManager.setSensorConnected(macAddress);
handleDeviceStatus(storageManager.getStormSensor(macAddress).isStatusChecked());
EventBus.getDefault().post(deviceConnected);
}, throwable -> Timber.e("Sensor basic info error %s", throwable.getMessage()));
}
private void subscribeForNotifications(RxBleConnection connection, String macAddress) {
notificationSubscription = Observable.combineLatest(
connection.setupNotification(AppConstants.BLUETOOTH_UUID_EVENT)
.<byte[]>flatMap(observable -> observable),
connection.setupNotification(AppConstants.BLUETOOTH_UUID_DISTANCE)
.<byte[]>flatMap(observable -> observable),
NotificationStormEvent::new)
.filter(CommonUtils::isStormEvent)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(notificationStormEvent -> {
Timber.d("New storm, event: %s, distance %s km", notificationStormEvent.getEvent(), notificationStormEvent.getDistance());
storageManager.addStormEvent(Calendar.getInstance().getTime(), notificationStormEvent.getDistance());
EventBus.getDefault().post(notificationStormEvent);
}, throwable -> Timber.e("Notification error %s", throwable.toString()));
}
private void keepDeviceAwaken() {
clearKeepAwaken();
keepAwakenSubscription = Observable.interval(
AppConstants.BLUETOOTH_KEEP_AWAKE_MINS, AppConstants.BLUETOOTH_KEEP_AWAKE_MINS, TimeUnit.MINUTES)
.flatMap(aLong -> writeCharacteristic(activeConnection, CommonUtils.getBleTime()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bytes -> Timber.d("Written value %s to keep device awaken", new String(bytes)),
throwable -> Timber.e("Error keeping awake %s", throwable.toString()));
}
private void onBleFailure(Throwable throwable) {
if (throwable instanceof BleScanException) {
handleBleScanException((BleScanException) throwable);
}
}
private void clearScan() {
Timber.d("Clear scan");
if (scanSubscription != null) {
scanSubscription.unsubscribe();
Timber.d("Clear unsubscribed");
}
scanSubscription = null;
}
private void clearConnection() {
if (connectionSubscription != null) connectionSubscription.unsubscribe();
connectionSubscription = null;
connectedDevice = null;
activeConnection = null;
}
private void clearNotification() {
if (notificationSubscription != null) notificationSubscription.unsubscribe();
notificationSubscription = null;
}
private void clearWrite() {
if (writeSubscription != null) writeSubscription.unsubscribe();
writeSubscription = null;
}
private void clearKeepAwaken() {
if (keepAwakenSubscription != null) keepAwakenSubscription.unsubscribe();
keepAwakenSubscription = null;
}
private Observable<byte[]> writeCharacteristic(RxBleConnection connection, String value) {
byte[] bytesToWrite = value.getBytes(StandardCharsets.UTF_8);
return connection.writeCharacteristic(AppConstants.BLUETOOTH_UUID_UART_WRITE, bytesToWrite);
}
private void handleBleScanException(BleScanException bleScanException) {
switch (bleScanException.getReason()) {
case BleScanException.BLUETOOTH_NOT_AVAILABLE:
Timber.e("Bluetooth is not available");
break;
case BleScanException.BLUETOOTH_DISABLED:
Timber.e("Enable bluetooth and try again");
break;
case BleScanException.LOCATION_PERMISSION_MISSING:
Timber.e("On Android 6.0 location permission is required. Implement Runtime Permissions ");
break;
case BleScanException.LOCATION_SERVICES_DISABLED:
Timber.e("Location services needs to be enabled on Android 6.0");
break;
case BleScanException.BLUETOOTH_CANNOT_START:
default:
Timber.e("Unable to onStart scanning");
break;
}
}
private void handleConnectionError(String macAddress) {
// This is the case where e.g. connection was lost or the device was turned off
// Need to call for realm to modify the sensor object in different thread
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(realm1 -> {
StormSensor sensor = realm1.where(StormSensor.class).equalTo(AppConstants.STORM_SENSOR_MAC_ADDRESS, macAddress).findFirst();
sensor.setConnected(false);
});
realm.close();
}
}