I'm going to break down what the flow for connection is for the first time a user wants to connect to a BLE Peripheral Device. I think I may be doing something that upsets the natural flow of the CoreBluetooth Module. My app currently works with regards to connecting and sending commands to a BLE device.
To connect, I first land on a page with a tableview which displays the devices available to connect to.
override func viewDidLoad() {
super.viewDidLoad()
//self.centralManager = CBCentralManager(delegate: blueCoreDelegate, queue: nil)
self.centralManager = CBCentralManager(delegate: self, queue: nil)
// setting the central manager in BlueCoreManager as the same
BlueCoreManager.shared.getCentralManager(centralManager!)
}
BlueCoreManager is a singleton that I have created to easily manage multiple connections. At this point the centralManagerDidUpdateState function is called.
I know that it goes to the centralManagerDidUpdateState function in both the current page and the singleton. In both I scan for peripherals.
In the current page: I append the discovered peripherals onto the tableview.
In the singleton: I keep track the connection states
Now once a cell is tapped on the current page, I connect to it:
// Connect to peripheral on tap
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let peripheral = peripherals[indexPath.row]
if peripheral.name != nil {
self.centralManager!.connectPeripheral(peripheral, options: nil)
print("Connecting to \(peripheral.name!)")
}
}
After the above, the didConnectPeripheral on the currentPage is called, where I update connectionStates and call the syncPeripheral function in the singleton.
func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
// setting connection state to 4 (currently connected)
let cstate = over.valueForKey("ConnectionState") as! Int
if cstate == 4 {
print("ConnectionState: One Connected. Connecting to Second")
over.setObject(5, forKey: "ConnectionState")
over.synchronize()
print("Connected To Two")
}
.......
.......
else if cstate == 7 {
print("ConnectionState: Disconnected from secondary. Connecting to Second")
over.setObject(5, forKey: "ConnectionState")
over.synchronize()
print("Connected To Two")
}
// synchronizing the current peripheral in BlueCoreManager
BlueCoreManager.shared.syncPeripheral(peripheral)
displayAlert("Connected!", message: "You are connected to \(peripheral.name!)")
}
This is the syncPeripheral function which is then called, where I set the peripherals' delegates and call discoverServices whichever one is connected.
func syncPeripheral(peripheral: CBPeripheral) {
let cstate = modeData.valueForKey("ConnectionState")
switch(cstate as! Int) {
case 0: print("Connection State Powered On")
print("Not keeping any reference")
case 4: print("Connection State To One")
self.easeDevice = peripheral
print(peripheral.name!)
self.easeDevice!.delegate = self
self.connectedPeripheralArray.insert(peripheral, atIndex: 0)
self.familiarPeripherals.insert(peripheral, atIndex: 0)
print(connectedPeripheralArray[0].name)
self.easeDevice!.discoverServices(nil)
case 5: print("Connection State To Two")
self.phy = peripheral
print(peripheral.name!)
self.phy!.delegate = self
self.connectedPeripheralArray.insert(peripheral, atIndex: 1)
self.familiarPeripherals.insert(peripheral, atIndex: 1)
self.phy!.discoverServices(nil)
case 6: print("Connection State Disconnected from One")
case 7: print("Connection State Disconnected from device two")
default: print("Connection State = \(cstate)")
}
}
I then discover the services and characteristics in the singleton NSObject class. In the disconnect function, I'm updating the disconnect states and trying to reconnect to the disconnected peripheral. Also I'm passing the disconnected peripheral through a protocol I defined.
// If disconnected, connect again
func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) {
// Delegate which passes disconnected peripherals
if delegate != nil {
delegate!.peripheralDidDisconnect("\(peripheral.name!)")
}
print("Disconnected")
if peripheral == self.easeDevice {
modeData.setObject(6, forKey: "ConnectionState")
modeData.synchronize()
connectedPeripheralArray.removeAtIndex(0)
self.centralManager!.connectPeripheral(peripheral, options: nil)
} else if peripheral == self.phy {
modeData.setObject(7, forKey: "ConnectionState")
modeData.synchronize()
connectedPeripheralArray.removeAtIndex(1)
self.centralManager!.connectPeripheral(peripheral, options: nil)
}
}
The only problem is that the above function never gets called on the singleton object (The singleton is an NSObject class, could this be the reason why?). What am I missing?! This issue is driving me insane! Any insights are highly appreciated.
Related
Im new to programming in swift, & I need help with working with bluetooth.
Im working on a project that involves sending a string to a computer via bluetooth, and Im able to enter the receiving device's MAC address beforehand so it has that to know where to send it.
My only problem at this stage is connecting to said device, & sending the data. I tried looking up tutorials, but they were either for Android (Which I already got working, I need one for iOS now), or they were about how to connect via service UUID (what?).
Heres the code I have so far:
import UIKit
import CoreBluetooth
class transmitter: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
var manager:CBCentralManager!
var peripheral:CBPeripheral!
let SCRATCH_UUID = UUID.init(uuidString: "00001101-0000-1000-8000-00805F9B34FB")
let SERVICE_UUID = CBUUID(string: "00001101-0000-1000-8000-00805F9B34FB")
override func viewDidLoad() {
super.viewDidLoad()
// Define manager
manager = CBCentralManager(delegate: self, queue: nil)
print(globals.data)
// Do any additional setup after loading the view.
}
#IBOutlet weak var console: UILabel!
// Check if teh bluetooth is enabled
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == CBManagerState.poweredOn {
central.scanForPeripherals(withServices:nil, options: nil)
print (central.isScanning)
console.text = String(describing: central.retrievePeripherals(withIdentifiers: [SCRATCH_UUID!]))
} else {
//print("Bluetooth not available.")
let alert = UIAlertController(title: "Bluetooth unavalible", message: "Bluetooth is unavalibe for this device. Is it even turned on?", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
// Pair with device....
// TODO: Change to be based on MAC Address instead of name...?
private func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
let device = (advertisementData as NSDictionary).object(forKey: CBAdvertisementDataLocalNameKey) as? NSString
// console.text = peripheral.name
/*
if device?.contains(globals.macAddress) == true {
self.manager.stopScan()
self.peripheral = peripheral
self.peripheral.delegate = self
manager.connect(peripheral, options: nil)
}
*/
}
//
// The rest is copied from a tutorial
//
// Once you are connected to a device, you can get a list of services on that device.
func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
peripheral.discoverServices(nil)
}
// Once you get a list of the services offered by the device, you will want to get a list of the characteristics. You can get crazy here, or limit listing of characteristics to just a specific service. If you go crazy watch for threading issues.
private func peripheral(peripheral: CBPeripheral,didDiscoverServices error: NSError?) {
for service in peripheral.services! {
let thisService = service as CBService
if service.uuid == SERVICE_UUID {
peripheral.discoverCharacteristics(nil, for: thisService)
}
}
}
// There are different ways to approach getting data from the BLE device. One approach would be to read changes incrementally. Another approach, the approach I used in my application, would be to have the BLE device notify you whenever a characteristic value has changed.
private func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) {
for characteristic in service.characteristics! {
let thisCharacteristic = characteristic as CBCharacteristic
if thisCharacteristic.uuid == SERVICE_UUID {
self.peripheral.setNotifyValue(true, for: thisCharacteristic)
}
}
}
// This is an optional step, but hey, let us be good programmers and clean up after ourselves. Also a good place to start scanning all over again.
private func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) {
central.scanForPeripherals(withServices: nil, options: nil)
}
}
What I was trying to do at this stage was:
Check to make sure bluetooth is enabled (Works)
List devices available, as I was not able to connect via MAC address (Fails)
What would be nice though is that if I didn't have to do step 2, & just connect via the provided MAC address instead of scanning for devices, which didn't show any.
Help?
Ok, so it seems that SPP is not supported. Crap :/
Ill look into what both John Doe & Paulw11 suggested
Thanks!
I am currently writing an app that connects via BLE to an external device. All operations are fine when the app is in foreground.....including connecting, obtaining data, and reconnecting (in cases of the device going out of range) via a reconnect protocol I wrote. The app also functions properly when it is backgrounded but the BLE connection remains alive.
However, the only instance in which the app does not function is if the app is backgrounded and then the BLE device goes out of range. Once the connection is broken, the app seems to be suspended by iOS after a few seconds and none of the code I wrote will continue to function...even if I bring the device back into range. The only way to restore functionality is to bring the app back into the foreground again. (Note: I have the info.plist file and all other settings configured appropriately for centralManager background functionality)
I've read some documentation and it seems that this comes down to not having state preservation/restore code properly implemented. I went ahead and implemented the "willRestoreState" and "didUpdateState" commands, but the app still doesn't reconnect to a device once it has been suspended when in background mode.
I've shown some relevant code below, including the willRestoreState, didUpdateState, and didDisconnect methods. Any ideas or suggestions? Thanks!
//define service+characteristic UUIDS
let serviceUUID = CBUUID(string: "xxxxxxxxx")
let streamingCharacteristicUUID = CBUUID(string: "xxxxxxxxx")
//Local dictionary of UUIDs for connected devices (the ble code updates this var with each connected device)
var devicesUniqueId:[UUID:String] = [UUID:String]()
//Local dictionary of connected peripherals, with respect to each of their UUIDS (the ble code updates this var with each connected device)
var sensorPeripheral = [UUID:CBPeripheral]()
///restoreState function
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
if let peripheralsObject = dict[CBCentralManagerRestoredStatePeripheralsKey] {
let peripherals = peripheralsObject as! Array<CBPeripheral>
print ("starting restorestate code")
if peripherals.count > 0 {
for i in 0 ..< peripherals.count {
print ("starting restorecheck")
//Check if the peripheral exists within our list of connected peripherals, and assign delegate if it does
if self.devicesUniqueId.keys.contains(peripherals[i].identifier) {
peripherals[i].delegate = self
}
}
}
}
}
func centralManagerDidUpdateState(_ central: CBCentralManager)
{
if central.state != .poweredOn
{
return
}
self.startScanning()
//////Preservation + Restoration code////////
//Iterate through array of connected UUIDS
let keysArray = Array(self.patchDevicesUniqueId.keys)
for i in 0..<keysArray.count {
//Check if peripheral exists for given UUID
if let peripheral = self.sensorPeripheral[keysArray[i]] {
print("peripheral exists")
//Check if services exist within the peripheral
if let services = peripheral.services {
print("services exist")
//Check if predefined serviceUUID exists within services
if let serviceIndex = services.index(where: {$0.uuid == serviceUUID}) {
print("serviceUUID exists within services")
let transferService = services[serviceIndex]
let characteristicUUID = streamingCharacteristicUUID
//Check if predefined characteristicUUID exists within serviceUUID
if let characteristics = transferService.characteristics {
print("characteristics exist within serviceUUID")
if let characteristicIndex = characteristics.index(where: {$0.uuid == characteristicUUID}) {
print("characteristcUUID exists within serviceUUID")
let characteristic = characteristics[characteristicIndex]
//If characteristicUUID exists, begin getting notifications from it
if !characteristic.isNotifying {
print("subscribe if not notifying already")
peripheral.setNotifyValue(true, for: characteristic)
}
else {
print("invoke discover characteristics")
peripheral.discoverCharacteristics([characteristicUUID], for: transferService)
}
}
}
}
else {
print("invoke discover characteristics")
peripheral.discoverServices([serviceUUID])
}
}
}
}
}
//didDisconnect method to handle a connect command issue
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?)
{
//commented out unnecessary code
self.removePeripheralData(peripheral: peripheral)
if(sensorCount>0){
sensorCount -= 1
}
}
//removePeripheralData function used in didDisconnect
func removePeripheralData ( peripheral: CBPeripheral) {
//Commented out unnecessary code
//Issue reconnect command
print ("issuing reconnect command")
centralManager.connect(peripheral, options: nil)
//Commented out unnecessary code
handleDidRemoveDevice()
}
I am currently trying to develop and application that allows users to bond to a Peripheral via a click of a button and the password will be automatically entered.
Is it possible to Bond and Remove Bond programmatically using swift?
Pairing is initiated any time that you attempt to write to or read from a characteristic on the BLE device. However, if the device is not set to require authentication and/or bonding, you will not see the iOS popup which requests the PIN code.
I struggled with this with my HM-10 because I could write data to the characteristic using the Core Bluetooth (via Swift) function writeValue() without ever seeing the pairing popup.
I couldn't figure it out until I read the HM-10 (implements the IC cc2451) datasheet very closely and found that I needed to set the AT+TYPE to value 3. It defaults to 0 which means that the HM-10 does not require pairing/bonding so you never see the iOS popup.
You can read more about the details where I asked the question and ultimately found the solution and wrote it up: How do I pair and/or bond to BLE on iOS using Swift code and an HM-10 so data sent is encrypted?
Follow the step to connect Ble device into iOS Program.
1) Import
import CoreBluetooth
2) Declared the variables into the class or ViewController.
let kServiceUART = CBUUID(string: "0x1800")
var peripheralHeartRateMonitor: CBPeripheral?
var cbManger: CBCentralManager!
3) Initialize the cbManger into ViewDidLoad function of viewController or initalize function of class.
cbManger = CBCentralManager(delegate: self, queue: .main)
4) Override delegate method of the CBCentralManager .
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .unsupported:
print("BLe Unsupported")
break
case .unauthorized:
print("BLe unauthorized")
break
case .poweredOff:
let alertMessgesInst = AlertMessages.sharedInstance
CommonUtils.showAlert(alertMessgesInst.actofit_Title, message: alertMessgesInst.trun_On_blueTooth)
break
case .poweredOn:
if isNewFirmWareOFImpulse {
let uuidString = StorageServices.readFromDefaults(key: Constants.userDefaultKeys.impulseUUID)
let uuid = UUID(uuidString:uuidString as! String )
let device = cbManger.retrievePeripherals(withIdentifiers: [uuid!])
peripheralHeartRateMonitor = device.first
peripheralHeartRateMonitor!.delegate = self
cbManger?.connect(peripheralHeartRateMonitor!)
}else {
let option:[String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey: NSNumber(value: false)]
cbManger.scanForPeripherals(withServices: nil, options: option)
}
break
case .unknown:
print("BLe unknown")
break
default:
break
} // End Swith
} // End 'centralManagerDidUpdateState' function.
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if isNewFirmWareOFImpulse {
peripheralHeartRateMonitor = peripheral
print("UUid of band is :- \(peripheralHeartRateMonitor?.identifier.uuidString)")
if impulseName == peripheral.name {
peripheralHeartRateMonitor!.delegate = self
cbManger.stopScan()
// STEP 6: connect to the discovered peripheral of interest
cbManger?.connect(peripheralHeartRateMonitor!)
} // End impulse condition
}else {
let keysArray = advertisementData.keys
if let tempImpulseName = peripheral.name {
print(impulseName + " and " + tempImpulseName )
if impulseName == tempImpulseName {
for key in keysArray {
if key == "kCBAdvDataManufacturerData"{
let manufactureData = advertisementData[key]
if let stringValue = manufactureData.debugDescription as? String {
var heartValue: String = String()
heartValue = stringValue
heartValue.removeLast()
heartValue.removeLast()
let last = heartValue.removeLast()
let secondLast = heartValue.removeLast()
let hR = String([secondLast, last])
if let value = UInt8(hR, radix: 16){
if Int(value) > 60 {
hrArray.append(Int(value))
}
} // End the value block
} // end of if 'stringValue' condition
} // end 'Key' if condition
} // End for each loop
} // End impulse condition
} // End pheripheral if condition
} // end version condition
} // End function 'didDiscover peripheral'.
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
// STEP 8: look for services of interest on peripheral
peripheralHeartRateMonitor?.discoverServices(nil)
} // END func centralManager(... didConnect peripheral
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if error != nil {
I am quite new to iOS programming and also to bluetooth protocol.
I have found a sample code written in swift and trying to modify it to work with my own bluetooth module. The module I have is DBM01 from dorji.
The service I need to use is FFF0 and the characteristics is FFF1 for sending a ASCII value.
When I use the LightBlue app on my macbook and connect to the board I have designed, which has the DBM01 module on it, I can send the char value of "1" and I get the expected response (Turning on a LED) and when I send value of "0" it turns the LED off.
Now with the code I have, I can connect to the DBM01 module. I can print its name. However, I cannot disconnect from it with the following function. I am also not sure if this is for disconnecting from the device or it is called automatically when the device is disconnected. Regardless, it does not work either way.
func centralManager(central: CBCentralManager,
didDisconnectPeripheral peripheral: CBPeripheral,
error: NSError?)
My main problem is I really didn't understand how I specify the service and the characteristics that I am interested in and connect to specific device that has them.
I am also unable to send a message. When I try I got the predefined error, since the following condition did't hold
if writeCharacteristic != nil
Below is my full code.
Appreciate if you can point out where I am doing wrong and how I can achieve connecting to specific device with specific service and characteristics information and sending data.
//
// ViewController.swift
// bleSwift
//
import UIKit
import CoreBluetooth
class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
var centralManager: CBCentralManager!
var peripheral: CBPeripheral!
var writeCharacteristic: CBCharacteristic!
var service: CBService!
var characteristic: CBCharacteristic!
var bluetoothAvailable = false
let message = "1"
#IBOutlet weak var deviceName: UILabel!
#IBOutlet weak var ServiceName: UILabel!
#IBOutlet weak var CharacteristicsName: UILabel!
func centralManagerDidUpdateState(central: CBCentralManager)
{
print("Checking state")
switch (central.state)
{
case .PoweredOff:
print("CoreBluetooth BLE hardware is powered off")
case .PoweredOn:
print("CoreBluetooth BLE hardware is powered on and ready")
bluetoothAvailable = true;
case .Resetting:
print("CoreBluetooth BLE hardware is resetting")
case .Unauthorized:
print("CoreBluetooth BLE state is unauthorized")
case .Unknown:
print("CoreBluetooth BLE state is unknown");
case .Unsupported:
print("CoreBluetooth BLE hardware is unsupported on this platform");
}
if bluetoothAvailable == true
{
discoverDevices()
}
}
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber)
{
// Stop scanning
self.centralManager.stopScan()
print("Stopped Scanning")
// Set as the peripheral to use and establish connection
//self.peripheral = peripheral
//self.peripheral.delegate = self
//self.centralManager.connectPeripheral(peripheral, options: nil)
peripheral.discoverServices([CBUUID(string: "FFF0")])
print("CONNECTED!!")
print(peripheral.name)
deviceName.text = peripheral.name
}
func discoverDevices() {
print("Discovering devices")
centralManager.scanForPeripheralsWithServices(nil, options: nil)
}
#IBAction func disconnectDevice(sender: AnyObject) {
func centralManager(central: CBCentralManager,
didDisconnectPeripheral peripheral: CBPeripheral,
error: NSError?)
{
print("CONNECTION WAS DISCONNECTED")
deviceName.text = "Disconnected"
}
}
#IBAction func Scan(sender: AnyObject)
{
print("Scan")
centralManager = CBCentralManager(delegate: self, queue: nil)
}
#IBAction func Send(sender: AnyObject)
{
let data = message.dataUsingEncoding(NSUTF8StringEncoding)
if writeCharacteristic != nil
{
print("Sent")
peripheral!.writeValue(data!, forCharacteristic: writeCharacteristic, type: CBCharacteristicWriteType.WithoutResponse)
}
else
{
print("Couldn't Send")
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
In order to send data to your ble peripheral, you should:
Start scanning.
In your ble central manager delegate you'll recieve didDiscoverPeripheral callback with discovered peripheral. Set yourself as its delegate and connect to it: centralManager.connectPeripheral(...)
Receive didConnectPeripheral in delegate. Now call discoverServices for this peripheral.
Recieve didDiscoverServices in your delegate. Finaly, call discoverCharacteristics for your service.
You'll recieve characteristic in didDiscoverCharacteristic delegate method. After that, you'll be able to send data to exact characteristic of your peripheral.
To disconnect peripheral, call method centralManager.cancelPeripheralConnection(...)
I'm trying to listen to all Bluetooth connect/disconnect events. Even though the delegate's centralManagerDidUpdateState function is called, nothing happens when I connect or disconnect Bluetooth devices.
I'm confused as to what's going wrong. I initialize the Central Manager/delegate like this:
var btDelegate: CBCentralManagerDelegate = BluetoothDelegate()
var btManager = CBCentralManager(delegate: btDelegate, queue: nil)
BluetoothDelegate is defined like so:
import Foundation
import CoreBluetooth
class BluetoothDelegate : NSObject, CBCentralManagerDelegate {
func centralManager(central: CBCentralManager!, didConnectPeripheral peripheral: CBPeripheral!) {
println("connect") //this line is not called
}
func centralManager(central: CBCentralManager!, didDisconnectPeripheral peripheral: CBPeripheral!, error: NSError!) {
println("disconnect") //this line is not called
}
func centralManagerDidUpdateState(central: CBCentralManager!) {
println("state update") //this line is called
}
}
Note: I can continuously receive more state update events such as when I toggle Bluetooth, even though connect and disconnect are not called.
From your code it looks like you haven't started scanning for peripherals. Once you have confirmed that your central is in powered on state from centralManagerDidUpdateState method you should start scanning for your peripherals.
(The bluetooth devices you connected from bluetooth settings panel are irrelevant. You can't have access to them. (as far as I know) Incase you want to scan and find out your device on your own below code will help)
func centralManagerDidUpdateState(central: CBCentralManager!) {
switch (central.state) {
case CBCentralManagerState.PoweredOff:
break
case CBCentralManagerState.PoweredOn:
startScan() // start scanning once the bluetooth is On
break
default:
break
}
}
And your startScan method will be (You can provide service UUID, use nil if you want all )
func startScan(){
if let central = btManager {
central.scanForPeripheralsWithServices(nil, options: nil)
println("started Scanning")
}
}
After that whenever you discover a peripheral didDiscoverPeripheral method will be called first.
func centralManager(central: CBCentralManager!, didDiscoverPeripheral peripheral: CBPeripheral!, advertisementData: [NSObject : AnyObject]!, RSSI: NSNumber!) {
println(peripheral)
// btManager.connectPeripheral(peripheral, options: nil)
}
From there you collect the peripheral and then invoke connectPeripheral method of CBCentralManager. If the connection is successful didConnectPeripheral method will be called.
You should go through this documentation for complete details