0
votes

I am creating an iOS-application that for now should transfer text messages between two iOS-devices. I want to/have to use Core Bluetooth for that. One device is becoming the peripheral and one the central.

The setup of the peripheral with one service containing two characteristics (1 read, 1 write) is no problem. I used the LightBlue App to connect to the peripheral and i also can see its characteristics.

When i use the central role device i find the peripheral, can connect and also see its characteristics. I store the peripheral and the characteristic both in own variables and use them later to call the writeValue(_ data: Data, for characteristic: CBCharacteristic, type: CBCharacteristicWriteType) of the stored peripheral which throws no error. But the peripheral doesn't seem to get that message because none of its didWriteValueFor or didUpdateValueFor methods are called. The central cannot subscribe to the characteristics too.

Also when i use the peripheral to update the value of its own characteristic, my check with the LightBlue App is not successful because the value always stays empty and the central doesn't get notified too of course.

It looks like everything that comes out of the application doesn't work. Only the data coming in (f.e. from the LightBlue App) seems to work.

Has anyone ever had a similar problem? I used many different tutorials and did exactly what is described there (because it was pretty much the same procedure everywhere).

Thanks in advance!


I use XCode Version 10.1, code in Swift and use an iPhone XS Max (iOS 12.1) and an iPad Air 2 (iOS 12.0) for testing.


Here is my peripheral class:

import CoreBluetooth
import UIKit

class BLEPeripheralManager: NSObject {

    //MARK:- Properties

    static let shared = BLEPeripheralManager()

    //just some delegates for other classes
    var peripheralDataReceiver: PeripheralDataReceiver?
    var peripheralChatDataReceiver: PeripheralChatDataReceiver?

    var peripheralManager: CBPeripheralManager

    var subscribedCentrals: [CBCentral] = []

    var readCharacteristic: CBMutableCharacteristic = {
        let uuidStringChar1 = UUIDs.characteristicUUID1
        let uuidChar1 = CBUUID(string: uuidStringChar1)
        let char = CBMutableCharacteristic(type: uuidChar1, properties: .read, value: nil, permissions: .readable)
        return char
    }()
    var writeCharacteristic: CBMutableCharacteristic = {
        let uuidStringChar2 = UUIDs.characteristicUUID2
        let uuidChar2 = CBUUID(string: uuidStringChar2)
        let char = CBMutableCharacteristic(type: uuidChar2, properties: .write, value: nil, permissions: .writeable)
        return char
    }()


    //MARK:- Private Methods

    private override init() {
        self.peripheralManager = CBPeripheralManager(delegate: nil, queue: nil)
        super.init()
        self.peripheralManager.delegate = self
    }

    private func setupManager() {
        let uuidStringServ = UUIDs.serviceUUID
        let uuidServ = CBUUID(string: uuidStringServ)

        let transferService = CBMutableService(type: uuidServ, primary: true)
        transferService.characteristics = [self.readCharacteristic, self.writeCharacteristic]

        self.peripheralManager.add(transferService)
    }

    private func teardownServices() {
        self.peripheralManager.removeAllServices()
    }

    private func clearSubscribers() {
        self.subscribedCentrals.removeAll()
    }

    //MARK:- Public Methods

    public func sendMessage(fromPeripheral peripheral: String, text: String) {
        if text.isEmpty { return }
        let chatMessage = ChatMsg(messageText: text, fromDevice: peripheral)
        let encoder = JSONEncoder()
        do {
            let data = try encoder.encode(chatMessage)
            print(self.readCharacteristic.uuid)
            if self.peripheralManager.updateValue(data, for: self.readCharacteristic, onSubscribedCentrals: nil) == false {
                print("Update from Peripheral failed (ReadCharacteristic)")
            } else {
                print("Message sent (ReadCharacteristic)")
            }
            if self.peripheralManager.updateValue(data, for: self.writeCharacteristic, onSubscribedCentrals: nil) == false {
                print("Update from Peripheral failed (WriteCharacteristic)")
            } else {
                print("Message sent (WriteCharacteristic)")
            }
        } catch {
            print("Error in encoding data")
        }
    }

    func startAdvertising() {
        let services = [CBUUID(string: UUIDs.serviceUUID)]
        let advertisingDict = [CBAdvertisementDataServiceUUIDsKey: services]

        self.peripheralManager.startAdvertising(advertisingDict)
    }

    public func stopAdvertising() {
        self.peripheralManager.stopAdvertising()
    }

    public func checkIfAdvertising() -> Bool {
        return self.peripheralManager.isAdvertising
    }
}


extension BLEPeripheralManager: CBPeripheralManagerDelegate {

    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
        switch peripheral.state {
        case .poweredOff:
            print("Peripheral powered off")
            self.teardownServices()
            self.clearSubscribers()
        case .poweredOn:
            print("Peripheral powered on")
            self.setupManager()
        case .resetting:
            print("Peripheral resetting")
        case .unauthorized:
            print("Unauthorized Peripheral")
        case .unknown:
            print("Unknown Peripheral")
        case .unsupported:
            print("Unsupported Peripheral")
        }
    }

    //doesn`t get called
    func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
        for request in requests {
            if let value = request.value {
                if let messageText = String(data: value, encoding: String.Encoding.utf8) {
                    //
                }
            }
        }
    }

    //doesn`t get called
    func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
        var shouldAdd: Bool = true
        for sub in self.subscribedCentrals {
            if sub == central {
                shouldAdd = false
            }
        }
        if shouldAdd { self.subscribedCentrals.append(central) }
    }

}

Here is my central class:

import CoreBluetooth
import UIKit


class BLECentralManager: NSObject {

    static let shared = BLECentralManager()

    //just some delegates for other classes
    var centralDataReceiver: CentralDataReceiver?
    var centralChatDataReceiver: CentralChatDataReceiver?

    var centralManager: CBCentralManager

    var peripheralArray: [CBPeripheral] = []

    var writeTransferPeripheral: (peripheral: CBPeripheral, characteristic: CBCharacteristic)?
    var readTransferPeripheral: (peripheral: CBPeripheral, characteristic: CBCharacteristic)?

    private override init() {
        self.centralManager = CBCentralManager(delegate: nil, queue: nil, options: nil)
        super.init()
        self.centralManager.delegate = self
    }

    private func startScan() {
        self.centralManager.scanForPeripherals(withServices: [CBUUID(string: UUIDs.serviceUUID)], options: nil)
        //self.centralManager.scanForPeripherals(withServices: nil, options: nil)
    }

    public func connectTo(index: Int) {
        self.centralManager.connect(self.peripheralArray[index], options: nil)
    }

    public func sendMessage(fromPeripheral peripheral: String, text: String) {
        let chatMsg = ChatMsg(messageText: text, fromDevice: peripheral)
        let encoder = JSONEncoder()

        do {
            let data = try encoder.encode(chatMsg)
            self.writeTransferPeripheral?.peripheral.writeValue(data, for: (self.writeTransferPeripheral?.characteristic)!, type: .withoutResponse)
        } catch {
            print("Error in encoding data")
        }
    }

    public func getActiveConnections() -> String {
        var connString: String = ""
        let conns = self.centralManager.retrieveConnectedPeripherals(withServices: [CBUUID(string: UUIDs.serviceUUID)])
        for peri in conns {
            if connString == "" {
                connString = "\(peri)"
            } else {
                connString = "\(connString), \(peri)"
            }
        }
        return connString
    }

    public func getMessages() {
        self.readTransferPeripheral?.peripheral.readValue(for: (self.readTransferPeripheral?.characteristic)!)
    }

    public func lookForPeripherals() {
        self.peripheralArray.removeAll()
        self.startScan()
    }

    public func getPeripherals() -> [CBPeripheral] {
        return self.peripheralArray
    }

    private func getNameOfPeripheral(peripheral: CBPeripheral) -> String {
        if let name = peripheral.name {
            return name
        } else {
            return "Device"
        }
    }
}


extension BLECentralManager: CBCentralManagerDelegate, CBPeripheralDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .poweredOff:
            print("BLE has powered off")
            centralManager.stopScan()
        case .poweredOn:
            print("BLE is now powered on")
            self.startScan()
        case .resetting:
            print("BLE is resetting")
        case .unauthorized:
            print("Unauthorized BLE state")
        case .unknown:
            print("Unknown BLE state")
        case .unsupported:
            print("This platform does not support BLE")
        }
    }

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        self.peripheralArray.append(peripheral)
    }

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        self.centralDataReceiver?.connectionEstablished(peripheral: peripheral)
        print("Connection Established")
        peripheral.delegate = self
        peripheral.discoverServices(nil)
    }

    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        self.centralDataReceiver?.connectionTornDown()
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        for service in peripheral.services! {
            peripheral.discoverCharacteristics(nil, for: service)
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        for characteristic in service.characteristics! {
            print(characteristic.uuid)
            let char = characteristic as CBCharacteristic
            if char.uuid.uuidString == UUIDs.characteristicUUID2 {
                self.writeTransferPeripheral?.peripheral = peripheral
                self.writeTransferPeripheral?.characteristic = char
                self.writeTransferPeripheral?.peripheral.setNotifyValue(true, for: (self.writeTransferPeripheral?.characteristic)!)
            } else if char.uuid.uuidString == UUIDs.characteristicUUID1 {
                self.readTransferPeripheral?.peripheral = peripheral
                self.readTransferPeripheral?.characteristic = char
                self.readTransferPeripheral?.peripheral.setNotifyValue(true, for: (self.readTransferPeripheral?.characteristic)!)
            }
        }
    }

    //doesn`t get called
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if let data = characteristic.value {
            if data.isEmpty { return }
            if let text = String(data: characteristic.value!, encoding: String.Encoding.utf8) {
                self.centralChatDataReceiver?.receiveMessage(fromPeripheral: self.getNameOfPeripheral(peripheral: peripheral), text: text)
            }
        }
    }

    //doesn`t get called
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if let data = characteristic.value {
            if data.isEmpty { return }
            if let text = String(data: characteristic.value!, encoding: String.Encoding.utf8) {
                self.centralChatDataReceiver?.receiveMessage(fromPeripheral: self.getNameOfPeripheral(peripheral: peripheral), text: text)
            }
        }
    }

    //doesn`t get called
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        print("Notification state changed")
    }

}
1
You should show your code for your peripheral side, but it sounds like you are using the wrong delegate methods. You need to implement the peripheralManager(_:didReceiveWrite:) method of the CBPeripheralManagerDelegate protocolPaulw11
I did implement the peripheralManager(:didReceiveWrite:) method. I only wrote the wrong method names in my question - sorry. The ones i wrote are the methods of the CBPeripheralDelegate not CBPeripheralManagerDelegate. But anyways... also the peripheralManager(:didReceiveWrite:) method only gets called when i write the value with the LightBlue App, not when my "custom central" is updating the value.SilasS.
Then, as per my original comment, please show your code. To be blunt, Core Bluetooth works, so there is a problem in your code. Without seeing your code we cannot help.Paulw11
I added the code of my central and peripheral classes. Its pretty much like in every tutorial i found online (besides of my custom classes). The only thing that seems a bit weird to me is storing the peripherals and its characteristics in variables and then using them to write values or subscribe. But i don't know another way to do that...SilasS.
It doesn't make sense to enable notification on a characteristic that you are going to write to. Also, your read characteristic doesn't support notification anyway; you need to add notify to the property set in your cbmutablecharacteristic.Paulw11

1 Answers

0
votes

I found out what's wrong on central side:

After discovering i store the peripheral and its characteristics in two variables/tuples (writeTransferPeripheral & readTransferPeripheral). I did this the wrong way:

self.writeTransferPeripheral?.peripheral = peripheral
self.writeTransferPeripheral?.characteristic = char

This way the variable was still nil when using it later to write value. Seems like you should set the value of a tuple like this:

self.writeTransferPeripheral = (peripheral, char)