3

I am trying to make an app that will be used as a main control for a bluetooth watch(e.g. fitness bracelets, smart watches). I have done my research about this, and although some people managed to do so, they don't give many details about the process. Below are a few of the "solutions" that I found:

Is it possible to switch central and peripheral each other on iOS?

Can iOS do central and peripheral work on same app at same time?

Peripheral and central at the same time on iOS

All of these are in Objective-C and although I am familiar with it, the posts are 3+ years old so things have changed concerning the code. Another problem is that I need to use the app with another Bluetooth device, not an iOS device as the ones above are doing it, and for the moment the connection request can only come from the iPhone, not from the bluetooth device.

The question is if it's possible to achieve the desired result, and if so, what would be the best way to do it? So far, one of the proposed solutions was to connect to the device, acquire the UUID and then switch the iPhone to peripheral mode so that it can advertise it's services. That is not possible(in my opinion), at least in this current stage.

iOS already has a predefined service that can be discovered and accessed by the device (Current Time Service) when the 2 of them connect, without any modifications from my part so there should be a way to accomplish this.

I hope I made myself clear enough about the problem, if you believe I can add more details to clarify the context, let me know. Thanks for your time.

I am posting below some of the key code from the view in which I discover peripherals:

 override func viewDidAppear(_ animated: Bool) {

    manager = CBCentralManager(delegate: self, queue: nil)
    peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
    peripherals = []

    if (manager?.state == CBManagerState.poweredOn) {
       scanBLEDevices()
    self.tableView.reloadData()
    }
}
 func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
    switch(peripheral.state)
    {
    case.unsupported:
        print("Peripheral is not supported")
    case.unauthorized:
        print("Peripheral is unauthorized")
    case.unknown:
        print("Peripheral is Unknown")
    case.resetting:
        print("Peripheral is Resetting")
    case.poweredOff:
        print("Peripheral service is powered off")
    case.poweredOn:
        print("Peripheral service is powered on")
        print("Start advertising.")

        let serviceUUID:CBUUID = CBUUID(string: self.service_uuid_string)
        let locationUUID:CBUUID = CBUUID(string: self.location_and_speed)

        // Start with the CBMutableCharacteristic
        self.locationCharacteristic = CBMutableCharacteristic(type: locationUUID, properties: .notify , value: nil, permissions: .readable)

        // Then the service
        let locationService = CBMutableService(type: serviceUUID, primary: true)

        // Add the characteristic to the service
        locationService.characteristics?.append(locationCharacteristic!)

        // And add it to the peripheral manager
        self.peripheralManager?.add(locationService)
        peripheralManager?.startAdvertising([CBAdvertisementDataServiceUUIDsKey : serviceUUID])
    }

}
  • It isn't really clear what your question is, but the short answer is yes, and ios app can act as a peripheral and a central at the same time. Your watch can also connect to services offered by iOS itself, such as ANCS, at the same time as it is connected as a peripheral to your app. – Paulw11 Jan 23 '18 at 20:28
  • I was asking if it is possible and how can it be done? How can an app act as a peripheral and a central at the same time in Swift. Since you said it can be done, the second part of my question is how to do it. Thank you. – Adrian Radulescu Jan 29 '18 at 14:32
  • You just write the code. Create an instance of `CBCentralManager` and advertise a peripheral via `CBPeripheralManager`. – Paulw11 Jan 29 '18 at 20:21
  • Someone said i have to switch between central and peripheral depending on what I want to do(advertise or discover), but it doesn't seem to work for me because the device can't see my service. I need a general flow of how to do that. I discover, connect to the device, then switch to peripheral? Or switch before? On the other questions, people gave a general idea of how to implement that. – Adrian Radulescu Jan 30 '18 at 13:56
  • If you consider just one pair of devices then you typically need one to be the central and one to be peripheral just so you know who is going to advertise and who is going to connect, but the chipset in an iOS device can act in both roles at the same time as can an iOS app. Once you have discovered and connected to the device why would you want to switch roles? You already have a connection. Your question isn’t clear as to why you want to switch roles. – Paulw11 Jan 30 '18 at 19:26
  • Can I expose services from my phone while it is scanning and/or connecting? Because the BLE device is checking for those services at the moment someone connects to it, and from what I've read, the phone has to be either central or peripheral, not both at the same time. – Adrian Radulescu Jan 31 '18 at 08:48
  • No, you can do both at the same time. – Paulw11 Jan 31 '18 at 08:50
  • As I do it now, I create an instance of *CBCentralManager*, I scan for devices, create an instance of *CBPeripheralManager*, I create the service and characteristic and advertise it. Then I connect to the device. With all these, the device does not find my service, it only finds the Time Service – Adrian Radulescu Jan 31 '18 at 09:02
  • You need to show some code. There is no reason you can't advertise the service at the same time as you are scanning with the CBCentral. – Paulw11 Jan 31 '18 at 09:10
  • `func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { let serviceUUID:CBUUID = CBUUID(string: self.service_uuid_string) self.locationCharacteristic = CBMutableCharacteristic(type: locationUUID, properties: .notify , value: nil, permissions: .readable) let locationService = CBMutableService(type: serviceUUID, primary: true) locationService.characteristics?.append(locationCharacteristic!) self.peripheralManager?.add(locationService) peripheralManager?.startAdvertising([CBAdvertisementDataServiceUUIDsKey : serviceUUID])}` – Adrian Radulescu Jan 31 '18 at 09:24
  • The code is not properly formatted because i have a limit of characters and I can't use the chat because I don't have enough reputation. In the same view I have both `PeripheralManager` and `CentralManager`. I also omitted `let locationUUID:CBUUID = CBUUID(string: self.location_and_speed)` so that is not a problem – Adrian Radulescu Jan 31 '18 at 09:28
  • Edit your question to include your code – Paulw11 Jan 31 '18 at 09:36
  • I added the code. – Adrian Radulescu Jan 31 '18 at 09:45
  • You need to ensure your peripheral manager is in the powered on state before advertising. – Paulw11 Jan 31 '18 at 09:46
  • I edited to include the verification of the powered on state. – Adrian Radulescu Jan 31 '18 at 12:20
  • You need to hold your CBMutableService in a property – Paulw11 Jan 31 '18 at 12:25
  • @Paulw11 i don't know any other way to reach you, I have something quite important to ask and there doesn't seems to be an answer. This is the partial answer: https://stackoverflow.com/questions/42799129/how-do-you-convert-a-hex-to-signed-float-in-swift-works-fine-with-positive-numb I need to do the opposite: convert from float value to a hex value like the initial one. For example, if I have the value 45.77, the string would be 0x4237147b. – Adrian Radulescu Feb 08 '18 at 14:22

1 Answers1

0

I'm getting back with the correct way of achieving the required functionality. After initialising the peripheralManager, create a CBMutableService and hold a reference to it(declared at the top of the class).

var globalService:CBMutableService? = nil

Next step is to check for the peripheralManager state, and do all the required work after you receive the poweredOn state:

func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
    switch(peripheral.state)

case.poweredOn:
        print("Peripheral service is powered on")

        createServiceWithCharacteristics()
}


func createServiceWithCharacteristics(){

    let serviceUUID:CBUUID = CBUUID(string: self.service_uuid_string)
    let featureCharacteristicUUID:CBUUID = CBUUID(string: self.feature_characteristic_uuid_string)

    // Start with the CBMutableCharacteristic
    let permissions: CBAttributePermissions = [.readable, .writeable]
    let properties: CBCharacteristicProperties = [.notify, .read, .write]

    self.featureCharacteristic = CBMutableCharacteristic(type: featureCharacteristicUUID, properties: properties , value: nil, permissions: permissions)

    // Then the service
    let localService = CBMutableService(type: serviceUUID, primary: true)

    // Add the characteristic to the service
    localService.characteristics = [featureCharacteristic!]
    globalService = localService

    // And add it to the peripheral manager
    self.peripheralManager?.add(globalService!)
    print("Start advertising.")
    peripheralManager?.startAdvertising([CBAdvertisementDataLocalNameKey:"Name"])
}