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.
Related
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")
}
}
}
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.
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?
In my iOS app, I want to search device using swift 3. How do I implement this functionality in my app using core Bluetooth?
this below code i what i wrote from rearching in internet and help of my friends to list bluetooth devices and connect to heart rate measure service
class HRMViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var cbCentralManager: CBCentralManager?
var cbPeripheral: CBPeripheral?
var discoveredPeripherals = [CBPeripheral]()
var connected = ""
var bodyData = ""
var manufacturer = ""
var deviceData = ""
var heartRate: UInt16 = 0
var localNameArray = [String]()
var pulseTimer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Do any additional setup after loading the view.
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.backgroundColor = UIColor.lightGray
tableView.layer.borderColor = UIColor.lightGray.cgColor
tableView.layer.borderWidth = 1.0
tableView.layer.cornerRadius = 2.0
tableView.layer.masksToBounds = true
let centralManager = CBCentralManager(delegate: self, queue: nil)
self.cbCentralManager = centralManager
}
}
extension HRMViewController : CBCentralManagerDelegate, CBPeripheralDelegate {
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
let localName = advertisementData[CBAdvertisementDataLocalNameKey] as? String ?? ""
if localName.count > 0 {
if RSSI.intValue > signalStrengthMax {
if let index = self.discoveredPeripherals.index(of: peripheral) {
self.discoveredPeripherals.remove(at: index)
self.localNameArray.remove(at: index)
self.tableView.reloadData()
}
return
}
if RSSI.intValue < signalStrengthMin {
if let index = self.discoveredPeripherals.index(of: peripheral) {
self.discoveredPeripherals.remove(at: index)
self.localNameArray.remove(at: index)
self.tableView.reloadData()
}
return
}
print("Discovered: \(peripheral.name ?? "a device") with udid: \(peripheral.identifier) at \(RSSI)")
if !(self.discoveredPeripherals.contains(peripheral)) {
self.discoveredPeripherals.append(peripheral)
self.localNameArray.append(localName)
self.tableView.reloadData()
}
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices(nil)
self.connected = "Connected: \((peripheral.state == .connected) ? "Yes" : "No")"
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state != .poweredOn {
return
}
if central.state == .poweredOn {
let deviceService = CBUUID(string: HRMConstants.Services.deviceInfoUUID)
let heartRateService = CBUUID(string: HRMConstants.Services.heartRateUUID)
let services = [deviceService,heartRateService]
cbCentralManager?.scanForPeripherals(withServices: services, options: nil)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
for service in peripheral.services ?? [CBService]() {
peripheral.discoverCharacteristics(nil, for: service)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if service.uuid == CBUUID(string: HRMConstants.Services.heartRateUUID) {
for character in service.characteristics ?? [CBCharacteristic]() {
if character.uuid == CBUUID(string: HRMConstants.Characteristics.measurementUUID) {
self.cbPeripheral?.setNotifyValue(true, for: character)
}
else if character.uuid == CBUUID(string: HRMConstants.Characteristics.bodyLocationUUID) {
self.cbPeripheral?.readValue(for: character)
}
}
}
if service.uuid == CBUUID(string: HRMConstants.Services.deviceInfoUUID) {
for character in service.characteristics ?? [CBCharacteristic]() {
if character.uuid == CBUUID(string: HRMConstants.Characteristics.manufacturerNameUUID) {
self.cbPeripheral?.readValue(for: character)
}
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.uuid == CBUUID(string: HRMConstants.Characteristics.measurementUUID) {
self.getHeartRateData(for: characteristic, error: error)
}
if characteristic.uuid == CBUUID(string: HRMConstants.Characteristics.manufacturerNameUUID) {
self.getManufacturerName(for: characteristic)
}
else if characteristic.uuid == CBUUID(string: HRMConstants.Characteristics.bodyLocationUUID) {
self.getBodyLocation(for: characteristic)
}
}
}
extension HRMViewController {
func getHeartRateData(for characteristic: CBCharacteristic, error: Error?) {
if let _ = error {
print("Error: \(error?.localizedDescription ?? "Unknown")")
return
}
guard let value = characteristic.value else { return }
let bpm : UInt16
if (value[0] & 0x01) == 0 {
bpm = UInt16(value[1])
} else {
bpm = UInt16(littleEndian: value.subdata(in: 1..<3).withUnsafeBytes { $0.pointee } )
}
heartRate = bpm
doHeartBeat()
pulseTimer = Timer.scheduledTimer(timeInterval: 60.0 / Double(heartRate), target: self, selector: #selector(self.doHeartBeat), userInfo: nil, repeats: false)
/*
var reportData = [UInt8](value)
var bpm: UInt16 = 0
if (reportData[0] & 0x01) == 0 {
bpm = UInt16(reportData[1])
}
else {
bpm = CFSwapInt16LittleToHost(UInt16(reportData[1]))
}
heartRate = bpm
doHeartBeat()
pulseTimer = Timer.scheduledTimer(timeInterval: 60.0 / Double(heartRate), target: self, selector: #selector(self.doHeartBeat), userInfo: nil, repeats: false)
*/
}
func getManufacturerName(for characteristic: CBCharacteristic) {
guard let value = characteristic.value else { return }
let manufacturerName = String(data: value, encoding: .utf8)
}
func getBodyLocation(for characteristic: CBCharacteristic) {
guard let value = characteristic.value else { return }
var location = ""
// 1
var bodyData = [UInt8](value)
if !bodyData.isEmpty {
let bodyLocation: UInt8 = bodyData[0]
// 2
location = "Body Location: \(bodyLocation == 1 ? "Chest" : "Undefined")"
// 3
}
else {
// 4
location = "Body Location: N/A"
}
print(location)
}
#objc func doHeartBeat() {
}
}
extension HRMViewController : UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = "\(localNameArray[indexPath.row]) \(discoveredPeripherals[indexPath.row].identifier )"
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let newPeripheral = discoveredPeripherals[indexPath.row]
if let _ = cbPeripheral { // had a device selected already
if newPeripheral != cbPeripheral! { // clean up old peripheral
self.cleanUp()
}
}
cbPeripheral = newPeripheral
self.cbCentralManager?.stopScan()
cbPeripheral?.delegate = self
self.cbCentralManager?.connect(cbPeripheral!, options: nil)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return discoveredPeripherals.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 45.0
}
func cleanUp() {
if !(cbPeripheral!.state == .connected) { // Don't do anything if we're not connected
return
}
if let services = self.cbPeripheral!.services {
for eachService in services {
if let characteristics = eachService.characteristics {
for eachCharacteristic in characteristics {
if eachCharacteristic.uuid.isEqual(CBUUID(string: HRMConstants.Characteristics.bodyLocationUUID)) {
if eachCharacteristic.isNotifying {
self.cbPeripheral?.setNotifyValue(false, for: eachCharacteristic)
}
}
if eachCharacteristic.uuid.isEqual(CBUUID(string: HRMConstants.Characteristics.manufacturerNameUUID)) {
if eachCharacteristic.isNotifying {
self.cbPeripheral?.setNotifyValue(false, for: eachCharacteristic)
}
}
if eachCharacteristic.uuid.isEqual(CBUUID(string: HRMConstants.Characteristics.measurementUUID)) {
if eachCharacteristic.isNotifying {
self.cbPeripheral?.setNotifyValue(false, for: eachCharacteristic)
}
}
}
}
}
}
self.cbCentralManager?.cancelPeripheralConnection(self.cbPeripheral!)
self.cbPeripheral = nil
}
}
and for constants
struct HRMConstants {
struct Services {
static let deviceInfoUUID = "180A"
static let heartRateUUID = "180D"
}
struct Characteristics {
static let measurementUUID = "2A37"
static let bodyLocationUUID = "2A38"
static let manufacturerNameUUID = "2A29"
}
}
with this you will get the basic idea and then you can try it for yourself
see for ble doc for different bluetooth services
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.