Writing data to bluetooth peripheral fails - ios

I have a program running on a Windows PC which sends/receives data over a COM port. The data is transmitted over Bluetooth via a HM10 Bluetooth module.
I'm able to discover the peripheral, connect to it, discover its services and characteristics all successfully. However my issue is with sending data. The central is my iPhone. The PC acts as the peripheral.
First my code.
import CoreBluetooth
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var peripheralNameLabel: UILabel!
#IBOutlet weak var noOfServicesLabel: UILabel!
#IBOutlet weak var sendBytesButton: UIButton!
#IBOutlet weak var sendStringButton: UIButton!
#IBOutlet weak var responseTextView: UITextView!
private let serviceUUID = CBUUID(string: "FFE0")
private var characteristicUUID = CBUUID(string: "FFE1")
private var manager: CBCentralManager!
private var peripheral: CBPeripheral!
private var characteristic: CBCharacteristic!
override func viewDidLoad() {
super.viewDidLoad()
manager = CBCentralManager(delegate: self, queue: nil)
}
#IBAction func didTapSendBytesButton(sender: UIButton) {
let bytes: [UInt8] = [0x35, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
let data = NSData(bytes: bytes, length: bytes.count)
peripheral.writeValue(data, forCharacteristic: characteristic, type: .WithResponse)
}
#IBAction func didTapSendStringButton(sender: UIButton) {
let string = "5100000000"
if let data = string.dataUsingEncoding(NSUTF8StringEncoding) {
peripheral.writeValue(data, forCharacteristic: characteristic, type: .WithResponse)
} else {
sendStringButton.enabled = false
sendStringButton.setTitle("😟", forState: .Normal)
}
}
}
extension ViewController: CBCentralManagerDelegate {
func centralManagerDidUpdateState(central: CBCentralManager) {
print(#function)
switch central.state {
case .Unsupported:
print("Unsupported")
case .Unauthorized:
print("Unauthorized")
case .PoweredOn:
print("Powered On")
navigationItem.title = "Connecting..."
central.scanForPeripheralsWithServices([serviceUUID], options: nil)
case .Resetting:
print("Resetting")
case .PoweredOff:
print("Powered Off")
case .Unknown:
print("Unknown")
}
}
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
print(#function)
print("Discovered \(peripheral.name) at \(RSSI)")
peripheralNameLabel.text = peripheral.name
if peripheral.name == nil || peripheral.name == "" {
return
}
if self.peripheral == nil || self.peripheral.state == .Disconnected {
self.peripheral = peripheral
central.connectPeripheral(peripheral, options: nil)
central.stopScan()
}
}
func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
print(#function)
navigationItem.title = "Connected!"
sendBytesButton.enabled = true
sendStringButton.enabled = true
peripheral.delegate = self
peripheral.discoverServices([serviceUUID])
}
func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) {
self.peripheral = nil
central.scanForPeripheralsWithServices(nil, options: nil)
}
func centralManager(central: CBCentralManager, didFailToConnectPeripheral peripheral: CBPeripheral, error: NSError?) {
print(#function)
self.peripheral = nil
}
}
extension ViewController: CBPeripheralDelegate {
func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) {
print(#function)
guard let services = peripheral.services else {
return
}
noOfServicesLabel.text = "\(services.count)"
for service in services {
print(service.UUID)
if service.UUID == serviceUUID {
peripheral.discoverCharacteristics(nil, forService: service)
}
}
}
func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) {
print(#function)
guard let characteristics = service.characteristics else {
return
}
for characteristic in characteristics {
print("characteristic: \(characteristic.UUID)")
if characteristic.UUID == characteristicUUID {
self.characteristic = characteristic
peripheral.setNotifyValue(true, forCharacteristic: characteristic)
}
}
}
func peripheral(peripheral: CBPeripheral, didWriteValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
print(#function)
print(error)
}
func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
print(#function)
if characteristic.UUID == characteristicUUID {
print("Got reply from: \(characteristic.UUID)")
if let data = characteristic.value, let string = String(data: data, encoding: NSUTF8StringEncoding) {
responseTextView.text = string
} else {
print("No response!")
}
}
}
}
I'm supposed to send a byte array like this,
0x35 0x31 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
to the peripheral and if the peripheral successfully receives it, I get this as a response,
0x43 0x41 0x4E 0x20 0x31 0x31 0x2F 0x35 0x30 0x30 0x0D 0x0A.
How it's supposed to look like.
This is what really happens.
Data packets are broken into pieces when transferred. Therefore I don't get the success response.
Weird part is sometimes, very rarely it actually works! The data gets sent properly (like shown in the very first image) and I receive the success response. But failures occur more often than not. Like 99% of the time.
I tried both ways, sending data as a byte array (didTapSendBytesButton()) and sending it as a string converted (didTapSendStringButton()). Both resulted in the same.
Also tested it with app called Bluetooth Serial. Same result.
I can't figure out why this is happening.

You are sending less than 20 bytes, so the Bluetooth data will be sent in a single transmission.
The problem is on your receiving side, or actually how you have structured your communications.
A serial port can only send or receive a single byte at a time, so even though iOS sends all of the bytes at once (this is a side-effect how how serial ports are emulated over GATT), Windows has to present them to the virtual COM port driver one at a time. Windows has buffering on COM ports so that bytes aren't lost if your program doesn't read fast enough, which is why you see more than one byte per "RX" but there is no concept of a "packet" in the COM driver, so it isn't aware of how many bytes were sent or that it should wait until all have been received and deliver them as one group.
The ultimate answer is that you need to modify your message so that it has some sort of delimiter (even a simple \n) that lets the receiving program know that the end of a message has been received. It can then validate the message and respond appropriately.
Or, if you can't control the receiving program, and that program works with other sending code, then I suspect you have a problem with the data you are sending, because the fact that the serial bytes are split acros multiple RX calls is normal and the receiving program must have been written to handle this

Related

have to initiate a BLE peripheral every time i use it in Swift

with CBCentralManager i succesully scan multiple BLE UART devices and connect to them, de devices are stored in the array periphirals[], but when i want to send data to them one by one, only the last one connected i can send succesfully send data to, i solved it by calling blePeripheral?.discoverServices([ParticlePeripheral.BLEService_UUID])before writing data again, but i think this is not the right solution, can someone explain what i am doing wrong?
Below the code i use, the scanning and connecting start by didload
and startCyclus starts the first device, after the data recieved "didUpdateValueFor" is entered , getting the data and direct send data to the next peripheral
import Foundation
import UIKit
import CoreBluetooth
var txCharacteristic : CBCharacteristic?
var rxCharacteristic : CBCharacteristic?
var blePeripheral : CBPeripheral?
var characteristicASCIIValue = NSString()
var Running = false
var currentNode = 0;
var updateService = false
var maxNodes = Int()
class ViewController : UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate, UITableViewDelegate{
//Data
var centralManager : CBCentralManager!
var RSSIs = [NSNumber]()
var data = NSMutableData()
var peripherals: [CBPeripheral] = []
var characteristicValue = [CBUUID: NSData]()
var timer = Timer()
var characteristics = [String : CBCharacteristic]()
var teller = 0
//UI
#IBOutlet weak var baseTableView: UITableView!
#IBOutlet weak var refreshButton: UIBarButtonItem!
#IBAction func naarRun(_ sender: Any) {
print("naarRun")
self.performSegue(withIdentifier:"naarRunView" , sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if (segue.identifier == "naarRunView") {
let vc = segue.destination as! ViewRun
vc.peripherals = peripherals
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//print("View Cleared")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("Stop Scanning")
centralManager?.stopScan()
}
/*Okay, now that we have our CBCentalManager up and running, it's time to start searching for devices. You can do this by calling the "scanForPeripherals" method.*/
func startScan() {
peripherals = []
print("Now Scanning...")
self.timer.invalidate()
centralManager?.scanForPeripherals(withServices: [ParticlePeripheral.BLEService_UUID] , options: [CBCentralManagerScanOptionAllowDuplicatesKey:false])
Timer.scheduledTimer(withTimeInterval: 3, repeats: false) {_ in
self.cancelScan()
self.connectToAllDevices()
}
}
/*We also need to stop scanning at some point so we'll also create a function that calls "stopScan"*/
func cancelScan() {
self.centralManager?.stopScan()
print("Scan Stopped")
print("Number of Peripherals Found: \(peripherals.count)")
}
func disconnectFromDevice () {
if blePeripheral != nil {
// We have a connection to the device but we are not subscribed to the Transfer Characteristic for some reason.
// Therefore, we will just disconnect from the peripheral
centralManager?.cancelPeripheralConnection(blePeripheral!)
}
}
func restoreCentralManager() {
//Restores Central Manager delegate if something went wrong
centralManager?.delegate = self
}
/*
Called when the central manager discovers a peripheral while scanning. Also, once peripheral is connected, cancel scanning.
*/
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral,advertisementData: [String : Any], rssi RSSI: NSNumber) {
blePeripheral = peripheral
teller+=1
self.peripherals.append(peripheral)
self.RSSIs.append(RSSI)
peripheral.delegate = self
//self.baseTableView.reloadData()
//if blePeripheral == nil {
print("Found new pheripheral devices with services")
print("Peripheral name: \(String(describing: peripheral.name))")
print("**********************************")
print ("Advertisement Data : \(advertisementData)")
//}
}
//Peripheral Connections: Connecting, Connected, Disconnected
//-Connection
func connectToDevice () {
centralManager?.connect(blePeripheral!, options: nil)
}
func connectToAllDevices(){
var seconds = 0.0
for per in peripherals
{
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
blePeripheral = per
print("Connecting naar " + (blePeripheral?.name!)!)
self.connectToDevice ()
}
seconds = seconds + 0.5;
}
}
/*
Invoked when a connection is successfully created with a peripheral.
This method is invoked when a call to connect(_:options:) is successful. You typically implement this method to set the peripheral’s delegate and to discover its services.
*/
//-Connected
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("*****************************")
print("Connection complete")
print("Peripheral info: \(String(describing: blePeripheral))")
//Erase data that we might have
data.length = 0
//Discovery callback
peripheral.delegate = self
//Only look for services that matches transmit uuid
peripheral.discoverServices([ParticlePeripheral.BLEService_UUID])
/*
//Once connected, move to new view controller to manager incoming and outgoing data
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let uartViewController = storyboard.instantiateViewController(withIdentifier: "UartModuleViewController") as! UartModuleViewController
uartViewController.peripheral = peripheral
navigationController?.pushViewController(uartViewController, animated: true)
*/
}
/*
Invoked when the central manager fails to create a connection with a peripheral.
*/
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
if error != nil {
print("Failed to connect to peripheral")
return
}
}
func disconnectAllConnection() {
centralManager.cancelPeripheralConnection(blePeripheral!)
}
/*
Invoked when you discover the peripheral’s available services.
This method is invoked when your app calls the discoverServices(_:) method. If the services of the peripheral are successfully discovered, you can access them through the peripheral’s services property. If successful, the error parameter is nil. If unsuccessful, the error parameter returns the cause of the failure.
*/
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)")
}
/*
Invoked when you discover the characteristics of a specified service.
This method is invoked when your app calls the discoverCharacteristics(_:for:) method. If the characteristics of the specified service are successfully discovered, you can access them through the service's characteristics property. If successful, the error parameter is nil. If unsuccessful, the error parameter returns the cause of the failure.
*/
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
print("*******************************************************")
if ((error) != nil) {
print("Error discovering services: \(error!.localizedDescription)")
return
}
guard let characteristics = service.characteristics else {
return
}
print("Found \(characteristics.count) characteristics!")
for characteristic in characteristics {
//looks for the right characteristic
if characteristic.uuid.isEqual(ParticlePeripheral.BLE_Characteristic_uuid_Rx) {
rxCharacteristic = characteristic
//Once found, subscribe to the this particular characteristic...
peripheral.setNotifyValue(true, for: rxCharacteristic!)
// We can return after calling CBPeripheral.setNotifyValue because CBPeripheralDelegate's
// didUpdateNotificationStateForCharacteristic method will be called automatically
peripheral.readValue(for: characteristic)
print("Rx Characteristic: \(characteristic.uuid)")
}
if characteristic.uuid.isEqual(ParticlePeripheral.BLE_Characteristic_uuid_Tx){
txCharacteristic = characteristic
print("Tx Characteristic: \(characteristic.uuid)")
}
peripheral.discoverDescriptors(for: characteristic)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
print("*******************************************************")
if error != nil {
print("\(error.debugDescription)")
return
}
guard let descriptors = characteristic.descriptors else { return }
descriptors.forEach { descript in
print("function name: DidDiscoverDescriptorForChar \(String(describing: descript.description))")
print("Rx Value \(String(describing: rxCharacteristic?.value))")
print("Tx Value \(String(describing: txCharacteristic?.value))")
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
print("*******************************************************")
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)")
}
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("Disconnected")
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
guard error == nil else {
print("Error discovering services: error")
return
}
print("Message sent")
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) {
guard error == nil else {
print("Error discovering services: error")
return
}
print("Succeeded!")
}
// MARK: - Getting Values From Characteristic
/** After you've found a characteristic of a service that you are interested in, you can read the characteristic's value by calling the peripheral "readValueForCharacteristic" method within the "didDiscoverCharacteristicsFor service" delegate.
*/
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
guard characteristic == rxCharacteristic,
let characteristicValue = characteristic.value,
let ASCIIstring = NSString(data: characteristicValue,
encoding: String.Encoding.utf8.rawValue)
else { return }
characteristicASCIIValue = ASCIIstring
let ontvangen = String(characteristicASCIIValue)
let waarde = haalwaarde(recieved: ontvangen)
print("Value Recieved: " + String(characteristicASCIIValue) )
NotificationCenter.default.post(name:NSNotification.Name(rawValue: "Notify"), object: self)
if(updateService){updateService=false; return}
if(!waarde.isNumber){print("waarde is no number");return}
if(Running ){Vervolg()}
}
func haalNode(recieved:String)->String{
//nodenr
if(recieved.count<10){return "fout";}
let temp = recieved
let lengte = recieved.count;
let start = temp.index(temp.startIndex, offsetBy: 7)
let end = temp.index(temp.endIndex, offsetBy: -(lengte-8))
let range = start..<end
let nodeNr = String(temp[range])
//print("nodeNr=", nodeNr)
return nodeNr
}
func haalwaarde(recieved:String)->String{
//Reactietijd
if(recieved.count<10){return "fout"};
let temp = recieved
let lengte = recieved.count;
let start = temp.index(temp.startIndex, offsetBy: 1)
let end = temp.index(temp.endIndex, offsetBy: -(lengte-7))
let range = start..<end
let Reactietijd = String(temp[range])
//print("\nreactietijd=", Reactietijd)
return Reactietijd
}
func startCyclus(){
currentNode = 0
if maxNodes == 0 {return}
Running = true
blePeripheral = peripherals[0]
updateService = true
blePeripheral?.discoverServices([ParticlePeripheral.BLEService_UUID])
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.writeValue(data: "N 5000G55")
currentNode += 1
}
func Vervolg(){
print("vervolg")
if currentNode == maxNodes {Running = false;return}
blePeripheral = peripherals[currentNode]
updateService = true
blePeripheral?.discoverServices([ParticlePeripheral.BLEService_UUID])
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(10)) {
self.writeValue(data: "N 5000G55")
currentNode += 1
}
}
// Write functions
func writeValue(data: String){
print("schrijfdata " + String(currentNode))
let valueString = (data as NSString).data(using: String.Encoding.utf8.rawValue)
//change the "data" to valueString
if let blePer = blePeripheral{
if let txCharacteristic = txCharacteristic {
blePer.writeValue(valueString!, for: txCharacteristic, type: CBCharacteristicWriteType.withoutResponse)
}
}
}
/*
Invoked when the central manager’s state is updated.
This is where we kick off the scan if Bluetooth is turned on.
*/
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == CBManagerState.poweredOn {
// We will just handle it the easy way here: if Bluetooth is on, proceed...start scan!
print("Bluetooth Enabled")
startScan()
} else {
//If Bluetooth is off, display a UI alert message saying "Bluetooth is not enable" and "Make sure that your bluetooth is turned on"
print("Bluetooth Disabled- Make sure your Bluetooth is turned on")
let alertVC = UIAlertController(title: "Bluetooth is not enabled", message: "Make sure that your bluetooth is turned on", preferredStyle: UIAlertControllerStyle.alert)
let action = UIAlertAction(title: "ok", style: UIAlertActionStyle.default, handler: { (action: UIAlertAction) -> Void in
self.dismiss(animated: true, completion: nil)
})
alertVC.addAction(action)
self.present(alertVC, animated: true, completion: nil)
}
}
}
extension String {
var isNumber: Bool {
let characters = CharacterSet.decimalDigits.inverted
return !self.isEmpty && rangeOfCharacter(from: characters) == nil
}
}
I think it's your logging is hiding what's really happening.
You reassign blePeripheral before each connection, but if any of them take longer than 0.5 seconds, by the time didConnect is called, the device that has connected won't be the one that's referred to by blePeripheral.
I would suggest removing blePeripheral entirely, and explicitly pass the CBPeripheral object into connectToDevice. Also, in didConnect, use the peripheral parameter that's passed in.

BLE Connection between iPhone and iPad with JustWorks bonding

I'm currently working on a app that needs to send data from a iPhone to a iPad. I've been able to find the devices and connecting them by using both the CentralManager and PeriphiralManager. However right now it's showing me a message to asking to pair every single time. But when I press the "NO" option it will still bond by using JustWorks (at least it seems because when I go to bluetooth in settings it will just say "iPad" and I can't see information about the device other than that and it disappears from the list completely when I disconnect).
I was wondering how I could make sure the popup asking for pairing doesn't show up at all. I read somewhere to disable encryption on the Peripheral however I'm not sure how to do this when the iPad for instance is the Peripheral. The code I'm using right now looks as follows (this is the first time I'm working with corebluetooth and I'm currently still messing around with it and trying to get a grasp of how it works).
import UIKit
import CoreBluetooth
import CoreLocation
class ViewController: UIViewController {
#IBOutlet weak var receiveLabel: UILabel!
#IBOutlet weak var sendButton: UIButton!
var centralManager: CBCentralManager!
var peripheralManager: CBPeripheralManager!
var peripherals: [CBPeripheral] = []
var keepScanning: Bool = false
private var timerScanInterval: TimeInterval = 5
static let SERVICE_UUID = CBUUID(string: "4DF91029-B356-463E-9F48-BAB077BF3EF5")
static let RX_UUID = CBUUID(string: "3B66D024-2336-4F22-A980-8095F4898C42")
static let RX_PROPERTIES: CBCharacteristicProperties = .write
static let RX_PERMISSIONS: CBAttributePermissions = .writeable
override func viewDidLoad() {
super.viewDidLoad()
centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
}
}
extension ViewController {
#IBAction func sendMessage(_ sender: UIButton) {
for per in peripherals {
centralManager.connect(per, options: nil)
}
}
func updateAdvertisingData() {
if peripheralManager.isAdvertising {
peripheralManager.stopAdvertising()
}
peripheralManager.startAdvertising([CBAdvertisementDataServiceUUIDsKey: [ViewController.SERVICE_UUID], CBAdvertisementDataLocalNameKey: "Test"])
}
func initService() {
let serialService = CBMutableService(type: ViewController.SERVICE_UUID, primary: true)
let rx = CBMutableCharacteristic(type: ViewController.RX_UUID, properties: ViewController.RX_PROPERTIES, value: nil, permissions: ViewController.RX_PERMISSIONS)
serialService.characteristics = [rx]
peripheralManager.add(serialService)
}
}
extension ViewController: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
centralManager.scanForPeripherals(withServices: [ViewController.SERVICE_UUID], options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
break
default: break
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
peripheral.discoverServices(nil)
peripherals.append(peripheral)
}
}
extension ViewController: CBPeripheralDelegate, CBPeripheralManagerDelegate {
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state == .poweredOn {
initService()
updateAdvertisingData()
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
for service in peripheral.services! {
peripheral.discoverCharacteristics(nil, for: service)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
for characteristic in service.characteristics! {
let characteristic = characteristic as CBCharacteristic
let message = "TestMessage".data(using: .utf8)
peripheral.writeValue(message!, for: characteristic, type: CBCharacteristicWriteType.withResponse)
}
}
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
for request in requests {
if let value = request.value {
let messageText = String(data: value, encoding: String.Encoding.utf8)
receiveLabel.text = messageText
}
self.peripheralManager.respond(to: request, withResult: .success)
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices(nil)
}
}
Even though you only searched for peripherals that are advertising your specific service, when you discover a peripheral, you call
peripheral.discoverServices(nil)
This will discover all services on the discovered peripheral (which is another iOS device), not just your specific service.
Then, for each service you discover all characteristics and for each characteristic you discover you write to it with
let message = "TestMessage".data(using: .utf8)
peripheral.writeValue(message!, for: characteristic, type: CBCharacteristicWriteType.withResponse)
Since you have discovered all characteristics for all services on the device and then attempt to write to each one, if any of those characteristics require encryption you will trigger the pairing dialog. When you cancel the pairing dialog, that particular write will fail, but your app will keep on working (which is why you see the connection in settings).
You should refine your code so that it only discovers the specific service you are interested in and only attempts to write to your characteristic. This will prevent the pairing dialog from being triggered as your characteristic does not require encryption.

iOS Bluetooth Low Energy (BLE) not discovering TX characteristic

I am using an an arduino feather BLE board and trying to create an iOS app that can send data to the board over BLE. I can connect but I can't get access to the txCharacteristic
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else {
return
}
for characteristic in characteristics {
if characteristic.properties.contains(.write) || characteristic.properties.contains(.writeWithoutResponse) {
writableCharacteristic = characteristic
}
// if(characteristic.uuid == CBUUID(string: "6e400002-b5a3-f393-e0a9-e50e24dcca9e"))
// {
// txCharacteristic = characteristic
// }
var txUUID = CBUUID(string: "6e400002-b5a3-f393-e0a9-e50e24dcca9e")
let temp = characteristic.uuid.uuidString;
switch characteristic.uuid {
case txUUID:
txCharacteristic = characteristic;
break;
default:
break;
}
peripheral.setNotifyValue(true, for: characteristic)
}
}
This code works, but only discovers the following UUIDs:
temp String "00001532-1212-EFDE-1523-785FEABCD123"
temp String "00001531-1212-EFDE-1523-785FEABCD123"
temp String "00001534-1212-EFDE-1523-785FEABCD123"
I have figured out that these UUID's are DFU UUIDs. How can I discover the txCharacteristic instead?
Added for more information how I am calling discoverCharacteristics():
extension SimpleBluetoothIO: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else {
return
}
targetService = services.first
if let service = services.first {
targetService = service
peripheral.discoverCharacteristics(nil, for: service)
}
}
I was able to figure this out, by reading info at this website:
https://learn.adafruit.com/getting-started-with-the-nrf8001-bluefruit-le-breakout/adding-app-support
Particularly this part (notice that UART is the service and TX and RX are characteristics of the service):
UART Service UUID: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
TX Characteristic UUID: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E
RX Characteristic UUID: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E
So I needed to call peripheral.discoverServices() with the UART Service ID. In my example below self.serviceUUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
//peripheral.discoverServices(nil)
//peripheral.discoverServices([CBUUID(string: "6e400001-b5a3-f393-e0a9-e50e24dcca9e")])
// Disover peripheral with service UUID that was given to us during initialization (self.serviceUUID)
peripheral.discoverServices([CBUUID(string: self.serviceUUID)])
}
Then in didDiscoverCharacteristicsFor service I needed to look for the TX Characteristic ID:
// Did Discover Characteristics
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else {
return
}
for characteristic in characteristics {
if characteristic.properties.contains(.write) || characteristic.properties.contains(.writeWithoutResponse) {
writableCharacteristic = characteristic
}
// TX Characteristic UUID: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E
// https://learn.adafruit.com/getting-started-with-the-nrf8001-bluefruit-le-breakout/adding-app-support
let txUUID = CBUUID(string: "6e400002-b5a3-f393-e0a9-e50e24dcca9e")
switch characteristic.uuid {
case txUUID:
txCharacteristic = characteristic;
break;
default:
break;
}
peripheral.setNotifyValue(true, for: characteristic)
}
}

iOS 11 Core Bluetooth restoration not working

I have an iOS app that uses Core Bluetooth to connect to a V.BTTN smart button. Everything worked perfectly fine in iOS 10, but since iOS 11 has come out the restoration process seems to break.
When I launch my app, with a previously paired button, the OS does in fact call the centralManager:willRestoreState, and the accompanying dictionary includes a pointer to a CBPeripheral object that has a status of connected. Just as it did back in iOS 10. However, the problem I am running into is when I call the discoverServices on the peripheral I am never returned any services in the peripheral:didDiscoverServices method. In fact that method, nor any other method, is called at all.
I have been searching all of the internet and have found people having similar issues with button pairing an connections, but those have generally been issues with improper lifecycle management of the peripheral object. I believe I have everything setup correct and at a loss. Does anyone have any idea what could be going on here?
import Foundation
import CoreBluetooth
class SmartButtonManager: NSObject {
//MARK: - Singleton
static let sharedManager = SmartButtonManager()
//MARK: - Properties
var discoveredDevices: [CBPeripheral] {
get {
return perfs
}
}
fileprivate(set) var isBluetoothOn = false
//MARK: - Private Constants & Variables
fileprivate var connectedButtonUUID: String?
fileprivate let queue = DispatchQueue(label: "V.BTTN", qos: .background, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
fileprivate var manager: CBCentralManager?
fileprivate var perfs = [CBPeripheral]()
fileprivate var connectedPerf: CBPeripheral?
fileprivate var isButtonReady = false
//MARK: - Initialization
override init() {
super.init()
}
//MARK: - Configure
func configure(withLaunchOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) {
// create new central manager
manager = CBCentralManager(delegate: self, queue: queue, options: [CBCentralManagerOptionRestoreIdentifierKey: managerIdentifier])
}
}
//MARK: - V.BTTN
extension SmartButtonManager: CBCentralManagerDelegate, CBPeripheralDelegate {
func scanForVbttn() {
perfs.removeAll()
manager?.scanForPeripherals(withServices: services, options: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOff:
print("[centralManagerDidUpdateState] CB BLE hardware is powered off")
perfs.removeAll()
isBluetoothOn = false
case .poweredOn:
print("[centralManagerDidUpdateState] CB BLE hardware is powered on. Start scanning for peripherals")
isBluetoothOn = true
case .unauthorized:
print("[centralManagerDidUpdateState] CB BLE hardware is not authorized")
case .unsupported:
print("[centralManagerDidUpdateState] CB BLE hardware is not supported")
isBluetoothOn = false
default:
print("[centralManagerDidUpdateState] CB BLE hardware state is unknown")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("[centralManager:didDiscover peripheral:advertisementData] CB BLE did discover peripheral with advertisement data: \(advertisementData)")
guard let perfName = advertisementData[CBAdvertisementDataLocalNameKey] as? String else {
print("[centralManager:didDiscover peripheral:advertisementData] peripheral name is unknown")
return
}
if perfName.contains("V.ALRT") {
peripheral.delegate = self
perfs.append(peripheral)
if connectedButtonUUID == perfName {
connect(peripheral: peripheral)
}
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("[central:didConnect peripheral:] CB BLE hardware did connect")
handleDidConnect(toPeripheral: peripheral)
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print("[central:didFailToConnect error:] CB BLE peripheral did failed to connect with error: \(String(describing: error))")
connectedPerf = nil
isButtonReady = false
connectedButtonUUID = nil
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("[central:didDisconnectPeripheral peripheral:] CB BLE peripheral did disconnect")
connectedPerf = nil
isButtonReady = false
connectedButtonUUID = nil
}
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
print("[central:willRestoreState dict:] CB BLE hardware will restore state")
print("\(dict)")
guard let ps = dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] else {
print("[central:willRestoreState dict:] No perfs to restore")
return
}
print("[central:willRestoreState dict:] Will restore perfs")
perfs = ps
print("[central:willRestoreState dict:] Attempt to reconnect to V.BTTN")
print("[central:willRestoreState dict:] perfs \(perfs)")
for p in perfs {
if p.name == connectedButtonUUID {
print("[central:willRestoreState dict:] Connect to perf \(p)")
handleDidConnect(toPeripheral: p)
break
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else {
print("[peripheral:didDiscoverServices error:] CB BLE peripheral unable to discover services")
return
}
print("[peripheral:didDiscoverServices error:] BLE peripheral did discover services")
for s in services {
print("[peripheral:didDiscoverServices error:] CB BLE Service \(s.description)")
peripheral.discoverCharacteristics(nil, for: s)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
print("[peripheral:didDiscoverCharacteristicsFor service:] CB BLE did discover characteristics for service \(service.uuid.description)")
if compareCBUUID(uuid1: service.uuid, uuid2: CBUUID(string: BLE_VSN_GATT_SERVICE_UUID)) {
guard let characteristics = service.characteristics else {
return
}
for aChar in characteristics {
// write the verification key
if aChar.uuid.isEqual(CBUUID(string: BLE_VERIFICATION_SERVICE_UUID)) {
self.writeVerificationKey(forPeripheral: peripheral, characteristic: aChar)
self.enable(peripheral: peripheral)
break
}
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
//NOTE: This code has been omitted as there is little need to see this
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
print("[peripheral:didWriteValueFor characteristic:] characteristic: \(characteristic.uuid)")
if let e = error {
print("[peripheral:didWriteValueFor characteristic:] error: \(e)")
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
if !characteristic.isNotifying {
print("[peripheral:didUpdateNotificationStateFor:][Characteristic is not notifiying so cancel the connection]")
manager?.cancelPeripheralConnection(peripheral)
}
}
//MARK: - Helpers
fileprivate func writeVerificationKey(forPeripheral peripheral: CBPeripheral, characteristic: CBCharacteristic) {
print("[peripheral:didDiscoverCharacteristicsFor service:] Write verification key")
let data = NSData(bytes: [0x80,0xBE,0xF5,0xAC,0xFF] as [UInt8], length: 5)
peripheral.writeValue(data as Data, for: characteristic, type: CBCharacteristicWriteType.withResponse)
}
fileprivate func handleDidConnect(toPeripheral peripheral: CBPeripheral) {
if let n = peripheral.name {
connectedButtonUUID = n
}
connectedPerf = peripheral
peripheral.delegate = self
peripheral.discoverServices(nil)
isButtonReady = true
}
fileprivate func enable(peripheral: CBPeripheral) {
guard let services = peripheral.services else {
return
}
for service: CBService in services {
if compareCBUUID(uuid1: service.uuid, uuid2: CBUUID(string: BLE_VSN_GATT_SERVICE_UUID)) {
guard let characteristics = service.characteristics else {
return
}
for aChar in characteristics {
if aChar.uuid.isEqual(CBUUID(string: BLE_KEYPRESS_DETECTION_UUID)) {
// enable button
print("[peripheral:didDiscoverCharacteristicsFor service:] Enable short press")
let data = NSData(bytes: [0x02] as [UInt8], length: 1)
peripheral.writeValue(data as Data, for: aChar, type: CBCharacteristicWriteType.withResponse);
} else if aChar.uuid.isEqual(CBUUID(string: BLE_SILENT_NORMAL_MODE)) {
// enable button
print("[peripheral:didDiscoverCharacteristicsFor service:] Enable normal mode")
let data = NSData(bytes: [0x00] as [UInt8], length: 1)
peripheral.writeValue(data as Data, for: aChar, type: CBCharacteristicWriteType.withResponse);
} else if aChar.uuid.isEqual(CBUUID(string: BLE_FALL_KEYPRESS_DETECTION_UUID)) {
// enable fall detection
print("[peripheral:didDiscoverCharacteristicsFor service:] Enable fall detection")
peripheral.setNotifyValue(true, for: aChar)
}
}
}
}
checkBatteryLevel()
}
func disconnectVbttn() {
guard let peripheral = connectedPerf else {
return
}
setVbttnToStealthMode()
manager?.cancelPeripheralConnection(peripheral)
isButtonReady = false
}
fileprivate func compareCBUUID(uuid1: CBUUID, uuid2: CBUUID) -> Bool {
if (uuid1.data as NSData).isEqual(to: uuid2.data) {
return true
}
return false
}
func stopScanning() {
manager?.stopScan()
}
}

iOS BLE app - I can get notifications from a characteristic, but can't read it

I've been working on an iOS app that interfaces with a BLE device. The device has 1 service with 5 characteristics. Three of them are read/notify, while the other two are read/write/notify.
When I first launch the app, I need to read the 5 characteristics, then listen for future notifications. For some reason, the read is not working (no response to readValueForCharacteristic), but the notifications do work (changes on the device show up in the app).
Also unusual - I never receive calls to didUpdateNotificationStateForCharacteristic, despite the fact that the notifications do indeed work.
I also am unable to write to the 2 writable characteristics, though that isn't my primary concern right now. I've verified none of these problems are on the device end, because I can perform them using the LightBlue.app
Here's my code:
import UIKit
import CoreBluetooth
class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
// GUI elements
#IBOutlet var statusLabel: UILabel!
#IBOutlet var char1Field: UITextField!
#IBOutlet var char2Field: UITextField!
#IBOutlet var char3Field: UITextField!
#IBOutlet var char4Selector: UISegmentedControl!
#IBOutlet var char5Field: UITextField!
// BLE
var centralManager : CBCentralManager!
var myPeriph : CBPeripheral!
var char3 : CBCharacteristic!
var char4 : CBCharacteristic!
var value1 : UInt32 = 0
var value2 : UInt32 = 0
var value3 : Float32 = 0
var value4 : UInt8 = 0
var value5 : UInt8 = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Initialize central manager on load
centralManager = CBCentralManager(delegate: self, queue: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func char3FieldChanged(sender: AnyObject) {
if char3Field.text == "" {
value3 = 0
} else {
value3 = Float32(char3Field.text!)!
}
let wtBytes = NSData(bytes: &value3, length: sizeof(UInt8))
char3Field.text = String(format: "%0.1f", arguments: [value3])
if char3 != nil {
self.myPeriph.writeValue(wtBytes, forCharacteristic: char3, type: CBCharacteristicWriteType.WithoutResponse)
}
}
#IBAction func char4SelectorChanged(sender: AnyObject) {
value4 = UInt8(char4Selector.selectedSegmentIndex)
let char4Bytes = NSData(bytes: &value4, length: sizeof(UInt8))
if char4 != nil {
self.myPeriph.writeValue(char4Bytes, forCharacteristic: char4, type: CBCharacteristicWriteType.WithoutResponse)
}
}
/******* CBCentralManagerDelegate *******/
// Check status of BLE hardware
func centralManagerDidUpdateState(central: CBCentralManager) {
if central.state == CBCentralManagerState.PoweredOn {
// Scan for peripherals if BLE is turned on
central.scanForPeripheralsWithServices(nil, options: nil)
self.statusLabel.text = "Searching for BLE Devices"
}
else {
// Can have different conditions for all states if needed - show generic alert for now
showAlertWithText("Error", message: "Bluetooth switched off or not initialized")
}
}
// Check out the discovered peripherals to find Sensor Tag
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
if MyPeriph.NameFound(advertisementData) == true {
// Update Status Label
self.statusLabel.text = "Device Found"
// Stop scanning, set as the peripheral to use and establish connection
self.centralManager.stopScan()
self.myPeriph = peripheral
self.myPeriph.delegate = self
self.centralManager.connectPeripheral(peripheral, options: nil)
}
else {
self.statusLabel.text = "Device NOT Found"
}
}
// Discover services of the peripheral
func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
self.statusLabel.text = "Discovering peripheral services"
peripheral.discoverServices(nil)
print("Searching for peripheral")
}
// If disconnected, start searching again
func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) {
self.statusLabel.text = "Disconnected"
central.scanForPeripheralsWithServices(nil, options: nil)
print("Disconnected")
}
/******* CBCentralPeripheralDelegate *******/
// Check if the service discovered is valid
func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) {
self.statusLabel.text = "Looking at peripheral services"
for service in peripheral.services! {
let thisService = service as CBService
if MyPeriph.validService(thisService) {
// Discover characteristics of all valid services
peripheral.discoverCharacteristics(nil, forService: thisService)
print("Peripheral service found, searching for characteristics")
}
}
}
func peripheral(peripheral: CBPeripheral, didUpdateNotificationStateForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
print("Did update notify status for \(characteristic.description)")
}
func peripheral(peripheral: CBPeripheral, didUpdateValueForDescriptor descriptor: CBDescriptor, error: NSError?) {
print("Did update value for descriptor")
}
// Enable notification and sensor for each characteristic of valid service
func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) {
self.statusLabel.text = "Configuring..."
print("Found \(service.characteristics?.count) characteristics")
for characteristic in service.characteristics! {
let thisCharacteristic = characteristic as CBCharacteristic
self.myPeriph.readValueForCharacteristic(thisCharacteristic)
self.myPeriph.discoverDescriptorsForCharacteristic(thisCharacteristic)
switch characteristic.UUID {
case Char1Uuid:
self.myPeriph.setNotifyValue(true, forCharacteristic: thisCharacteristic)
case Char2Uuid:
self.myPeriph.setNotifyValue(true, forCharacteristic: thisCharacteristic)
case Char3Uuid:
self.myPeriph.setNotifyValue(true, forCharacteristic: thisCharacteristic)
char3 = characteristic
case Char4Uuid:
self.myPeriph.setNotifyValue(true, forCharacteristic: thisCharacteristic)
char4 = characteristic
case Char5Uuid:
self.myPeriph.setNotifyValue(true, forCharacteristic: thisCharacteristic)
default: break
}
}
}
// Get data values when they are updated
func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
self.statusLabel.text = "Connected"
switch characteristic.UUID {
case Char1Uuid:
value1 = MyPeriph.getvalue1(characteristic.value!)
char1Field.text = String(value1)
case Char2Uuid:
value2 = MyPeriph.getvalue2(characteristic.value!)
char2Field.text = String(value2)
case Char3Uuid:
value3 = MyPeriph.getvalue3(characteristic.value!)
char3Field.text = String(value3)
char3 = characteristic
case Char4Uuid:
value4 = MyPeriph.getvalue4(characteristic.value!)
if value4 == 0 {
char4Selector.selectedSegmentIndex = 0
} else {
char4Selector.selectedSegmentIndex = 1
}
char4 = characteristic
case Char5Uuid:
value5 = MyPeriph.getvalue5(characteristic.value!)
char5Field.text = String(value5)
default: break
}
}
/******* Helper *******/
// Show alert
func showAlertWithText (header : String = "Warning", message : String) {
let alert = UIAlertController(title: header, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))
alert.view.tintColor = UIColor.redColor()
self.presentViewController(alert, animated: true, completion: nil)
}
}

Resources