2

I'm trying to set up my iPhone to act as a heart rate monitor and send information using the standard heart rate service so that the app I have running on my PC can retrieve the data. I'm a newbie to iOS but I have got bluetooth stuff running on Android and Windows before. I'm following the information here and I'm falling at the first hurdle...

As specified in the doc, I first call

CBPeripheralManager *myPeripheralManager = [[CBPeripheralManager alloc] initWithDelegate:(id<CBPeripheralManagerDelegate>)self queue:nil options:nil];

I also implement the peripheralManagerDidUpdateState callback but here lies the problem. This callback never runs! I have waited for minutes and nothing has happened. I suspect I'm missing a very simple step because I have searched all day and have found nothing online! Can anyone explain the explicit steps to take here both in code and in physically doing stuff with my iPhone? Does it have to have a connection before this callback runs? Does it have to be paired to anything?

Here's the full code. It all runs fine, I'm getting no errors at all.

#import "ViewController.h"
@import CoreBluetooth;

@interface ViewController()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    NSLog(@"App running...");

    //Create the peripheral manager
    CBPeripheralManager *myPeripheralManager = [[CBPeripheralManager alloc] initWithDelegate:(id<CBPeripheralManagerDelegate>)self queue:nil options:nil];
}

- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
    int state = peripheral.state;
    NSLog(@"Peripheral manager state =  %d", state);

    //Set the UUIDs for service and characteristic
    CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"];
    CBUUID *heartRateCharacteristicUUID = [CBUUID UUIDWithString:@"2A37"];
    CBUUID *heartRateSensorLocationCharacteristicUUID = [CBUUID UUIDWithString:@"0x2A38"];


    //char heartRateData[2]; heartRateData[0] = 0; heartRateData[1] = 60;

    //Create the characteristics
    CBMutableCharacteristic *heartRateCharacteristic =
    [[CBMutableCharacteristic alloc] initWithType:heartRateCharacteristicUUID
                                       properties: CBCharacteristicPropertyNotify
                                            value:nil
                                      permissions:CBAttributePermissionsReadable];

    CBMutableCharacteristic *heartRateSensorLocationCharacteristic =
    [[CBMutableCharacteristic alloc] initWithType:heartRateSensorLocationCharacteristicUUID
                                       properties:CBCharacteristicPropertyRead
                                            value:nil
                                      permissions:CBAttributePermissionsReadable];
    //Create the service
    CBMutableService *myService = [[CBMutableService alloc] initWithType:heartRateServiceUUID primary:YES];
    myService.characteristics = @[heartRateCharacteristic, heartRateSensorLocationCharacteristic];

    //Publish the service
    NSLog(@"Attempting to publish service...");
    [peripheral addService:myService];

    //Set the data
    NSDictionary *data = @{CBAdvertisementDataLocalNameKey:@"iDeviceName",
                           CBAdvertisementDataServiceUUIDsKey:@[[CBUUID UUIDWithString:@"180D"]]};

    //Advertise the service
    NSLog(@"Attempting to advertise service...");
    [peripheral startAdvertising:data];

}

- (void)peripheralManager:(CBPeripheralManager *)peripheral
            didAddService:(CBService *)service
                    error:(NSError *)error {

    if (error) {
        NSLog(@"Error publishing service: %@", [error localizedDescription]);
    }
    else NSLog(@"Service successfully published");
}

- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
                                       error:(NSError *)error {

    if (error) {
        NSLog(@"Error advertising: %@", [error localizedDescription]);
    }
    else NSLog(@"Service successfully advertising");
}

@end
Michał Ciuba
  • 7,876
  • 2
  • 33
  • 59
Hester
  • 133
  • 1
  • 13

2 Answers2

5

The object myPeripheralManager is deallocated as soon as viewDidLoad method returns, as you have only one reference pointing to this object and it goes out of scope.

The solution is to create a property in ViewController which references the instance of CBPeripheralManager:

@interface ViewController()
@property (nonatomic, strong) CBPeripheralManager *myPeripheralManager;
@end

and then initialise the property in viewDidLoad:

self.myPeripheralManager = myPeripheralManager = [[CBPeripheralManager alloc] initWithDelegate:(id<CBPeripheralManagerDelegate>)self queue:nil options:nil];

You may want to read more about memory management (in particular, Automatic Reference Counting) in Objective-C.

Michał Ciuba
  • 7,876
  • 2
  • 33
  • 59
  • Thank you so much that did the trick. I did read some stuff in the tutorial about that but I guess I didn't fully understand the implications of not doing it!! Lesson learned :) – Hester Jul 07 '15 at 07:28
  • When to use a 'CBPeripheralManager' or `CBCentralManager`, what's the main difference between them? – Hemang Oct 24 '16 at 10:52
  • Yes you're right! In my case I use a local variable as a reference to the manager instead of a class attribute. – Javier Calatrava Llavería May 26 '17 at 07:51
1

My solution on this matter is:

import UIKit
import CoreBluetooth

class BluetoothManager: NSObject, CBPeripheralManagerDelegate {

    static let sharedInstance = BluetoothManager()

    var bluetoothPeripheralManager: CBPeripheralManager?


    override fileprivate init() {
    }

    func start() {
        bluetoothPeripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: nil)

    }

    // MARK - CBCentralManagerDelegate
    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {

        var statusMessage = ""

        switch peripheral.state {
        case .poweredOn:
            statusMessage = "Bluetooth Status: Turned On"

        case .poweredOff:
            statusMessage = "Bluetooth Status: Turned Off"

        case .resetting:
            statusMessage = "Bluetooth Status: Resetting"

        case .unauthorized:
            statusMessage = "Bluetooth Status: Not Authorized"

        case .unsupported:
            statusMessage = "Bluetooth Status: Not Supported"

        default:
            statusMessage = "Bluetooth Status: Unknown"
        }

        print(statusMessage)

        if peripheral.state == .poweredOff {
            //TODO: Update this property in an App Manager class
        }
    }}

Usage:

BluetoothManager.sharedInstance.start()

Start up your up and turn/off and bluetooth a log message should appear indicating bluetooth status.