IOS/Objective-C: BLE в периферийном режиме не работает

Я пытаюсь запустить BLE в Central и Peripheral. С жестко закодированными переменными на данный момент для простоты.
Я думаю, что я реализовал все в соответствии с документами.

Я могу проверить, работает ли периферийный режим с помощью Android-смартфона (api-19, не поддерживайте периферийный режим). iPhone отображается правильно, когда я использую приложение MyBeacon, например.

Однако он не появляется, когда я запускаю этот код в своем приложении:

Вот .h:

#import <RCTBridgeModule.h>
#import <RCTEventEmitter.h>
@import CoreBluetooth;
@import QuartzCore;

@interface BTManager : RCTEventEmitter <RCTBridgeModule, CBCentralManagerDelegate, CBPeripheralManagerDelegate, CBPeripheralDelegate>

@property (nonatomic, strong) CBCentralManager *centralManager;
@property (nonatomic, strong) CBPeripheralManager *peripheralManager;
@property (nonatomic, strong) CBMutableCharacteristic *transferCharacteristic;

@end

И .m:

#import "BTManager.h"

@implementation BTManager

RCT_EXPORT_MODULE();

- (NSArray<NSString *> *)supportedEvents
{
  return @[@"BTManagerDeviceFound", @"BTManagerStatus"];
}

- (void)viewDidLoad
{
  CBCentralManager *centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
  self.centralManager = centralManager;
  CBPeripheralManager *peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
  self.peripheralManager = peripheralManager;
}

RCT_EXPORT_METHOD(start:(NSDictionary *)options)
{

  [self.centralManager scanForPeripheralsWithServices:nil options:nil];

  self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:@"EB6727C4-F184-497A-A656-76B0CDAC633A"] properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable];

  CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:@"EB6727C4-F184-497A-A656-76B0CDAC633A"] primary:YES];

  transferService.characteristics = @[self.transferCharacteristic];

  [self.peripheralManager addService:transferService];

  [self.peripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:@"FB694B90-F49E-4597-8306-171BBA78F846"]] }];

  NSLog(@"Started");
}

- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
  // log peripheralManager state
  NSLog(@"peripheralManagerDidUpdateState peripheral %@", peripheral);
}

- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error
{
  // log centralManager state
  NSLog(@"peripheralManager didAddService peripheral %@", peripheral);
  NSLog(@"peripheralManager didAddService service %@", service);
  NSLog(@"peripheralManager didAddService error %@", error);
}

- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error
{
  NSLog(@"peripheralManagerDidStartAdvertising peripheral %@", peripheral);
  NSLog(@"peripheralManagerDidStartAdvertising error %@", error);
}

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
  [self sendEventWithName:@"BTManagerDeviceFound" body:advertisementData];
}

- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
  NSLog(@"centralManagerDidUpdateState central %@", central);
}

RCT_EXPORT_METHOD(stop:(NSDictionary *)options)
{
  // remove all related processes, send event to js
  [self sendEventWithName:@"BTManagerStatus" body:@"details here"];
  //  [self.myCentralManager stopScan];
}

@end

Никто из прослушивателей событий не стреляет, кроме NSLog(@"Started");

У меня есть это предложение:

В отношении связанного объекта убедитесь, что любой код, созданный и выполняющий ваш BTManager, позволяет объекту жить достаточно долго для выполнения действий Bluetooth. Если он выходит из сферы действия или собирает мусор, у вас будет такая же проблема.

Я не знаю, как проверить, верно это или нет.

Кроме того, этот учебник использует этот метод:

- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
    if (peripheral.state != CBPeripheralManagerStatePoweredOn) {
        return;
    }

    if (peripheral.state == CBPeripheralManagerStatePoweredOn) {
        self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID] properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];

        CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID] primary:YES];

        transferService.characteristics = @[_transferCharacteristic];

        [_peripheralManager addService:transferService];
    }
}

... но docs не содержит ничего, что связано с этим CBPeripheralManagerStatePoweredOn. Статья составляет 4 года, поэтому она может быть даже не актуальна сейчас.

О, да, а также я едва знаком с objective-c, поэтому ошибка там может быть очень простой.

Спасибо, что прочитали здесь:)

Обновление 1

По-видимому, viewDidLoad никогда не запускается. Поэтому я переместил код из него в метод start и код из start в ...DidUpdateState, как предложил @LarsBlumberg.

#import "BTManager.h"
#import <React/RCTLog.h>

@implementation BTManager

RCT_EXPORT_MODULE();

- (NSArray<NSString *> *)supportedEvents
{
  return @[@"BTManagerDeviceFound", @"BTManagerStatus"];
}

RCT_EXPORT_METHOD(start:(NSDictionary *)options)
{
  RCTLogInfo(@"Start?");
  CBCentralManager *centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:@{CBCentralManagerOptionShowPowerAlertKey: @(YES)}];
  self.centralManager = centralManager;
  CBPeripheralManager *peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
  self.peripheralManager = peripheralManager;
  RCTLogInfo(@"Started");
}

- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
  // log peripheralManager state
  RCTLogInfo(@"peripheralManagerDidUpdateState peripheral %ld", (long)peripheral.state);
  if (peripheral.state != CBPeripheralManagerStatePoweredOn) {
    RCTLogInfo(@"Peripheral is not powered on");
    return;
  }
  self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:@"EB6727C4-F184-497A-A656-76B0CDAC633A"] properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable];

  CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:@"EB6727C4-F184-497A-A656-76B0CDAC633A"] primary:YES];

  transferService.characteristics = @[self.transferCharacteristic];

  [peripheral addService:transferService];

  NSDictionary *advertisingData = @{CBAdvertisementDataLocalNameKey : @"yphone", CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:@"EBA38950-0D9B-4DBA-B0DF-BC7196DD44FC"]]};
  [peripheral startAdvertising:advertisingData];
}

- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error
{
  // log centralManager state
  RCTLogInfo(@"peripheralManager didAddService peripheral %@", peripheral);
  RCTLogInfo(@"peripheralManager didAddService service %@", service);
  RCTLogInfo(@"peripheralManager didAddService error %@", error);
}

- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error
{
  RCTLogInfo(@"peripheralManagerDidStartAdvertising peripheral %@", peripheral);
  RCTLogInfo(@"peripheralManagerDidStartAdvertising error %@", error);
}

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
  [self sendEventWithName:@"BTManagerDeviceFound" body:advertisementData];
}

- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
  RCTLogInfo(@"centralManagerDidUpdateState central %ld", (long)central.state);
  // if (central.state == CBCentralManagerStatePoweredOff) {
  //   UIAlertView *alert = [[UIAlertView alloc] initWithTitle: @"Error" message: @"Please turn on Bluetooth in Settings" delegate: nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; 
  //   [alert show]; 
  // }
  if (central.state != CBCentralManagerStatePoweredOn) {
    RCTLogInfo(@"Central is not powered on");
    return;
  }
  [central scanForPeripheralsWithServices:nil options:nil];
}

RCT_EXPORT_METHOD(stop:(NSDictionary *)options)
{
  // remove all related processes, send event to js
  [self sendEventWithName:@"BTManagerStatus" body:@"details here"];
  //  [self.myCentralManager stopScan];
}

@end

Теперь все идет нормально до состояния обновлений. Оба central.state и peripheral.state возвращают 4, а CB...ManagerStatePoweredOn равно 5. Как сделать его равным 5? Приложение запрашивает доступ к bt, но iphone не разрешает его. Когда он включается вручную, все работает хорошо.

Ответ 1

Вероятно, вы добавите службу transferService слишком рано для периферийного менеджера, и вы также, вероятно, начинаете рекламу слишком рано.

Вы должны ждать, пока peripheralManagerDidUpdateState не сообщит, что ваше состояние менеджера периферии достигло CBPeripheralManagerStatePoweredOn.

Это означает, что вы можете переместить код из RCT_EXPORT_METHOD(start:(NSDictionary *)options) в пустую peripheralManagerDidUpdateState реализацию.

В качестве альтернативы, если вы хотите добавить сервисы к своему peripheralManager, а затем начать рекламу, когда кто-то вызвал ваш метод start (похоже, вы создаете привязку React Native?), убедитесь, что self.peripheralManager.state CBPeripheralManagerStatePoweredOn в вашем методе start. Для этого, однако, требуется, чтобы вызывающий start (ваш React Native JS-код) не вызывал этот метод слишком рано.

RCT_EXPORT_METHOD(start:(NSDictionary *)options)
{
  NSLog(@"Start?");
  if (self.peripheralManager.state != CBPeripheralManagerStatePoweredOn) {
      NSLog(@"Peripheral is not powered on");
      return;
  }
  if (self.centralManager.state != CBCentralManagerStatePoweredOn) {
      NSLog(@"Central is not powered on");
      return;
  }
  NSLog(@"OK, peripheral and central are both powered on");
  [self.centralManager scanForPeripheralsWithServices:nil options:nil];

  self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:@"EB6727C4-F184-497A-A656-76B0CDAC633A"] properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable];

  CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:@"EB6727C4-F184-497A-A656-76B0CDAC633A"] primary:YES];

  transferService.characteristics = @[self.transferCharacteristic];

  [self.peripheralManager addService:transferService];

  [self.peripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:@"FB694B90-F49E-4597-8306-171BBA78F846"]] }];

  NSLog(@"Started");
}

Аналогичным образом вы можете только начать сканирование периферийных устройств с помощью centralManager, если оно имеет состояние "включено".

Ответ 2

По-видимому, нет возможности программно включить Bluetooth в iOS, а спаривание невозможно программно прямо сейчас.

Однако мы можем интимно подключить Bluetooth через оповещение, как показано ниже в методе RCT_EXPORT_METHOD (начало: (NSDictionary *)).

if(peripheralManager.state < CBPeripheralManagerStatePoweredOn){
{
     //Show alert to user to turn on the Bluetooth and it will go to 
       peripheralManagerDidUpdateState when user turn it on.
}
else
{
     //You can continue setting up the data.
}