I recently upgraded from Swift 3 to Swift 4 and iOS 10.3.3 to iOS 11.1.
I'm developing an application that uses BLE to communicate bi-directionally. The workflow is as follows:
PERIPHERAL - Advertise Identity
CENTRAL - Receive Identity (process it...)
CENTRAL - Respond to peripheral
PERIPHERAL - Receive response from central
Done
My code was working perfectly before the update but now it's not. At the end of step 4, I execute the following line:
peripheral.writeValue(encryptedData!, for: characteristic, type: .withResponse)
This should call the following delegate method but it doesn't:
public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) {
print("Did Write")
print("Error=\(error?.localizedDescription)")
}
It should also (and was calling) the following delegate method on the PERIPHERAL device but it doesn't:
public func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
print("did receive write request")
}
The service and characteristic are set as follows :
let prefs = Preferences()
let strServiceUUID = prefs.GetString(key: Preferences.PREF_IDENTITY_SERVICE_UUID, defaultVal: "")!
let strCharacteristicUUID = prefs.GetString(key: Preferences.PREF_IDENTITY_CHARACTERISTIC_UUID, defaultVal: "")!
print("ServiceUUID=\(strServiceUUID)")
print("CharacteristicUUID=\(strCharacteristicUUID)")
mServiceUUID = CBUUID(string: strServiceUUID)
mCharacterUUID = CBUUID(string: strCharacteristicUUID)
mCBBluetoothServices = CBMutableService(type: mServiceUUID, primary: true)
//lets configure the data we want to advertise for
var characteristics : [CBCharacteristic] = []
//let strData : String = "933911"
//let data = strData.data(using: .utf8)
let cbProperties: CBCharacteristicProperties = [.read, .write, .notify]
let cbPermissions: CBAttributePermissions = [.readable, .writeable]
mIdentityObjectCharacteristic = CBMutableCharacteristic(type: mCharacterUUID,
properties: cbProperties,
value: nil,
permissions: cbPermissions)
characteristics.append(mIdentityObjectCharacteristic)
mCBBluetoothServices.characteristics = characteristics
mCBPeripheralManager.add(mCBBluetoothServices)
I am not sure why upgrading the OS and Swift versions broke your code, however, it looks to me like you may be using the wrong delegate method?
Try using this
func peripheral(CBPeripheral, didWriteValueFor: CBCharacteristic, error: Error?)
instead of this
func peripheral(CBPeripheral, didWriteValueFor: CBDescriptor, error: Error?)
Swift 4
For any kind of update characteristic (ex. read/write characteristic), then the didUpdateValueFor delegate will be called.
So, first check in the following delegate methods.
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
print("didUpdateValueForChar", characteristic)
if let error1 = error{
alertMSG(titleString: "Error", subTitleString: "Found error while read characteristic data, Plase try again", buttonTitle: "OK")
print(error1)
}
else{
print("Update Characteristic: ", characteristic)
}
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
print("Write Characteristic :", characteristic)
}
Swift 5
iOS 13
Some things to check:
Make sure to set the peripheral's delegate to whichever controller is conforming to the CBPeripheralDelegate protocol (this should also be the same controller that needs to implement the peripheral(_:didWriteValueFor:error:) method).
Make sure you are not specifying a .withoutResponse write type.
As mentioned by this other answer, there are two very similar delegate methods that have the signature peripheral(_:didWriteValueFor:error:). Make sure you are implementing the correct one.
When writing to a characteristic:
writeValue(_:for:type:):
func writeValue(_ data: Data,
for characteristic: CBCharacteristic,
type: CBCharacteristicWriteType)
peripheral(_:didWriteValueFor:error:)
func peripheral(_ peripheral: CBPeripheral,
didWriteValueFor characteristic: CBCharacteristic,
error: Error?)
When writing to a descriptor:
writeValue(_:for:)
func writeValue(_ data: Data,
for descriptor: CBDescriptor)
peripheral(_:didWriteValueFor:error:)
func peripheral(_ peripheral: CBPeripheral,
didWriteValueFor descriptor: CBDescriptor,
error: Error?)
It is easy to confuse the 2 sets of write and delegate methods.
Since you are using:
peripheral.writeValue(encryptedData!, for: characteristic, type: .withResponse)
The code for the write and delegate pair should be something like this:
class BluetoothController: CBCentralManagerDelegate, CBPeripheralDelegate {
...
func writeSomething(to characteristic: CBCharacteristic, of peripheral: CBPeripheral) {
let something = "1234"
NSLog("Writing \(something) to \(characteristic.uuid.uuidString)")
peripheral.delegate = self // <===== You may have forgotten this?
peripheral.writeValue(something.data(using: .utf8)!,
for: characteristic,
type: .withResponse)
}
...
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
if error != nil {
NSLog("Write error: \(String(describing: error))")
} else {
NSLog("Wrote value to \(characteristic.uuid.uuidString)")
}
}
}
Related
We have a peripheral device and I am trying to connect it with iOS device. It was working fine with Swift 3.0 But when I am trying to convert it in Swift 5.0 it did not work. It is working perfectly fine with Android device. So it is not an issue of hardware.
I have successfully connected and also discovered services and characteristics. My characteristics give me notify property only, I set that to true which also triggered the didupdateNotificationStateFor delegate method and it showed the characteristic.isnotifying as true. Which meant my subscription is successful. The only problem I am facing is didUpdateValueForCharacteristic is never been called. My code is as under.
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)
// break
}
}
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 transferCharacteristic.properties.contains(.notify) {
print("\(characteristic.uuid): properties contains .notify")
peripheral.setNotifyValue(true, for: characteristic)
}
if characteristic.properties.contains(.write){
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
switch characteristic.uuid {
case aCBUUID:
lblResult.text = "UUID: \(characteristic.uuid) Value = \(String(describing: characteristic.value))"
case bCBUUID:
lblResult2.text = "UUID: \(characteristic.uuid) Value = \(String(describing: characteristic.value))"
case f:
print("CORRECT Characteristic UUID: \(characteristic.uuid)")
print(characteristic.value ?? "no value")
default:
print("Unhandled Characteristic UUID: \(characteristic.uuid)")
print(characteristic.value ?? "no value")
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
if error != nil {
print("Error ==",error?.localizedDescription ?? "")
}
print("updateNotification: \(characteristic.isNotifying)")
}
Any help in this regard is highly appreciated.
I'm sending a command to bluetooth device but got no response return back. What'm I doing wrong for creating the data packet?
Here is the example for the frame format describe by document.
I've tried on create a data such as:
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
let bytes : [UInt16] = [0x0000, 0x0002, 01, 00]
let data = Data(bytes: bytes, count: bytes.count)
self.peripheral.writeValue(data, for: characteristic, type: CBCharacteristicWriteType.withoutResponse)
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
if (error != nil) {
print("didWrite Value for characteristic:\(characteristic)")
}
}
}
I though I did write value to peripheral success, but after that no any respond of func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor ...) return. Anyone please help?
You are using CBCharacteristicWriteType.withoutResponse as the type when calling self.peripheral.writeValue, and according to Apples documentation, you would have no response from the peripheral to indicate whether the write was successful.
https://developer.apple.com/documentation/corebluetooth/cbcharacteristicwritetype/withoutresponse
Is it possible for you to try using CBCharacteristicWriteType.withResponse instead, to see if you get a returned response?
I have a problem where I connect to a peripheral and set notify value to true correctly but I can't get the value. I am following the next steps.
1 - I connect to peripheral
2 - I discover services
3 - I discover characteristics for services
4 - I activate notifications for a specific characteristic
override func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if characteristic.uuid == historicCharacteristicCBUUID && characteristic.properties.contains(.notify) {
print("\(characteristic.uuid): properties contains .notify")
if hasWritenOnHistoric == false {
peripheral.setNotifyValue(true, for: characteristic)
}
}
}
5 - I wait to the delegate and then write on the characteristic to tell the peripheral to start sending data
override func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
print(characteristic)
if error == nil {
print("TRUE")
if characteristic.uuid == historicCharacteristicCBUUID && characteristic.isNotifying == true {
writeOnHistory(peripheral: peripheral, characteristic: characteristic)
} else {
print("no ha cambiado su estado")
}
}
}
6 - Write on peripheral
func writeOnHistory(peripheral: CBPeripheral, characteristic: CBCharacteristic) {
if characteristic.uuid == historicCharacteristicCBUUID {
hasWritenOnHistoric = true
var bitesArray: [UInt8] = [0x01, 0x05]
for _ in 1...16 {
bitesArray.append(0x00)
}
print(bitesArray.count)
print(bitesArray)
let data = Data(bytes: bitesArray)
peripheral.writeValue(data, for: characteristic, type: .withResponse)
}
}
7 - This delegate is only called once and with empty data [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00](in Android is working fine getting all the data from the peripheral)
override func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if let data = characteristic.value {
print(data)
}
}
Is there something I am missing?
Thanks in advance!
You will not observe if your value is changed unless you call didWriteValueFor.
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.uuid == myCharacteristic {
self.bluetoothManager.readValueForCharacteristic(characteristic: myCharacteristic)
}
}
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)
}
}
}
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)