Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS/Objective-C: BLE in Peripheral mode doesn't work

I'm trying to launch BLE in both Central and Peripheral modes. With hardcoded variables for now for sake of simplicity.
I think i've implemented everything according to the docs.

I can check if Peripheral mode is working using Android smartphone (api-19, do not support Peripheral mode). iPhone shows up correctly when i'm using MyBeacon app, for example.

However it doesn't show up when i'm running this code in my app:

Here is .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

And .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

None of the event listeners above are firing, except NSLog(@"Started");

I've got this suggestion:

On a related subject, make sure that whatever code creates and executes your BTManager allows the object to live for long enough for the bluetooth actions to be performed. If it goes out of scope or gets garbage collected, you'll have the same problem.

I do not know how to check if this is true or not.

Also, this tutorial is using this method:

- (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];
    }
}

... but the docs contain nothing related to this CBPeripheralManagerStatePoweredOn or so. The article is 4 years old, so it may be not even relevant now.

Oh, yeah, and also i'm barely familiar with objective-c, so the mistake there may be very simple.

Thank you for reading till here :)

Update 1

Apparently, viewDidLoad never being initiated. Therefore I've moved code from it to start method, and code from start into ...DidUpdateStates methods, as @LarsBlumberg suggested.

#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

Now everything goes ok till state updates. Both central.state and peripheral.state returns 4, while CB...ManagerStatePoweredOn is equal to 5. How do i make it equal to 5? App is requesting access to bt, but iphone is not enabling it. When it's enabled manually, everything seems work well.

like image 226
stkvtflw Avatar asked Mar 11 '17 18:03

stkvtflw


1 Answers

You are probably adding the service transferService too early to the peripheral manager and you are also probably starting the advertisement too early.

You have to wait until peripheralManagerDidUpdateState reports that your peripheral manager's state has reached CBPeripheralManagerStatePoweredOn.

This said, you can either move the code from RCT_EXPORT_METHOD(start:(NSDictionary *)options) into your empty peripheralManagerDidUpdateState implementation.

Alternatively, if you only want to add services to your peripheralManager and then start advertising when somebody has called your start method (looks like you're creating a React Native binding?), make sure that self.peripheralManager.state is CBPeripheralManagerStatePoweredOn within your start method. This however requires the caller of start (your React Native JS code) to not call this method too early.

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");
}

Similarly you can only start scanning for peripherals with your centralManager if it has the "powered on" state.

like image 178
Lars Blumberg Avatar answered Sep 28 '22 22:09

Lars Blumberg