Cannot Convert String to Int from BLE Data - ios

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")
}
}
}

Related

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.

Closure returning data before async work is done

UPDATED WITH PROPOSED SOLUTION AND ADDITIONAL QUESTION
I'm officially stuck and also in callback hell. I have a call to Firebase retrieving all articles in the FireStore. Inside each article object is a an Image filename that translates into a storage reference location that needs to be passed to a function to get the absolute URL back. I'd store the URL in the data, but it could change. The problem is the ArticleListener function is prematurely returning the closure (returnArray) without all the data and I can't figure out what I'm missing. This was working fine before I added the self.getURL code, but now it's returning the array back empty and then doing all the work.
If anyone has some bonus tips here on chaining the methods together without resorting to PromiseKit or GCD that would be great, but open to all suggestions to get this to work as is
and/or refactoring for more efficiency / readability!
Proposed Solution with GCD and updated example
This is calling the Author init after the Article is being created. I am trying to transform the dataDict dictionary so it get's used during the Author init for key ["author"]. I think I'm close, but not 100% sure if my GCD enter/leave calls are happening in the right order
public func SetupArticleListener(completion: #escaping ([Article]) -> Void) {
var returnArray = [Article]()
let db = FIRdb.articles.reference()
let listener = db.addSnapshotListener() { (querySnapshot, error) in
returnArray = [] // nil this out every time
if let error = error {
print("Error in setting up snapshot listener - \(error)")
} else {
let fireStoreDispatchGrp = DispatchGroup() /// 1
querySnapshot?.documents.forEach {
var dataDict = $0.data() //mutable copy of the dictionary data
let id = $0.documentID
//NEW EXAMPLE WITH ADDITIONAL TASK HERE
if let author = $0.data()["author"] as? DocumentReference {
author.getDocument() {(authorSnapshot, error) in
fireStoreDispatchGrp.enter() //1
if let error = error {
print("Error getting Author from snapshot inside Article getDocumentFunction - leaving dispatch group and returning early")
fireStoreDispatchGrp.leave()
return
}
if let newAuthor = authorSnapshot.flatMap(Author.init) {
print("Able to build new author \(newAuthor)")
dataDict["author"] = newAuthor
dataDict["authorId"] = authorSnapshot?.documentID
print("Data Dict successfully mutated \(dataDict)")
}
fireStoreDispatchGrp.leave() //2
}
}
///END OF NEW EXAMPLE
if let imageURL = $0.data()["image"] as? String {
let reference = FIRStorage.articles.referenceForFile(filename: imageURL)
fireStoreDispatchGrp.enter() /// 2
self.getURL(reference: reference){ result in
switch result {
case .success(let url) :
dataDict["image"] = url.absoluteString
case .failure(let error):
print("Error getting URL for author: \n Error: \(error) \n forReference: \(reference) \n forArticleID: \(id)")
}
if let newArticle = Article(id: id, dictionary: dataDict) {
returnArray.append(newArticle)
}
fireStoreDispatchGrp.leave() ///3
}
}
}
//Completion block
print("Exiting dispatchGroup all data should be setup correctly")
fireStoreDispatchGrp.notify(queue: .main) { ///4
completion(returnArray)
}
}
}
updateListeners(for: listener)
}
Original Code
Calling Setup Code
self.manager.SetupArticleListener() { [weak self] articles in
print("🌈🌈🌈🌈🌈🌈🌈In closure function to update articles🌈🌈🌈🌈🌈🌈🌈")
self?.articles = articles
}
Article Listener
public func SetupArticleListener(completion: #escaping ([Article]) -> Void) {
var returnArray = [Article]()
let db = FIRdb.articles.reference()
let listener = db.addSnapshotListener() { (querySnapshot, error) in
returnArray = [] // nil this out every time
if let error = error {
printLog("Error retrieving documents while adding snapshotlistener, Error: \(error.localizedDescription)")
} else {
querySnapshot?.documents.forEach {
var dataDict = $0.data() //mutable copy of the dictionary data
let id = $0.documentID
if let imageURL = $0.data()["image"] as? String {
let reference = FIRStorage.articles.referenceForFile(filename: imageURL)
self.getURL(reference: reference){ result in
switch result {
case .success(let url) :
print("Success in getting url from reference \(url)")
dataDict["image"] = url.absoluteString
print("Dictionary XFORM")
case .failure(let error):
print("Error retrieving URL from reference \(error)")
}
if let newArticle = Article(id: id, dictionary: dataDict) {
printLog("Success in creating Article with xformed url")
returnArray.append(newArticle)
}
}
}
}
print("🌈🌈🌈🌈🌈🌈🌈 sending back completion array \(returnArray)🌈🌈🌈🌈🌈🌈🌈")
completion(returnArray)
}
}
updateListeners(for: listener)
}
GetURL
private func getURL(reference: StorageReference, _ result: #escaping (Result<URL, Error>) -> Void) {
reference.downloadURL() { (url, error) in
if let url = url {
result(.success(url))
} else {
if let error = error {
print("error")
result(.failure(error))
}
}
}
}
You need dispatch group as the for loop contains multiple asynchronous calls
public func SetupArticleListener(completion: #escaping ([Article]) -> Void) {
var returnArray = [Article]()
let db = FIRdb.articles.reference()
let listener = db.addSnapshotListener() { (querySnapshot, error) in
returnArray = [] // nil this out every time
if let error = error {
printLog("Error retrieving documents while adding snapshotlistener, Error: \(error.localizedDescription)")
} else {
let g = DispatchGroup() /// 1
querySnapshot?.documents.forEach {
var dataDict = $0.data() //mutable copy of the dictionary data
let id = $0.documentID
if let imageURL = $0.data()["image"] as? String {
let reference = FIRStorage.articles.referenceForFile(filename: imageURL)
g.enter() /// 2
self.getURL(reference: reference){ result in
switch result {
case .success(let url) :
print("Success in getting url from reference \(url)")
dataDict["image"] = url.absoluteString
print("Dictionary XFORM")
case .failure(let error):
print("Error retrieving URL from reference \(error)")
}
if let newArticle = Article(id: id, dictionary: dataDict) {
printLog("Success in creating Article with xformed url")
returnArray.append(newArticle)
}
g.leave() /// 3
}
}
}
g.notify(queue:.main) { /// 4
print("🌈🌈🌈🌈🌈🌈🌈 sending back completion array \(returnArray)🌈🌈🌈🌈🌈🌈🌈")
completion(returnArray)
}
}
}
updateListeners(for: listener)
}

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.

EXC_BAD_ACCESS error when inserting NSManagedObject

I get an EXC_BAD_ACCESS error when inserting an NSManagedObject at the superclass init call. The error doesn't always occur, so my guess is that it has something to do with threading, however I'm very new to iOS development so I might be completely wrong.
In the code below I mark the line where the error occurs with a comment.
import Foundation
import CoreData
#objc(Measurement)
public class Measurement: NSManagedObject {
convenience init(sensorId: Int32, fromDatetime: Int64, pm10: Float, pm25: Float, airQualityIndex: Float32, pollutionLevel: Int16,
latitude: Double, longitude: Double, source: String, windDeg: Float32, windSpeed: Float32,
context: NSManagedObjectContext) {
if let ent = NSEntityDescription.entity(forEntityName: "Measurement", in: context) {
self.init(entity: ent, insertInto: context) // Thread 10: EXC_BAD_ACCESS (code=1, address=0xfffffffc)
self.fromDatetime = fromDatetime
self.pm10 = pm10
// omitted value asignments
} else {
fatalError("Unable to find entity name Measurement")
}
}
}
The inserting happens after a call to an api inside a for loop.
let task = session.dataTask(with: request) { (data, response, error) in
// if an error occurs, print it and re-enable the UI
func displayError(_ error: String) {
print(error)
}
/* GUARD: Was there an error? */
guard (error == nil) else {
displayError("There was an error with your request: \(String(describing: error))")
return
}
/* GUARD: Did we get a successful 2XX response? */
guard let statusCode = (response as? HTTPURLResponse)?.statusCode, statusCode >= 200 && statusCode <= 299 else {
displayError("Your request returned a status code other than 2xx!")
return
}
/* GUARD: Was there any data returned? */
guard let data = data else {
displayError("No data was returned by the request!")
return
}
// parse the data
let parsedResult: [[String:AnyObject]]!
do {
parsedResult = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [[String:AnyObject]]
} catch {
displayError("Could not parse the data as JSON: '\(data)'")
return
}
let delegate = UIApplication.shared.delegate as! AppDelegate
let stack = delegate.stack
var counter = 0
for measurement in parsedResult {
if counter == 0 {
print(measurement)
}
guard let sensorId = measurement[Constants.MeasurementModelKeys.sensorId] as? Int32 else {
print("Cannot find key 'sensor_id' in \(measurement)")
continue
}
// other guarded assignments ...
_ = Measurement(sensorId: sensorId, fromDatetime: fromDatetime, pm10: pm10, pm25: pm25,
airQualityIndex: airQualityIndex, pollutionLevel: pollutionLevel, latitude: latitude, longitude: longitude,
source: source, windDeg: windDeg, windSpeed: windSpeed,
context: stack.context) // << this is where the insertion happens
counter += 1
}
print("counter counted: \(counter) elements")
}
// start the task!
task.resume()
}
Any help is much appreciated (including tips on how to track down the exact cause of the error).
Core Data is indeed very touchy about threading. Inserting new objects in a context should be performed on that context queue.
Try this :
stack.context.perform {
_ = Measurement(sensorId: sensorId, fromDatetime: fromDatetime, pm10: pm10, pm25: pm25,
airQualityIndex: airQualityIndex, pollutionLevel: pollutionLevel, latitude: latitude, longitude: longitude,
source: source, windDeg: windDeg, windSpeed: windSpeed,
context: stack.context)
}
Note that this is now an asynchronous call. Make sure this doesn't break your code somewhere else.

ios ble "Characteristic User Description"

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.

Resources