Device Disconnect After some time & Unable to get characteristic - ios

I know disconnect error mentioned is asked many times on stack but answers accepted are not actual solution to issue
I am trying to connect MI Band 3 with my swift App. It get connected successfully but after some time it get disconnected automatically
Error: Error Domain=CBErrorDomain Code=7 "The specified device has disconnected from us." UserInfo={NSLocalizedDescription=The specified device has disconnected from us
My App Requirement: I need to get heart rate and walking distance from Apple Watch, Fitbit and MI Smart Watch
I have tried connecting same MI Band on multiple device result is same. But that specific MI band work perfectly with their own app
--> Connect Device - Kept reference of connected Device
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Connected With Peripheral: \(peripheral)")
selectedPeripheral=peripheral
self.delegate?.scannedPeripherals(Is: peripheral)
/// Discover Services Provided By Device
selectedPeripheral?.delegate=self
selectedPeripheral?.discoverServices([heartRateServiceCBUUID])
}
---> After Discovering services With heart Rate CBUUID
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return }
selectedPeripheral=peripheral
for char in characteristics {
print("Characterstics: \(char)")
if char.properties.contains(.read) {
}
if char.properties.contains(.notify) {
/// ------------- Setting Notify to true but not never call required delegates ----------------
peripheral.setNotifyValue(true, for: char)
print("\(char.uuid): properties contains .notify")
peripheral.readValue(for: char)
}
}
}
Calculating BPM from delegate
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
switch characteristic.uuid {
case bodySensorLocationCharacteristicCBUUID:
let bodySensorLocation = bodyLocation(from: characteristic)
print(bodySensorLocation)
default:
/// --------- here characteristic value is nil --------------
let bpm = heartRate(from: characteristic)
print("BPM: \(bpm)")
}
}

In func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)
Try to remove peripheral.readValue(for: char) and test again!

Related

L2CAP Channel data transfer

I am working on developing BLE app using connection oriented channel . I am using nordic semiconductor nrf52 as a peripheral device and iPhone 6 as central manager.
I have used predefined PSM value provided by Bluetooth SIG that is 0x0025.
I am ble to connect to peripheral and open L2CAP channel successfully.
I am getting below error :
**[CoreBluetooth] WARNING: Unknown error: 436
2018-06-08 10:03:17.532709-0400 BluetoothTest[407:62057] [CoreBluetooth] **No known channel matching peer with psm 37****
Could please let me know how to proceed and what is meaning of error code 436
Below is my code :
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
//handling callback when a peripheral is discover
print("Central Manager PowerOn State Check:\(central.state.rawValue)")
if (peripheral.name?.contains("Nordic_OTSX") == true)
{
print(peripheral.name ?? "no name")
print("advertisement Data : \(advertisementData) ")
central.connect(peripheral, options: nil )
myPeripheral = peripheral
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral)
{
print("didConnect\(myPeripheral.debugDescription)")
myPeripheral.delegate = self
myPeripheral.discoverServices(nil)
}
//if error while making connection
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?)
{
print("error:\(error.debugDescription)")
}
//after opening L2CAP Channel
func peripheral(_ peripheral: CBPeripheral, didOpen channel: CBL2CAPChannel?, error: Error?)
{
print("didOpen")
print(error.customMirror)
print(channel!.outputStream.debugDescription)
print(channel!.inputStream.debugDescription)
print(channel?.outputStream.hasSpaceAvailable)
}
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
}
print("Discovered Services: \(services)")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?)
{
print("*******************************************************")
if let charcterstics = service.characteristics
{
print("characteristics :")
for char in charcterstics
{
/* if char.uuid == buttonCharacteristicUUID
{
buttonCharacteristic = char
enableButtonNotifications(buttonCharacteristic!)
readButtonValue()
}*/
print(char.uuid.uuidString)
}
}
peripheral.openL2CAPChannel(0x0025)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I had a similar problem. I manage to solve it by creating a variable to save "channel: CBL2CAPChannel?" in the function
func peripheral(_ peripheral: CBPeripheral, didOpen channel: CBL2CAPChannel?, error: Error?)
I was only saving the "channel.outputStream", which was the only one I needed. But it looks like if you don't save it, it will be closed.
0x25 PSM is for OTS. You need ATT PSM which is 0x1F

How to get battery life of Bluetooth Low Energy in Swift 4?

I am able to get a value for BLE battery life with the help of following questions:
Read data from BLE device
Reading a BLE Peripheral Characteristic and checking its value?
But I am not sure if It returns the right value? It returns 18, and I am also not sure about the maximum number to determine the battery life based on percentage. Does it mean 18 hours?
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
print("-CBService is: \(service.uuid.description)")
if compare(service.uuid, uuid2: CBUUID(string:BluetoothConstants.TI_KEYFOB_BATT_SERVICE_UUID)) {
print("Battyer Life determination")
for characteristic: CBCharacteristic in service.characteristics! {
if characteristic.uuid == CBUUID(string: BluetoothConstants.TI_KEYFOB_LEVEL_SERVICE_UUID) {
print(characteristic.properties.rawValue)
}
}
}
}
The following line is print for characterstic:
CBCharacteristic: 0x1c00be8a0, UUID = Battery Level, properties =
0x12, value = (null), notifying = NO>
I tried most of the answers here but they are not working for Swift 4.
I'm not sure about Swift 4, but here's how I figured it out using Swift 5. The value in the characteristic.properties is not the battery level. You need to request a read from the device on the Battery Level characteristic (0x2A19):
if characteristic.uuid == CBUUID(string: "0x2A19") {
peripheral.readValue(for: characteristic)
}
Then you'll need to add the didUpdateValueFor as part of your CBPeripheralDelegate as well:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
print("Battery level: \(characteristic.value![0])")
}
I tested this with an old 3V that was at 2.76V (which is pretty much considered dead) and it gave me a value of 3. Then I put in a new battery and it gave me a value of 100.
func peripheral(
_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?
) {
if characteristic.uuid.uuidString == "2A19" {
print("Battery level: \(characteristic.value![0])")
}
}
func peripheral(
_ peripheral: CBPeripheral,
didDiscoverCharacteristicsFor service: CBService,
error: Error?
) {
if let error = error {
print("Error discovering service characteristics: \(error.localizedDescription)")
}
for newChar: CBCharacteristic in service.characteristics! {
peripheral.readValue(for: newChar)
if newChar.properties.rawValue == 0x12 {
peripheral.setNotifyValue(true, for: newChar)
print("characteristicValues",newChar)
}
}
}

Swift BLE "didDiscoverServices" not executing. Am I missing something?

I've been reading the tutorial to use the nRF8001 from Adafruit and connecting it to an iOS device via Arduino. So far I've set up everything correctly and it works fine in their app.
I'm trying to write my own app that (for now) does the exact same thing, I read their tutorial and from what I understand I copied the code as close as I could, so far I can get the connection and most things seem to be working (UI-wise), however I can't seem to do anything past connecting to the device:
Here's my code for AFTER connecting:
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
//What to do when it discovers a peripheral, add it to the array list
print("Peripheral found: " + (peripheral.name ?? "Unknown Name"))
peripheralsFoundNames.append((peripheral.name ?? "Unknown Name"))
peripheralsFoundData.append((advertisementData.description ))
peripheralsFoundCB.append(peripheral)
peripheralsFoundRSSIs.append(RSSI)
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Connected to device!")
displayStatusAlert(localmsg: "Connection Succesful!")
NotificationCenter.default.post(name: Notification.Name(rawValue: DEVICE_READY_KEY), object: self)
data?.length = 0 //clear any data that might be stored
peripheral.discoverServices([BLETemperatureService])
print("Here at didConnect, connected to:" + peripheral.name!)
// Here needs to add code to check if it's a single or multi-channel device via the advertisement data or some other constant, maybe the name?
}
As you can see, I am calling explicitly peripheral.discoverServices, and then i have a print statement that executes. Then I have the following (NOTE NONE OF THE BELOW LINES SEEM TO EXECUTE AT ANY TIME (AT LEAST NOT THE PRINT STATEMENTS):
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
print("here at diddisoverservices")
if ((error) != nil){
displayStatusAlert(localmsg: "Error: \n" + (error?.localizedDescription ?? "Error Unknown" ))
}
guard let services = peripheral.services
else{
return
}
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
}
print ("Discovered!")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if ((error) != nil){
displayStatusAlert(localmsg: "Error: \n" + (error?.localizedDescription ?? "Error Unknown" ))
}
guard let characteristics = service.characteristics
else{
return
}
for characteristic in characteristics {
//looks for the right characteristic
print("looking for characteristic")
if characteristic.uuid.isEqual(BLERXCharacteristic) {
deviceConnectedRXChar = characteristic
//Once found, subscribe to the this particular characteristic
peripheral.setNotifyValue(true, for: deviceConnectedRXChar!)
peripheral.readValue(for: characteristic)
print("Rx Characteristic: \(characteristic.uuid)")
}
if characteristic.uuid.isEqual(BLETXCharacteristic){
deviceConnectedTXChar = characteristic
print("Tx Characteristic: \(characteristic.uuid)")
}
peripheral.discoverDescriptors(for: characteristic)
}
print ("Characteristic discovered")
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic == deviceConnectedRXChar {
if let ASCIIstring = NSString(data: characteristic.value!, encoding: String.Encoding.utf8.rawValue) {
receivedDataString = ASCIIstring
print("Value Recieved: \((receivedDataString as String))")
NotificationCenter.default.post(name: Notification.Name(rawValue: DEVICE_SENT_DATA), object: nil)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) is a CBPeripheralDelegate method.
So what you were missing is setting the CBPeripheral object delegate.
So just before doing peripheral.discoverServices([BLETemperatureService]), you need to do peripheral.delegate = self.

Core Bluetooth setNotifyValue isn't setting CBCharacteristics notifying property to true

Hey guys I'm trying to get this bluetooth LE device to connect to the app and grab the values out. I'm using Core Bluetooth which is supposed to get the values out. I had it working but all the sudden it stopped notifying the values when it updates.
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
for characteristic in service.characteristics! {
peripheral.setNotifyValue(true, for: characteristic)
print("peripheral did discover characteristics \(characteristic)")
}
}
In the delegate method call I'm setting every single CBCharacteristic object's notify value to true. This should mean that any value that it updates should call another delegate method
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?)
However this method is not being called. In I added a print statement to print out each characteristics, but each single characteristics notify property is still set to NO when I set the value as true!
peripheral did discover characteristics <CBCharacteristic: 0x1700ac0c0, UUID = 28930002-C868-423A-9DC2-E9C2FCB4EBB5, properties = 0x12, value = (null), notifying = NO>
Am I missing something? Thanks in advance
You can implement func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) to see if a call to setNotifyValue(_:for:) was actually successful

Bluetooth LE on iOS10 strange behavior

I found out that when an iPhone is updating a local characteristic value, and also listen to notification for that characteristic, he will get notified even the fact that he was the one that updated the value, so the delegate :
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
Will be called, even that I was the one who change the value, and not the remote server (ble device). When the remote side send data, I will also get this delegate. Is it the way it should be ? I don't remember so .
I found out the same behavior on other 3rd apps that scan for Bluetooth LE.
I also found out that for some reason my code is not always get the delegate , maybe I am doing things wrong here:
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
let foundName=peripheral.name
let deviceName = "Name"
if foundName?.range(of: deviceName) != nil
{
self.centralManager.stopScan()
self.peripheral = peripheral
self.peripheral.delegate = self
self.centralManager.connect(peripheral, options: nil)
NotificationCenter.default.post(name: Notification.Name(rawValue: "Bluetooth"), object: "Detected")
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.discoverServices( nil)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
for service in peripheral.services!
{
let thisService = service as CBService
print(thisService.uuid.uuidString)
if thisService.uuid.uuidString == serviceUUID {
peripheral.discoverCharacteristics(nil, for: thisService)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
for charateristic in service.characteristics!
{
let thisCharacteristic = charateristic as CBCharacteristic
// check for data characteristic
if thisCharacteristic.uuid.uuidString == characteristicUUID {
print("REGISTERED CHARACTERISTIC:",thisCharacteristic)
self.peripheral.setNotifyValue(true, for: thisCharacteristic)
self.characteristic=thisCharacteristic
isConnected=true
NotificationCenter.default.post(name: Notification.Name(rawValue: "Bluetooth"), object: "Connected")
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.uuid.uuidString == characteristicUUID {
if let str = NSString(data: characteristic.value!, encoding: String.Encoding.utf8.rawValue)
{
print("BLE:DATAIN:",str )
NotificationCenter.default.post(name: Notification.Name(rawValue: "Bluetooth"), object: str)
}
}
}
func sendData(data:String)
{
if(peripheral != nil)
{
print("BLE:SENT")
var bytesData = [UInt8](data.utf8)
let writeData = NSData (bytes: &bytesData, length: bytesData.count)
peripheral.writeValue(writeData as Data, for: characteristic, type: CBCharacteristicWriteType.withoutResponse)
}
}
After a day investigation, both the hardware chip and the iOS, I realized that the notify update- will notify you when there is a new value.
So, whats a new value ?
Its when the previous value is different from the current value.
For some hardware configurations, the previous value is kept on cache even after reset, and iOS will see it as a new value, even though the hardware was not updated it at all (!)
So when you register to notifications, iOS will check the initial value , and if it holds something other then zero/nil , a delegate will be called.
Its your job to clean the previous cached value in the chip in some way.
In general, I found out (maybe too late) that the best practice is to connect and stay connected as long as the app is running. This will eliminate all sorts of problems that happens when you connect/disconnect every time you need to send data.
Conclusion: connect once, when disconnected for any reason -reset your hardware automatically (using the host controller software)

Resources