ОБНОВЛЕНИЕ 14/08 - 3 - найдено реальное решение:
Вы можете проверить решение в ответах ниже!
ОБНОВЛЕНИЕ 16/06 - 2 - Может быть решение:
Как сказал Сэнди Чепмен в комментариях в своем ответе, теперь я могу получить свое периферийное устройство в начале сканирования, используя этот метод:
- (NSArray<CBPeripheral *> * nonnull)retrievePeripheralsWithIdentifiers:(NSArray<NSUUID *> * nonnull)identifiers
Я на самом деле пытаюсь заставить его работать, возвращая мое периферийное устройство в начале сканирования и запуская соединение (даже если оно не входит в диапазон), когда оно мне нужно. iOS будет поддерживать его до тех пор, пока не найдет устройство, которое я ищу.
Также обратите внимание, что может произойти ошибка в iOS 8.x, которая заставляет приложение с отладочной сборкой сканировать (исчезающие обратные вызовы, когда я получаю) время от времени, если есть другое приложение в фоновом режиме с выпуском сборки который использует Bluetooth.
ОБНОВЛЕНИЕ 16/06:
Итак, я проверил с помощью retrievePeripheralsWithServices, если какое-либо устройство было подключено во время запуска сканирования. Когда я получаю ошибку, я запускаю приложение и первое, что я делаю в
- (void) applicationDidBecomeActive:(UIApplication *)application
- проверить размер возвращаемого массива. Это всегда 0, каждый раз, когда я получаю ошибку. Ошибка может произойти и в том случае, если мое устройство не подключалось ранее в текущем запуске. Я также могу видеть рекламу своего устройства и запускать команду со вторым устройством, пока я получил ошибку с другим устройством.
ОБНОВЛЕНИЕ 10/06:
- Я оставил свое приложение за всю ночь, чтобы проверить, нет ли утечки памяти или массового использования ресурсов, вот мой результат после ~ 12-14 часов работы в фоновом режиме. Использование памяти/процессора точно такое же, как и когда я ушел. Это заставляет меня думать, что у моего приложения нет утечек, которые могут привести к закрытию iOS, чтобы вернуть память/использование ЦП.
ОБНОВЛЕНИЕ 08/06:
- Обратите внимание, что это не проблема рекламы, так как наше устройство BLE постоянно работает, и мы использовали самую сильную электронную электронную карту BLE, которую мы могли найти.
- Это также не проблема с синхронизацией обнаружения iOS в фоновом режиме. Я ждал очень долго (20-30 минут), чтобы быть уверенным, что это не проблема.
ОРИГИНАЛЬНЫЙ ВОПРОС
В настоящее время я работаю над приложением, которое обрабатывает связь с устройством BLE. Одним из моих ограничений является то, что я должен подключиться к этому устройству только тогда, когда мне нужно отправить команду или прочитать данные. Я должен отключиться как можно скорее, когда это будет сделано, чтобы другие потенциальные пользователи могли сделать то же самое.
Одной из особенностей приложения является следующее:
- Пользователи могут включать автоматическую команду, когда приложение находится в фоновом режиме. Эта автоматическая команда запускает, если устройство не было обнаружено в течение 10 минут.
- Мое приложение сканирует, пока не найдет мое устройство BLE.
- Чтобы он не проснулся, когда мне это нужно, я каждый раз перезапускаю сканирование из-за игнорирования параметра CBCentralManagerScanOptionAllowDuplicatesKey.
- Когда он обнаружен, я проверяю, было ли последнее обнаружение более 10 минут назад. Если это случай, я подключаюсь к устройству, а затем записываю в характеристику, соответствующую той службе, которая мне нужна.
Цель состоит в том, чтобы запускать это устройство, когда пользователь входит в диапазон. Это может произойти через несколько минут после выхода за пределы диапазона, например, несколько часов, это зависит от моих привычек пользователей.
Все работает нормально, но иногда (похоже, происходит случайным образом), вид сканирования "зависает". Мой процесс прошел хорошо, но через пару раз я вижу сканирование своего приложения, но мой didDiscoverPeripheral: callback никогда не вызывается, даже если мое тестовое устройство прямо перед моим устройством BLE. Иногда это может занять некоторое время, чтобы обнаружить это, но здесь ничего не происходит через пару минут.
Я думал, что iOS, возможно, убил мое приложение, чтобы потребовать обратно память, но когда я выключаюсь и по Bluetooth, centralManagerDidUpdateState: называется правильным. Если мое приложение, где его убили, не должно быть так? Если я открою свое приложение, сканирование будет перезапущено, и оно вернется к жизни. Я также проверил, что iOS не отключает мое приложение после 180 секунд активности, но это не так, потому что он работает хорошо после этого количества времени.
Я установил свой .plist для правильных настроек (bluetooth-central в UIBackgroundModes). Мой класс, управляющий всей обработкой BLE, хранится в моем AppDelegateкак одноэлемент, доступный через все мое приложение. Я также тестировал, чтобы переключиться туда, где я создаю этот объект. В настоящее время я создаю его в приложении: метод didFinishLaunchingWithOptions:. Я попытался поместить его в AppDelegate init:, но сканирование терпит неудачу каждый раз, когда я в фоновом режиме, если я это делаю.
Я не знаю, какая часть моего кода я могу показать вам, чтобы помочь вам лучше понять мой процесс. Вот несколько примеров, которые могут помочь. Обратите внимание: " AT_appDelegate" - это макрос, чтобы получить доступ к моему AppDelegate.
// Init of my DeviceManager class that handles all BLE processing
- (id) init {
self = [super init];
// Flags creation
self.autoConnectTriggered = NO;
self.isDeviceReady = NO;
self.connectionUncomplete = NO;
self.currentCommand = NONE;
self.currentCommand_index = 0;
self.signalOkDetectionCount = 0; // Helps to find out if device is at a good range or too far
self.connectionFailedCount = 0; // Helps in a "try again" process if a command fails
self.main_uuid = [CBUUID UUIDWithString:MAINSERVICE_UUID];
self.peripheralsRetainer = [[NSMutableArray alloc] init];
self.lastDeviceDetection = nil;
// Ble items creation
dispatch_queue_t queue = dispatch_queue_create("com.onset.corebluetooth.queue", DISPATCH_QUEUE_SERIAL);
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:queue];
[self startScanning];
return self;
}
// The way i start the scan
- (void) startScanning {
if (!self.isScanning && self.centralManager.state == CBCentralManagerStatePoweredOn) {
CLS_LOG(@"### Start scanning ###");
self.isScanning = YES;
NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:!self.isBackground] forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.centralManager scanForPeripheralsWithServices:@[self.main_uuid] options:options];
});
}
}
// The way i stop and restart the scan after i've found our device. Contains some of foreground (UI update) process that you can ignore
- (void) stopScanningAndRestart: (BOOL) restart {
CLS_LOG(@"### Scanning terminated ###");
if (self.isScanning) {
self.isScanning = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.centralManager stopScan];
});
// Avoid clearing the connection when waiting for notification (remote + learning)
if (!self.isWaitingNotifiy && !self.isSynchronizing && self.currentCommand == NONE ) {
// If no device found during scan, update view
if (self.deviceToReach == nil && !self.isBackground) {
// Check if any connected devices last
if (![self isDeviceStillConnected]) {
CLS_LOG(@"--- Device unreachable for view ---");
} else {
self.isDeviceInRange = YES;
self.deviceToReach = AT_appDelegate.user.device.blePeripheral;
}
[self.delegate performSelectorOnMainThread:@selector(updateView) withObject:nil waitUntilDone:YES];
}
// Reset var
self.deviceToReach = nil;
self.isDeviceInRange = NO;
self.signalOkDetectionCount = 0;
// Check if autotrigger needs to be done again - If time interval is higher enough,
// reset autoConnectTriggered to NO. If user has been away for <AUTOTRIGGER_INTERVAL>
// from the device, it will trigger again next time it will be detected.
if ([[NSDate date] timeIntervalSinceReferenceDate] - [self.lastDeviceDetection timeIntervalSinceReferenceDate] > AUTOTRIGGER_INTERVAL) {
CLS_LOG(@"### Auto trigger is enabled ###");
self.autoConnectTriggered = NO;
}
}
}
if (restart) {
[self startScanning];
}
}
// Here is my detection process, the flag "isInBackground" is set up each time the app goes background
- (void) centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
CLS_LOG(@"### : %@ -- %@", peripheral.name, RSSI);
BOOL deviceAlreadyShown = [AT_appDelegate isDeviceAvailable];
// If current device has no UUID set, check if peripheral is the right one
// with its name, containing his serial number (macaddress) returned by
// the server on remote adding
NSString *p1 = [[[peripheral.name stringByReplacingOccurrencesOfString:@":" withString:@""] stringByReplacingOccurrencesOfString:@"Extel " withString:@""] uppercaseString];
NSString *p2 = [AT_appDelegate.user.device.serial uppercaseString];
if ([p1 isEqualToString:p2]) {
AT_appDelegate.user.device.scanUUID = peripheral.identifier;
}
// Filter peripheral connection with uuid
if ([AT_appDelegate.user.device.scanUUID isEqual:peripheral.identifier]) {
if (([RSSI intValue] > REQUIRED_SIGNAL_STRENGTH && [RSSI intValue] < 0) || self.isBackground) {
self.signalOkDetectionCount++;
self.deviceToReach = peripheral;
self.isDeviceInRange = (self.signalOkDetectionCount >= REQUIRED_SIGNAL_OK_DETECTIONS);
[peripheral setDelegate:self];
// Reset blePeripheral if daughter board has been switched and there were
// not enough time for the software to notice connection has been lost.
// If that was the case, the device.blePeripheral has not been reset to nil,
// and might be different than the new peripheral (from the new daugtherboard)
if (AT_appDelegate.user.device.blePeripheral != nil) {
if (![AT_appDelegate.user.device.blePeripheral.name isEqualToString:peripheral.name]) {
AT_appDelegate.user.device.blePeripheral = nil;
}
}
if (self.lastDeviceDetection == nil ||
([[NSDate date] timeIntervalSinceReferenceDate] - [self.lastDeviceDetection timeIntervalSinceReferenceDate] > AUTOTRIGGER_INTERVAL)) {
self.autoConnectTriggered = NO;
}
[peripheral readRSSI];
AT_appDelegate.user.device.blePeripheral = peripheral;
self.lastDeviceDetection = [NSDate date];
if (AT_appDelegate.user.device.autoconnect) {
if (!self.autoConnectTriggered && !self.autoTriggerConnectionLaunched) {
CLS_LOG(@"--- Perform trigger ! ---");
self.autoTriggerConnectionLaunched = YES;
[self executeCommand:W_TRIGGER onDevice:AT_appDelegate.user.device]; // trigger !
return;
}
}
}
if (deviceAlreadyShown) {
[self.delegate performSelectorOnMainThread:@selector(updateView) withObject:nil waitUntilDone:YES];
}
}
if (self.isBackground && AT_appDelegate.user.device.autoconnect) {
CLS_LOG(@"### Relaunch scan ###");
[self stopScanningAndRestart:YES];
}
}