I am implementing a CentralManager with the CoreBluetooth framework in order to extract values provided by peripherals.
I first look for peripherals that have a service with a specific UUID ("52486FA8-FF0B-4A91-A344-D642D0E91AD0"). The CentralManager finds the peripheral but when I try to print the Services of that peripheral, I get an empty array.
Here is my code
//
// ViewController.swift
// centralLearn
//
// Created by Francesco Vanduynslager on 24/09/2016.
// Copyright © 2016 Francesco Vanduynslager. All rights reserved.
//
import UIKit
import CoreBluetooth
class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
var centralManager: CBCentralManager!
var discoveredPeripherals: [CBPeripheral]!
let serviceUUID = CBUUID(string:"52486FA8-FF0B-4A91-A344-D642D0E91AD0")
override func viewDidLoad() {
super.viewDidLoad()
centralManager = CBCentralManager(delegate: self, queue: nil)
discoveredPeripherals = []
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
print("Central Started")
centralManager.scanForPeripherals(withServices: [serviceUUID],
options: nil)
} else if central.state == .poweredOff {
centralManager.stopScan()
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("Discovered Peripheral: \(peripheral.name)")
self.discoveredPeripherals.append(peripheral)
centralManager.connect(peripheral, options: nil)
centralManager.stopScan()
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Connected to peripheral!! \(peripheral)")
print("count: \(peripheral.services?.count)")
peripheral.delegate = self
if (peripheral.services == nil) {
peripheral.discoverServices([serviceUUID])
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if error != nil {
print("Discover service Error: \(error)")
} else {
print("Discovered Service")
for service in peripheral.services!{
print("SERV: \(service)")
peripheral.discoverCharacteristics([CBUUID(string: "5A5E5393-4505-471C-BA90-7AD044FFFD9C")], for: service)
}
print(peripheral.services)
print("DONE")
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
let characteristic = service.characteristics?[0]
print("Discovered Characteristic")
peripheral.readValue(for: characteristic!)
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if error != nil {
print("Read value service Error: \(error)")
} else {
print("Value of Characteristic: \(characteristic.value)")
}
}
}
And the resulting prints are as follow:
Central Started
Discovered Peripheral: Optional("-ShEsKo-")
Connected to peripheral!! <CBPeripheral: 0x1740f7d00, identifier = A9B3F888-99E1-C62B-DF93-87F1F99AE847, name = -ShEsKo-, state = connected>
count: nil
Discovered Service
Optional([])
DONE
EDIT
Here is my peripheral code:
//
// ViewController.swift
// peripheralLearn
//
// Created by Francesco Vanduynslager on 24/09/2016.
// Copyright © 2016 Francesco Vanduynslager. All rights reserved.
//
import UIKit
import CoreBluetooth
import CoreLocation
class ViewController: UIViewController, CBPeripheralManagerDelegate {
var localBeacon: CLBeaconRegion!
var beaconPeripheralData: NSDictionary!
var peripheralManager: CBPeripheralManager!
var services: [CBMutableService]!
override func viewDidLoad() {
super.viewDidLoad()
initLocalBeacon()
// Do any additional setup after loading the view, typically from a nib.
}
func initLocalBeacon() {
if localBeacon != nil {
stopLocalBeacon()
}
let localBeaconUUID = "B65D79F6-74A2-482F-A669-FA5AB35CD3B8"
let localBeaconMajor: CLBeaconMajorValue = 123
let localBeaconMinor: CLBeaconMinorValue = 456
let uuid = UUID(uuidString: localBeaconUUID)!
localBeacon = CLBeaconRegion(proximityUUID: uuid, major: localBeaconMajor, minor: localBeaconMinor, identifier: "Your private identifer here")
beaconPeripheralData = localBeacon.peripheralData(withMeasuredPower: nil)
peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: nil)
/// FIRST SERVICE
let serviceUUID = CBUUID(string: "52486FA8-FF0B-4A91-A344-D642D0E91AD0")
let characteristicUUID = CBUUID(string: "5A5E5393-4505-471C-BA90-7AD044FFFD9C")
let characteristic = CBMutableCharacteristic(type: characteristicUUID,
properties: .read,
value: "hello".data(using: .utf8),
permissions: .readable)
let service = CBMutableService(type: serviceUUID, primary: true)
service.characteristics = [characteristic]
services=[service]
peripheralManager.add(service)
}
func stopLocalBeacon() {
peripheralManager.stopAdvertising()
peripheralManager = nil
beaconPeripheralData = nil
localBeacon = nil
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state == .poweredOn {
print("peripheral Started")
// peripheralManager.startAdvertising(beaconPeripheralData as! [String: AnyObject]!)
peripheralManager.startAdvertising([CBAdvertisementDataServiceUUIDsKey: [services[0].uuid, services[1].uuid]])
} else if peripheral.state == .poweredOff {
peripheralManager.stopAdvertising()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
if (error != nil){
print("ERROR starting advertising")
}else{
print("Did Start advertising")
}
}
func peripheralManager(_ peripheral: CBPeripheralManager, willRestoreState dict: [String : Any]) {
print("Restoring state")
}
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
if (error != nil) {
print("ERROR adding service: \(error)")
}else{
print("Service added: \(service)")
}
}
}
Your problem is in your peripheral code. You can't add the service to the CBMutablePeripheral until the CBPeripheralManager is in the powered on state.
I suggest you move your service to its own function and call this function when you are in the powered on state.
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state == .poweredOn {
print("peripheral Started")
// peripheralManager.startAdvertising(beaconPeripheralData as! [String: AnyObject]!)
self.setupService()
} else if peripheral.state == .poweredOff {
peripheralManager.stopAdvertising()
}
}
func setupService() {
let serviceUUID = CBUUID(string: "52486FA8-FF0B-4A91-A344-D642D0E91AD0")
let characteristicUUID = CBUUID(string: "5A5E5393-4505-471C-BA90-7AD044FFFD9C")
let characteristic = CBMutableCharacteristic(type: characteristicUUID,
properties: .read,
value: "hello".data(using: .utf8),
permissions: .readable)
let service = CBMutableService(type: serviceUUID, primary: true)
service.characteristics = [characteristic]
services=[service]
peripheralManager.add(service)
peripheralManager.startAdvertising([CBAdvertisementDataServiceUUIDsKey: [services[0].uuid]])
}
Related
I am trying to develop bluetooth based application that will provide users the functionality to share data via bluetooth pairing. I am facing issue while pairing the device.
This is my scenario
The user will login the application and will be prompted to enable bluetooth service. This is the code for the same :-
//MARK: - DECLARATIONS
var cbCentralManager : CBCentralManager?
var peripheral : CBPeripheral?
var peripherals: [CBPeripheral] = []
//MARK: - VIEW_METHODS
override func viewDidLoad() {
super.viewDidLoad()
self.setUpView()
}
func setUpView() {
cbCentralManager = CBCentralManager(delegate: self, queue: nil)
}
//MARK: - EXTENSION
extension ViewController : CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
print("Bluetooth is enabled..")
central.scanForPeripherals(withServices: nil, options: nil)
} else {
print("Bluetooth is not enabled..")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
guard peripheral.name != nil else {return}
print("Sensor Found!")
//stopScan
cbCentralManager?.stopScan()
//connect
cbCentralManager?.connect(peripheral, options: nil)
self.peripheral = peripheral
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
//discover all service
peripheral.discoverServices(nil)
peripheral.delegate = self
}
In the next step, on the click of button, near by BLE devices will be scanned.
func startScan() {
let options: [String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey:
NSNumber(value: false)]
peripherals = []
print("Now Scanning...")
self.timer.invalidate()
centralManager?.scanForPeripherals(withServices: nil, options: options)
Timer.scheduledTimer(timeInterval: 17, target: self, selector: #selector(self.cancelScan), userInfo: nil, repeats: false)
}
After the user selects a device to be paired from the list of scanned BLE devices, below code will be executed for establishing connection between devices.
func connectToDevice(device:CBPeripheral) {
centralManager = CBCentralManager(delegate: self, queue: .main)
self.blePeripheral = device
self.blePeripheral?.delegate = self
centralManager?.connect(self.blePeripheral!, options: nil)
}
The delegate methods are extended
extension HomeVC : CBPeripheralDelegate {
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state == .poweredOn {
return
}
print("Peripheral manager is running")
}
//Check when someone subscribe to our characteristic, start sending the data
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
print("Device subscribe to characteristic")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
print("*******************************************************")
if ((error) != nil) {
print("Error discovering services: \(error!.localizedDescription)")
return
}
guard let services = peripheral.services else {
return
}
//We need to discover the all characteristic
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
// bleService = service
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if ((error) != nil) {
print("Error discovering services: \(error!.localizedDescription)")
return
}
guard let services = peripheral.services else {
return
}
//We need to discover the all characteristic
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
// bleService = service
}
print("Discovered Services: \(services)")
}
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
if let error = error {
print("\(error)")
let errorStr = "\(error)"
let alert = UIAlertController(title: "Alert", message: errorStr, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
return
}
}
}
I am stuck as I am not getting the alert for pairing devices. Can anyone guide me in this ?
Thanks in advance.
I am creating an app for BLE connection for iOS.
I can connect to the peripheral from central (iPhone6: iOS12.9) and send commands with
I am able to send commands with writevalue.
https://developer.apple.com/documentation/corebluetooth/cbperipheral/1518949-setnotifyvalue
In the above setNotifyValue, there is a description that seems to be accepted by indicate.
The following methods of didUpdateValueFor do not return.
/// When changing the characteristic
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
https://developer.apple.com/documentation/corebluetooth/cbperipheraldelegate/1518708-peripheral
If you know how to implement receiving data in indicate, please let me know.
The sample code is shown below.
I'm still working on it, so there may be some garbage code, sorry.
// ViewController.swift
import UIKit
import CoreBluetooth
import os
class ViewController: UIViewController {
/// https://qiita.com/eKushida/items/def628e0eff6c106d467
var serviceUUID : CBUUID!
var characteristicUUID : CBUUID!
var responseCharacteristicUUID : CBUUID!
var centralManager: CBCentralManager!
var peripheral: CBPeripheral!
var writeCharacteristic: CBCharacteristic!
var responsCharacteristic: CBCharacteristic!
var data = Data()
#IBOutlet weak var dispLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
setup()
dispLabel.text = "Startup"
}
/// Initialize the central manager and UUID
private func setup() {
// Create an object representing the UUID.
self.serviceUUID = CBUUID(string: "XXXXX0000-XXXX-XXXX-XXXX-XXXXXXXXXX")
self.characteristicUUID = CBUUID(string: "XXXX2001-XXXX-XXXX-XXXX-XXXXXXXXXXXX")
self.responseCharacteristicUUID = CBUUID(string: "XXXX2000-XXXX-XXXX-XXXX-XXXXXXXXXXXX")
}
/// Pairing process
#IBAction func scan(_ sender: UIButton) {
print("Pairing process")
dispLabel.text = "Pairing process pressed"
self.centralManager = CBCentralManager(delegate: self, queue: nil)
}
/// Communication connection
#IBAction func connect(_ sender: UIButton) {
print("Communication connection")
/// https://qiita.com/MashMorgan/items/32500f158cb08d565786
/// https://knkomko.hatenablog.com/entry/2019/07/16/013443
let message = "**COMMAND**"
let command = message + "\r"
let writeData = Data(command.utf8)
print("writeData:" + String(data: writeData, encoding: .utf8)!)
peripheral.writeValue(writeData, for: writeCharacteristic, type: CBCharacteristicWriteType.withResponse)
}
}
//MARK : - CBCentralManagerDelegate
extension ViewController: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
//wait for power on and scan
case CBManagerState.poweredOn:
let services: [CBUUID] = [serviceUUID] ///serviceUUID
centralManager.scanForPeripherals(withServices: nil, options: nil)
// centralManager.scanForPeripherals(withServices: services, options: nil)
print("isScanning:" + String(centralManager.isScanning))
default:
break
}
}
/// Called when a peripheral is discovered
func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String : Any],
rssi RSSI: NSNumber) {
self.peripheral = peripheral
print("peripheral.name:" + String(peripheral.name ? "") + " peripheral.id:" + peripheral.identifier.uuidString)
if "XXXXXXX" == peripheral.name {
//start connection
self.centralManager.connect(self.peripheral, options: nil)
//peripheral is found, stop scanning
centralManager.stopScan()
}
}
/// called when connected
func centralManager(_ central: CBCentralManager,
didConnect peripheral: CBPeripheral) {
print("Connection successful serviceUUID:" + serviceUUID.uuidString)
peripheral.delegate = self
peripheral.discoverServices([serviceUUID])
dispLabel.text = "Peripheral connection successful"
}
/// Called when the connection fails
func centralManager(_ central: CBCentralManager,
didFailToConnect peripheral: CBPeripheral,
error: Error?) {
print("Connection failed")
}
/// When disconnected
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("Disconnection: \(String(describing: error))")
}
}
//MARK : - CBPeripheralDelegate
extension ViewController: CBPeripheralDelegate {
/// Called when the characteristic is found
func peripheral(_ peripheral: CBPeripheral,
DidDiscoverCharacteristicsFor service: CBService,
error: Error?) {
if error ! = nil {
print(error.debugDescription)
return
}
guard let serviceCharacteristics = service.characteristics else {
// error handling
return
}
// Processing by characteristic
for characreristic in serviceCharacteristics {
if characreristic.uuid == characteristicUUID
{
// keep the characteristic for writing data
self.writeCharacteristic = characreristic
print("Write characreristic / UUID:" + characreristic.uuid.uuidString)
print("Write characreristic / properties: \(self.writeCharacteristic.properties)")
continue
}
if characreristic.uuid == responseCharacteristicUUID {
peripheral.setNotifyValue(true, for: characreristic)
self.responsesCharacteristic = characreristic
print("Responses characreristic / UUID:" + characreristic.uuid.uuidString)
print("Responses characreristic / properties: \(self.responsesCharacteristic.properties)")
continue
}
print("Other characreristic / UUID:" + characreristic.uuid.uuidString)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverIncludedServicesFor: CBService, error: Error?){
print("peripheral didDiscoverIncludedServicesFor")
}
/// When writing data to the characteristic (called when sending a command)
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
print("peripheral didWriteValueFor")
guard error == nil else {
print("Error when writing characteristic data: \(String(describing: error))")
// failure handling
return
}
print(characteristic.value)
}
func peripheral(peripheral: CBPeripheral,
didUpdateNotificationStateForCharacteristic characteristic: CBCharacteristic,
error: NSError?)
{
print("peripheral didUpdateNotificationStateForCharacteristic")
if let error = error {
print("Notify state update failed.... .error: \(error)")
} else {
print("Notify state update succeeded! isNotifying: \(characteristic.isNotifying)")
}
}
func peripheral(peripheral: CBPeripheral,
didUpdateValueForCharacteristic characteristic: CBCharacteristic,
error: NSError?)
{
print("peripheral didUpdateValueForCharacteristic")
if let error = error {
print("Data update notification error: \(error)")
return
}
print("Data update! value: \(characteristic.value)")
}
/// When changing the characteristic
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
print("peripheral didUpdateValueFor")
guard error == nil else {
print("Error getting/changing characteristic value: \(String(describing: error))")
// failure handling
return
}
guard let data = characteristic.value else {
print("characteristic.value")
// failure process
return
}
// data will be passed to us
print(data)
}
}
I have example iOS projects (Central & Peripheral) which send/receive indications: https://github.com/alexanderlavrushko/BLEProof-collection
setNotifyValue here is called similarly as you do, it should be fine.
I suggest to check the way how the characteristic is created and updated on the Peripheral side, iOS example link.
Also there is a great iOS application LightBlue which can simulate a BLE device, see this guide:
Central - the topic "Subscribing to Characteristics" might be useful
Peripheral - "Adding a New Virtual Peripheral", but use Blank device and configure services/characteristics you need
CBCentralManager unwraps as nil even if accessed in the stateUpdateHandler on .poweredOn. If I put a sleep(1) before accessing CBCentralManager, there's no problem. Why is this happening? If it's .poweredOn surely there's a non-nil instance there already?
The following code worked until upgrading my OS to Catalina and Xcode to 11.5.
extension BLEController: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .unknown:
print("central.state is .unknown")
case .resetting:
print("central.state is .resetting")
case .unsupported:
print("central.state is .unsupported")
case .unauthorized:
print("central.state is .unauthorized")
case .poweredOff:
print("central.state is .poweredOff")
case .poweredOn:
print("Bluetooth module is on. Searching...")
sleep(1) // works fine if this is here, but if .poweredOn it should be non-nil?
// unwraps as nil
guard let cManager = centralManager else {
print("centralManager is nil")
return
}
cManager.scanForPeripherals(withServices: [self.heartRateServiceCBUUID])
#unknown default:
return
}
}
}
Full code:
import Foundation
import CoreBluetooth
class BLEController: CBCentralManager {
var btQueue = DispatchQueue(label: "BT Queue")
var bpmReceived: ((Int) -> Void)?
var bpm: Int? {
didSet {
self.bpmReceived?(self.bpm!)
}
}
var centralManager: CBCentralManager!
var heartRatePeripheral: CBPeripheral!
let heartRateServiceCBUUID = CBUUID(string: "0x180D")
let heartRateMeasurementCharacteristicCBUUID = CBUUID(string: "2A37")
let batteryLevelCharacteristicCBUUID = CBUUID(string: "2A19")
func start() -> Void {
print("bluetooth started")
self.centralManager = CBCentralManager(delegate: self, queue: self.btQueue)
}
func stop() -> Void {
centralManager.cancelPeripheralConnection(heartRatePeripheral)
}
func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String: Any],
rssi RSSI: NSNumber) {
heartRatePeripheral = peripheral
heartRatePeripheral.delegate = self
centralManager.stopScan()
centralManager.connect(heartRatePeripheral)
}
func centralManager(_ central: CBCentralManager,
didConnect peripheral: CBPeripheral) {
print("Connected to HRM!")
heartRatePeripheral.discoverServices(nil)
}
func onHeartRateReceived(_ heartRate: Int) {
self.bpm = heartRate
}
}
extension BLEController: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .unknown:
print("central.state is .unknown")
case .resetting:
print("central.state is .resetting")
case .unsupported:
print("central.state is .unsupported")
case .unauthorized:
print("central.state is .unauthorized")
case .poweredOff:
print("central.state is .poweredOff")
case .poweredOn:
print("Bluetooth module is on. Searching...")
sleep(1) // works fine if this is here, but if .poweredOn it should be non-nil?
// unwraps as nil
guard let cManager = centralManager else {
print("centralManager is nil")
return
}
cManager.scanForPeripherals(withServices: [self.heartRateServiceCBUUID])
#unknown default:
return
}
}
}
extension BLEController: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
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 {
if characteristic.properties.contains(.read) {
peripheral.readValue(for: characteristic)
}
if characteristic.properties.contains(.notify) {
peripheral.setNotifyValue(true, for: characteristic)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic,
error: Error?) {
switch characteristic.uuid {
case batteryLevelCharacteristicCBUUID:
let percent = batteryLevel(from: characteristic)
print("Battery level: \(percent)%")
case heartRateMeasurementCharacteristicCBUUID:
let bpm = heartRate(from: characteristic)
onHeartRateReceived(bpm)
default:
return
}
}
private func heartRate(from characteristic: CBCharacteristic) -> Int {
guard let characteristicData = characteristic.value else { return -1 }
let byteArray = [UInt8](characteristicData)
let firstBitValue = byteArray[0] & 0x01
if firstBitValue == 0 {
// Heart Rate Value Format is in the 2nd byte
return Int(byteArray[1])
} else {
// Heart Rate Value Format is in the 2nd and 3rd bytes
return (Int(byteArray[1]) << 8) + Int(byteArray[2])
}
}
private func batteryLevel(from characteristic: CBCharacteristic) -> Int {
guard let characteristicData = characteristic.value else { return -1 }
let byteArray = [UInt8](characteristicData)
return Int(byteArray[0])
}
}
The relevant code is:
self.centralManager = CBCentralManager(delegate: self, queue: self.btQueue)
and:
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
...
case .poweredOn:
...
}
The call of the CBCentralManager initializer starts the action. You have to assume that the delegate will be immediately called from the initializer, i.e. the second piece of code is run before the initializer has returned and before the result has been assigned to the instance variable centralManager.
This will probably always happen if the Bluetooth device is already powered on when the initializer is called. If it isn't powered on yet, the delegate will be called later.
Anyway, you shouldn't need to worry about it. Instead of:
guard let cManager = centralManager else {
print("centralManager is nil")
return
}
cManager.scanForPeripherals(withServices: [self.heartRateServiceCBUUID])
just use:
central.scanForPeripherals(withServices: [self.heartRateServiceCBUUID])
Within the delegate, the CBCentralManager instance is available as it is passed as a parameter.
I am using CoreBluetooth to write an app that subscribes to one characteristic with one property: 'notify' which will allow a Raspberry Pi 3 to send an integer digit to be displayed on the app. I have created the service with this characteristic using Pybleno, a Python direct port of Bleno. I'm able to connect to the service and read its characteristic, but only for 30 seconds - after which the didDisconnect method is fired. On my central I use time.sleep() to alter the frequency at which the data is sent. I noticed that no delay causes the peripheral to disconnect after about 10 seconds, whereas 20-30ms delay causes a disconnect after 30 seconds. Any help would be great! Thank you. (I followed a Core Bluetooth tutorial for reading a Heart Rate monitor)
import UIKit
import CoreBluetooth
let TranslatorServiceCBUUID = CBUUID(string: "16dedcf4-027f-435f-b1e6-22e601276949")
let PredictionCharacteristicCBUUID = CBUUID(string: "16DEDCF4-027F-435F-B1E6-22E601276950")
var raspberryAsPeripheral: CBPeripheral!
class HRMViewController: UIViewController {
#IBOutlet weak var heartRateLabel: UILabel!
var centralManager: CBCentralManager!
override func viewDidLoad() {
super.viewDidLoad()
centralManager = CBCentralManager(delegate: self, queue: nil)
// Make the digits monospaces to avoid shifting when the numbers change
heartRateLabel.font = UIFont.monospacedDigitSystemFont(ofSize: heartRateLabel.font!.pointSize, weight: .regular)
}
func onDigitReceived(_ digit: Int) {
var predictedNumber : String
if (digit == 16) {
predictedNumber = ""
}
else {
predictedNumber = String(digit)
}
heartRateLabel.text = predictedNumber
print("Predicted digit: \(digit)")
}
}
extension HRMViewController : CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .unknown:
print("central.state is .unknown")
case .resetting:
print("central.state is .resetting")
case .unsupported:
print("central.state is .unsupported")
case .unauthorized:
print("central.state is .unauthorized")
case .poweredOff:
print("central.state is .poweredOff")
case .poweredOn:
print("central.state is .poweredOn")
centralManager.scanForPeripherals(withServices: [TranslatorServiceCBUUID])
}
}
func centralManager(_ central : CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
print(peripheral)
raspberryAsPeripheral = peripheral
raspberryAsPeripheral.delegate = self
centralManager.stopScan()
centralManager.connect(raspberryAsPeripheral)
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("you connected with the raspberry pi")
raspberryAsPeripheral.discoverServices([TranslatorServiceCBUUID])
}
}
extension HRMViewController : CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
print(service)
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)
if characteristic.properties.contains(.read) {
print("\(characteristic.uuid): properties contains .read")
peripheral.readValue(for: characteristic)
}
if characteristic.properties.contains(.notify) {
print("\(characteristic.uuid): properties contains .notify")
peripheral.setNotifyValue(true, for: characteristic)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic,
error: Error?) {
switch characteristic.uuid {
case PredictionCharacteristicCBUUID:
let whichNumber = predictedValue(from: characteristic)
onDigitReceived(whichNumber)
default:
print("Unhandled Characteristic UUID: \(characteristic.uuid)")
}
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("DISSSCONNNNECCCTEDDDDD")
}
private func predictedValue(from characteristic: CBCharacteristic) -> Int {
guard let characteristicData = characteristic.value else { return -1 }
let byteArray = [UInt8](characteristicData)
return Int(byteArray[0])
/*
let firstBitValue = byteArray[0] & 0x01
if firstBitValue == 0 {
// Heart Rate Value Format is in the 2nd byte
return Int(byteArray[1])
} else {
// Heart Rate Value Format is in the 2nd and 3rd bytes
return (Int(byteArray[1]) << 8) + Int(byteArray[2])
}
*/
}
}
I have gone and set up my CBCentralManager to search for devices and have the basic structure ready to receive and check on updated info.
I just cant seem to grasp on how to make a CBPeripheralManager ViewController and how to send my CBCentral data from the separate app on the press of a button. Simplest way being to send some string.
Here is my CBCentralManager ViewController.
class ViewController: NSViewController, CBCentralManagerDelegate,CBPeripheralDelegate {
let TRANSFER_SERVICE_UUID = "FB694B90-F49E-4597-8306-171BBA78F846"
let TRANSFER_CHARACTERISTIC_UUID = "EB6727C4-F184-497A-A656-76B0CDAC633A"
var centralManager: CBCentralManager?
var discoveredPeripheral: CBPeripheral?
override func viewDidLoad() {
super.viewDidLoad()
centralManager = CBCentralManager(delegate: self, queue: nil)
// Do any additional setup after loading the view.
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if (central.state != .poweredOn) {
return
}
else{
let serviceUUID:[CBUUID] = [CBUUID(string: self.TRANSFER_SERVICE_UUID)]
centralManager!.scanForPeripherals(withServices: serviceUUID, options: nil)
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("Discovered a peripheral")
print(peripheral.identifier)
print(peripheral.name!)
print(RSSI)
if(discoveredPeripheral != peripheral){
discoveredPeripheral = peripheral
centralManager?.stopScan()
print("Connection to peripheral")
centralManager?.connect(peripheral, options: nil)
}
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print(error!.localizedDescription)
centralManager?.cancelPeripheralConnection(peripheral)
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Connected")
peripheral.delegate = self
let serviceUUIDS:[CBUUID] = [CBUUID(string: self.TRANSFER_SERVICE_UUID)]
peripheral.discoverServices(nil)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if error != nil{
centralManager?.cancelPeripheralConnection(peripheral)
}
for service:CBService in peripheral.services as [CBService]!{
peripheral.discoverCharacteristics(nil, for: service)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if error != nil{
centralManager?.cancelPeripheralConnection(peripheral)
}
for characteristic:CBCharacteristic in service.characteristics as [CBCharacteristic]!{
if characteristic.uuid.isEqual(CBUUID(string:self.TRANSFER_CHARACTERISTIC_UUID)){
peripheral.setNotifyValue(true, for: characteristic)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
let stringFromData:String = String(data: characteristic.value!, encoding: String.Encoding.utf8)!
//if
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
Is this done by doing the exact opposite?
I want to make sure I am looking at this in the right direction.
UPDATE
Inside the other application I am trying to begin this process like so:
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheralManager?.state != .poweredOn {
return
} else {
let serviceUUId:CBUUID = CBUUID(string:self.TRANSFER_SERVICE_UUID)
let mutable:CBMutableService = CBMutableService(type: serviceUUId, primary: true)
peripheralManager?.add(mutable)
}
}
Is the next step to start advertising?