iosswiftbluetoothcore-bluetoothcbcentralmanager

Receive string data via Bluetooth


I'm creating a simple BLE app that communicates with a single peripheral. The phone acts as the central. I have an iPad which I'm using as the peripheral for testing. It has the app LightBlue installed to simulate a peripheral. The peripheral is supposed to send a string of data in this format.

TEM:25.11 | HUM:70 | PM10:43 | PM25:32

So I created a blank virtual peripheral in LightBlue with one service.

enter image description here

enter image description here

Below is my code for Bluetooth connectivity handling.

import UIKit
import CoreBluetooth

class ViewController: UIViewController {

    fileprivate let serviceUUID = CBUUID(string: "19B10010-E8F2-537E-4F6C-D104768A1214")
    fileprivate let characteristicUUID = CBUUID(string: "19B10011-E8F2-537E-4F6C-D104768A1214")

    fileprivate var manager: CBCentralManager!
    fileprivate var peripheral: CBPeripheral!
    fileprivate var characteristic: CBCharacteristic!

    override func viewDidLoad() {
        super.viewDidLoad()
        manager = CBCentralManager(delegate: self, queue: nil)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        stopScan()
    }

    fileprivate func startScan() {
        manager.scanForPeripherals(withServices: [serviceUUID], options: nil)
    }

    fileprivate func stopScan() {
        manager.stopScan()
    }

    fileprivate func disconnectFromDevice() {
        guard let peripheral = peripheral else { return }
        manager.cancelPeripheralConnection(peripheral)
    }

    fileprivate func restoreCentralManager() {
        manager.delegate = self
    }

}

// MARK: - CBCentralManagerDelegate
extension ViewController: CBCentralManagerDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .unsupported:
            print("Unsupported")
        case .unauthorized:
            print("Unauthorized")
        case .poweredOn:
            print("Powered On")
            startScan()
        case .resetting:
            print("Resetting")
        case .poweredOff:
            print("Powered Off")
        case .unknown:
            print("Unknown")
        }
    }

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        print("Discovered \(String(describing: peripheral.name)) at \(RSSI)")

        if peripheral.name == nil || peripheral.name == "" {
            return
        }

        if self.peripheral == nil || self.peripheral.state == .disconnected {
            stopScan()

            self.peripheral = peripheral
            central.connect(peripheral, options: nil)
        }
    }

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        peripheral.delegate = self
        peripheral.discoverServices([serviceUUID])
    }

    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {

        self.peripheral = nil
        central.scanForPeripherals(withServices: nil, options: nil)
    }

    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        self.peripheral = nil
    }

    func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
    }
}

// MARK: - CBPeripheralDelegate
extension ViewController: CBPeripheralDelegate {
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        guard let services = peripheral.services else { return }
        print("No. of services: \(services.count)")

        for service in services {
            print(service.uuid)
            if service.uuid == serviceUUID {
                peripheral.discoverCharacteristics(nil, for: service)
            }
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard let characteristics = service.characteristics else { return }

        for characteristic in characteristics {
            print("characteristic: \(characteristic.uuid)")
            if characteristic.uuid == characteristicUUID {
                self.characteristic = characteristic
                peripheral.setNotifyValue(true, for: characteristic)
                peripheral.readValue(for: characteristic)
            }
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        print(error)
    }

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {        
        if characteristic.uuid == characteristicUUID {
            print("Got reply from: \(characteristic.uuid)")
            print(characteristic.value)
            if let data = characteristic.value, let string = String(data: data, encoding: String.Encoding.utf8) {
                print(string)
            } else {
                print("No response!")
            }
        }
    }

}

The discovering and connecting part works just fine. The problem is I don't receive that data string from the peripheral.

The method peripheral(_:didUpdateValueFor:error:) does get fired. I get the Got reply from: 19B10011-E8F2-537E-4F6C-D104768A1214 output in the console. However when I tried to see if there's any data by printing out the characteristic.value, it returns nil.

Not sure if it's something wrong with my code. Or I've configured the peripheral on LightBlue wrong. Does LightBlue send out data automatically? I don't see any Send button or anything anywhere either.

I uploaded a demo project here as well.


Solution

  • Your LightBlue configuration showing that you can only write value on that particular characteristic You have to make this read also