ios ble "Characteristic User Description" - ios

Trying to retrieve readable information from an characteristics by using the function:
peripheral.discoverDescriptors(for: characteristic)
Later the delegate method:
func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?)
is called but how can I get the string description? When I read the value from the descriptors it's always nil.
let descriptors = characteristic.descriptors! as [CBDescriptor]
for descriptor in descriptors {
print("\(#function): descriptor = \(descriptor) UUID = \(descriptor.uuid) value = \(descriptor.value)")
}
However, if I'm browsing and connecting with an BLE scanner it can read the characteristic human readable descriptors.

Reading descriptors is a two-step process, much like discovering and then reading characteristics.
Try:
public func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
guard let descriptors = characteristic.descriptors else { return }
for descr in descriptors {
peripheral.readValue(for: descr)
}
}
And then fill in the blanks of:
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
switch descriptor.uuid.uuidString {
case CBUUIDCharacteristicExtendedPropertiesString:
guard let properties = descriptor.value as? NSNumber else {
break
}
print(" Extended properties: \(properties)")
case CBUUIDCharacteristicUserDescriptionString:
guard let description = descriptor.value as? NSString else {
break
}
print(" User description: \(description)")
case CBUUIDClientCharacteristicConfigurationString:
guard let clientConfig = descriptor.value as? NSNumber else {
break
}
print(" Client configuration: \(clientConfig)")
case CBUUIDServerCharacteristicConfigurationString:
guard let serverConfig = descriptor.value as? NSNumber else {
break
}
print(" Server configuration: \(serverConfig)")
case CBUUIDCharacteristicFormatString:
guard let format = descriptor.value as? NSData else {
break
}
print(" Format: \(format)")
case CBUUIDCharacteristicAggregateFormatString:
print(" Aggregate Format: (is not documented)")
default:
break
}
}
The string constants and the associated data types came from the overview table here.
In my (limited) experience, descriptors don't reveal anything particularly interesting.

Related

Cannot Convert String to Int from BLE Data

I've spent 3 weeks avoiding this question on Stackover flow but I'm at my wits end.
I don't know why I can create a string manually and convert it into an INT but can't do the same thing from BLE data.
This is my code:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if error != nil {
print("ERROR ON UPDATING VALUE FOR CHARACTERISTIC: \(characteristic) - \(String(describing: error?.localizedDescription))")
return
}
guard let data = characteristic.value else{
return
}
// let myString = "345345" // I CAN MANUALLY SET A STRING
// let myInt = Int(myString) ?? 2
// print (myInt + 523436) I CAN CONVERT AND USE THIS INT
let weightStringData = swiftDataToString(ESP32Data: data)
func swiftDataToString(ESP32Data:Data) -> String? {
return String(data: data, encoding: .utf8)
}
if let myNewstring = weightStringData{
// print(myNewstring) // THIS PRINTS AS A STRING
let myInt = Int(myNewstring) ?? 2 //THIS DOES NOTHING IT SEEMS OTHER THAN SET THE CONSTANT TO "2"
print (myInt + 9898) //CANT USE THE INT LIKE IN THE ABOVE MANUAL EXAMPLE
}else{
print("Nothing")
}
}
}

Swift converting BLE characteristic value to a string

I am having an issue that is making no sense. Everything that I am doing I would think is pretty standard for BLE. Anyways, I have a BLE module that I am sending string data from to an iOS application and I am displaying this data on an interface. Since this is my first run using BLE code on an iOS application, I am using the tutorial from adafruit as a guide which can be found here: Adafruit tutorial
As I said, pretty standard stuff. Nothing special here. The code that I have written is able to perform all of the BLE steps. But, when it gets the data, the data is /0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0. At first, I thought the source on the transmitting side was incorrect. But I opened another test app that lets me see sent BLE data and the data coming in is correctly formatted. It must be an issue with my code. I am not sure why this is happening or what the issue is. I could really use another pair of eyes to see if there is any issue with my code:
My code init code:
required init(parent: ViewController) {
self.parent = parent
self.bluetoothOn = false;
bodyTemperature = 0
airPressure = 0
lightLevel = 0
fanSpeed = 0
Humidity = 0
otherBatteryLevel = 0
wifiStatus = "Good"
super.init()
self.centralManager = CBCentralManager(delegate: self, queue: nil)
// self.periphal = CBPeripheralManager(delegate: self, queue: nil)
}
My didupdateState code:
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if #available(iOS 10.0, *) {
if centralManager.state == CBManagerState.poweredOn{
bluetoothOn = true
self.centralManager.scanForPeripherals(withServices: [ATP_SERVICE_UUID], options: [CBCentralManagerScanOptionAllowDuplicatesKey : false])
}
} else {
// Fallback on earlier versions
}
}
My didDiscover code:
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
stopScan()
self.blePeripheral = peripheral
self.RSSI = RSSI
self.blePeripheral.delegate = self
centralManager?.connect(blePeripheral, options: nil)
print("Found BLE module")
// blePeripheral.discoverServices([ATP_SERVICE_UUID])
}
my didConnect code:
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Connected to BLE device")
centralManager?.stopScan()
blePeripheral.delegate = self
blePeripheral.discoverServices([ATP_SERVICE_UUID])
}
My didDiscoverServices code:
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else{
return
}
for service in services{
peripheral.discoverCharacteristics([ATP_UART_WRITE14_UUID, ATP_UART_READ13_UUID, ATP_NOTIFY_UUID], for: service)
}
print("Discovered Services: \(services)")
}
My didDiscoverCharacteristics code:
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else{
return
}
print ("Found \(characteristics.count) characteristics")
for characteristic in characteristics{
if characteristic.uuid.isEqual(ATP_UART_READ13_UUID){
readCharacteristic = characteristic
// blePeripheral.setNotifyValue(true, for: readCharacteristic!)
blePeripheral.readValue(for: characteristic)
print("Rx Characteristic: \(characteristic.uuid)")
}
if(characteristic.uuid.isEqual(ATP_UART_WRITE14_UUID)){
writeCharacteristic = characteristic
print("Tx characteristic: \(characteristic.uuid)")
}
}
}
My didupdateNotificationState code:
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
if(error != nil){
print("Error changing notification state:\(String(describing: error?.localizedDescription))")
} else {
print("Characteristic's value subscribed")
}
if(characteristic.isNotifying){
print("Subscribed. Notification has begun for: \(characteristic.uuid)")
}
}
And lastly, my didUpdateValueFor:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic == readCharacteristic{
// let ASCIIString = NSString(data: characteristic.value!, encoding: String.Encoding.utf8.rawValue)
var stringFromData = String(data: characteristic.value!, encoding: String.Encoding.ascii)
if stringFromData != "" {
let textArray = stringFromData!.components(separatedBy: "\n\r")
for line in textArray{
if line.hasPrefix("bodyTemperature:") {
var splitString = line.characters.split(separator: ":").map(String.init)
bodyTemperature = Float(splitString[1])!
self.delegate?.PQEData(didRecieveData: bodyTemperature)
} else if line.hasPrefix("enclosurePressure:") {
var splitString = line.characters.split(separator: ":").map(String.init)
airPressure = Float(splitString[1])!
self.delegate?.PQEData(didRecieveData: airPressure)
} else if line.hasPrefix("lightLevel:") {
var splitString = line.characters.split(separator: ":").map(String.init)
lightLevel = Float(splitString[1])!
self.delegate?.PQEData(didRecieveData: lightLevel)
} else if line.hasPrefix("humidity:") {
var splitString = line.characters.split(separator: ":").map(String.init)
Humidity = Float(splitString[1])!
self.delegate?.PQEData(didRecieveData: Humidity)
} else if line.hasPrefix("BATT:") {
var splitString = line.characters.split(separator: ":").map(String.init)
otherBatteryLevel = Float(splitString[1])!
self.delegate?.PQEData(didRecieveData: otherBatteryLevel)
} else if line.hasPrefix("fanSpeed:") {
var splitString = line.characters.split(separator: ":").map(String.init)
fanSpeed = Int(splitString[1])!
self.delegate?.PQEData(didRecieveData: fanSpeed)
} else if line.hasPrefix("wifiStatus:") {
var splitString = line.characters.split(separator: ":").map(String.init)
wifiStatus = splitString[1]
self.delegate?.PQEData(didRecieveData: wifiStatus)
}
}
}
}
}
Additional Notes:
1) In the didDiscoverCharaceristics function, I have commented out the call to blePeripheral.setNotifyValue function. That is because on the line for the call to peripheral.discoverCharacteristics call, I am already discovering for the NOTIFY characteristics. I have also tested with the setNotifyValue uncommented and the results are the same.
2) I have a local variable called blePeriphal that stores a reference to the periphal for the ble device. Same applies for writeCharacteristic and readCharacteristic
3) When the blePeripheral.setNotifyValue function was uncommented, the didUpdateNotificationStateFor function was executed. However, the if(error != nil) statement would be true and the error that I would get would be: Error changing notification state:Optional("The request is not supported."). I am not sure if this is related. However, the didUpdateValueFor function would still get executed.
4) The code that is giving me the issue is found in the didUpdateValueFor function and the line is: var stringFromData = String(data: characteristic.value!, encoding: String.Encoding.ascii)
5) Yes, I did try var stringFromData = String(data: characteristic.value!, encoding: String.Encoding.utf8) and var stringFromData = String(data: characteristic.value!, encoding: String.Encoding.utf8.rawValue) and the results were the same. StringFromData is all /0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0/0
Update:
6) So after some testing, I have come across an issue that may be related. The didUpdateValueFor delegate is only being called once and after that, never again. I have verified this by constantly sending a string once every 1/4 second.

How do I handle NSInlineData from Bluetooth scanning?

I want to use data that is passed through Bluetooth scan.
and I want to split and use data from NSInlineData.
I import data via Bluetooth scan as follows.
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if !peripherals.contains(peripheral){
peripherals.append(peripheral)
}
let key = peripheral.identifier.uuidString
let data = advertisementData.description
let rawData = advertisementData
rawData.forEach { (key, val) in
print("\(key) : \(val)")
}
print("uuid: \(key)")
if let previous = datas[key] {
if (previous != data) {
datas.updateValue(data, forKey: key)
rawdatas.updateValue(advertisementData, forKey: key)
}
} else {
datas[key] = data
rawdatas[key] = advertisementData
}
}
The form of the data is as follows.
//uuid: 81DB0A8E-6C47-1ADF-E1C5-3D7D4269D66D
//kCBAdvDataIsConnectable : 0
//kCBAdvDataServiceData : {
// FEAA = <20000200 19400000 03d80000 0000>;
//}
//kCBAdvDataServiceUUIDs : (
// FEAA
//)
The following data were extracted.
if rawdata["kCBAdvDataServiceData"] != nil{
let parseData:NSDictionary = rawdata["kCBAdvDataServiceData"] as! NSDictionary
let data = parseData.allValues.first
// data => optional(<20000200 19400000 03d80000 0000>)
// data type => _NSinlineData
}
I want to take out the number 5,6,7,8(0200) from the data and replace it with a decimal number. I don't know how to handle NSInlineData.
rawdata["kCBAdvDataServiceData"] as? [CBUUID: Data]
NSDictionary is bridged to Dictionary in Swift, which can be represented as [:].
_NSInlineData is the internal type (as indicated by _). It is NSData, which is bridged to Data in Swift.

BLE IOS - CBPeripheralManager .updatevalue

I am trying to send data (image) between 2 devices (both iPhones).
This code is for the CBPeripheralManager that advertises:
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
switch peripheral.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")
updateAdvertisingData()
}
}
func updateAdvertisingData() {
if (cameraPeripheralManager.isAdvertising) {
cameraPeripheralManager.stopAdvertising()
}
let advertisementData = String(format: "%#", "advertisementData")
char = CBMutableCharacteristic(type: CHAR_UUID, properties: [.notify], value: nil, permissions: [.readable])
myRoei = CBMutableService(type: RX_UUID, primary: true)
myRoei.characteristics = [char]
cameraPeripheralManager.add(myRoei)
cameraPeripheralManager.startAdvertising([CBAdvertisementDataServiceUUIDsKey:[RX_UUID], CBAdvertisementDataLocalNameKey: advertisementData])
}
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
print("didSubscribeTo")
if let img = UIImage(named: "maiden") {
let data = UIImagePNGRepresentation(img)
self.sendData(data: data!)
}
}
func sendData(data: Data){
data.withUnsafeBytes { (u8Ptr: UnsafePointer<UInt8>) in
let mutRawPointer = UnsafeMutableRawPointer(mutating: u8Ptr)
let uploadChunkSize = 20
let totalSize = data.count
var offset = 0
while offset < totalSize {
let chunkSize = offset + uploadChunkSize > totalSize ? totalSize - offset : uploadChunkSize
let chunk = Data(bytesNoCopy: mutRawPointer+offset, count: chunkSize, deallocator: Data.Deallocator.none)
offset += chunkSize
print("The offset is: + \(offset)")
print("Total size is: + \(totalSize)")
cameraPeripheralManager.updateValue(chunk, for: char, onSubscribedCentrals: nil)
}
}
}
In the CBCentralManager I am using didUpdateValueFor characteristic and the it is always nil.
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
print(characteristic.value as Any)
switch characteristic.uuid {
case CHAR_UUID:
print("Char value: \(String(describing: characteristic.value))")
case RX_UUID: break
//
default:
print("Unhandled Characteristic UUID: \(characteristic.uuid)")
}
}
What am I doing wrong?

How to get NSDictionary from NSData out of bluetooth characteristic in Swift

I try to get NSDictionary from NSData of bluetooth characteristic but I got error message, "NSKeyedUnarchiver initForReadingWithData:]: incomprehensible archive".
peripheralManager send NSDictionary like this to Central.
func peripheralManager(peripheral: CBPeripheralManager!, didReceiveReadRequest request: CBATTRequest!) {
var responseDictonary: Dictionary = [
"id" : 11111,
"name" : "hoge"
]
request.value = NSKeyedArchiver.archivedDataWithRootObject(responseDictonary)
peripheralManager.respondToRequest(request, withResult: CBATTError.Success)
}
CentralManager recieve peripheral like this.
func peripheral(peripheral: CBPeripheral!, didUpdateValueForCharacteristic characteristic: CBCharacteristic!, error: NSError!) {
if (error != nil) {
return
}
if characteristic.UUID == BLECharacteristicUUID {
let data : NSData = characteristic.value
if let recieveDictonary = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? NSDictionary {
var id = recieveDictonary["id"] as Int
var name = recieveDictonary["name"] as String
Tracker.sharedInstance.debug("\(id) \(name)")
}
}
}
Do you have any solutions?
Try doing the following it might help you find the root cause.
Just try to unarchive immediately after you archive in the first method,
It will help you find if there is any issue while archiving itself.
(your archiving code looks fine still make sure its working properly)
check for a nil value for "data" in the second method.

Resources