Подтвердить что ты не робот

IOS - BLE Сканирование на фоне замерзает случайно

ОБНОВЛЕНИЕ 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, чтобы вернуть память/использование ЦП.

Resource usage analysis

ОБНОВЛЕНИЕ 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];
}
  }
4b9b3361

Ответ 1

После многих попыток я, наконец, нашел реальное решение.

FYI, у меня было много разговоров с инженером Apple (через Техническую поддержку). Он вел меня двумя способами:

  • Проверка правильности реализации состояния Сохранение/восстановление Bluetooth.. Вы сможете найти несколько потоков об этом при переполнении стека, например этот поток или этот. Вы также найдете полезную документацию для яблока здесь: Основная фоновая обработка Bluetooth.
  • Проверка параметров соединений, реализованных в моем устройстве Bluetooth, например: Интервал между подключениями, Минимальный и максимальный интервал подключения, Задержка подчиненного устройства и Тайм-аут подключения. Существует много информации в Руководства по дизайну Apple Bluetooth

В этих элементах может участвовать реальное решение. Я добавил и исправил все, пока я искал эту ошибку. Но то, что заставило его работать (я тестировал его около 4-5 часов, и он вообще не замерзал) был около dispatch_queue.

Что я сделал раньше:

// Initialisation
dispatch_queue_t queue = dispatch_queue_create("com.onset.corebluetooth.queue", DISPATCH_QUEUE_SERIAL);
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:queue options:@{CBCentralManagerOptionRestoreIdentifierKey:RESTORE_KEY}];

// Start scanning
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   [self.centralManager scanForPeripheralsWithServices:@[self.main_uuid] options:options];
});

// Stop scanning
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   [self.centralManager stopScan];
});

Что я сейчас делаю:

// Initialisation
self.bluetoothQueue = dispatch_queue_create("com.onset.corebluetooth.queue", DISPATCH_QUEUE_SERIAL);
    self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:self.bluetoothQueue options:@{CBCentralManagerOptionRestoreIdentifierKey:RESTORE_KEY}];

// Start scanning
dispatch_async(self.bluetoothQueue, ^{
   [self.centralManager scanForPeripheralsWithServices:@[self.main_uuid] options:options];
});

// Stop scanning
dispatch_async(self.bluetoothQueue, ^{
   [self.centralManager stopScan];
});

Обратите внимание, что я добавил эту строку в свой DeviceManager.h(мой основной класс Ble):

@property (atomic, strong) dispatch_queue_t bluetoothQueue;

Как вы можете видеть, это было немного испорчено:)

Итак, теперь я могу сканировать столько, сколько нужно. Спасибо за вашу помощь! Надеюсь, однажды это поможет кому-то.

Ответ 2

В вашем примере кода не похоже, что вы вызываете любой из этих методов на свой CBCentralManager:

- (NSArray<CBPeripheral *> * nonnull)retrieveConnectedPeripheralsWithServices:(NSArray<CBUUID *> * nonnull)serviceUUIDs

- (NSArray<CBPeripheral *> * nonnull)retrievePeripheralsWithIdentifiers:(NSArray<NSUUID *> * nonnull)identifiers

Возможно, вы находитесь в состоянии, в котором вы ожидаете didDiscoverPeripheral, который никогда не появится, потому что система уже подключена. После создания экземпляра CBCentralManager сначала проверьте, подключено ли ваше периферийное устройство, вызвав один из этих методов.

Начальный поток для моего центрального менеджера, который использует восстановление состояния, выглядит следующим образом:

  • Проверьте наличие доступных периферийных устройств из состояния восстановления (не требуется в вашем случае).
  • Проверьте наличие периферийных устройств, обнаруженных при предыдущем сканировании (я сохраняю их в массиве доступных периферийных устройств).
  • Проверьте подключенные периферийные устройства (используя методы, упомянутые выше).
  • Начать сканирование, если ни одна из вышеперечисленных не вернула периферию.

Еще одна подсказка, которую вы, возможно, уже обнаружили: iOS будет кэшировать характеристики и UUID, рекламируемые периферией. Если это изменение, единственный способ очистить кэш - это отключить и включить bluetooth в настройках системы iOS.

Ответ 3

Это может быть связано с устройством Bluetooth, с которым вы пытаетесь установить соединение, некоторые из них имеют низкую рекламную скорость, обычно это делается для экономии заряда батареи. Первое, что я проверил, - это то, как часто устройство BLE настроено для рекламы.

Это может быть даже связано с тем, что устройство iOS не сканирует так часто, как другие устройства, как описано в этом сообщении.