I've been reading the tutorial to use the nRF8001 from Adafruit and connecting it to an iOS device via Arduino. So far I've set up everything correctly and it works fine in their app.
I'm trying to write my own app that (for now) does the exact same thing, I read their tutorial and from what I understand I copied the code as close as I could, so far I can get the connection and most things seem to be working (UI-wise), however I can't seem to do anything past connecting to the device:
Here's my code for AFTER connecting:
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
//What to do when it discovers a peripheral, add it to the array list
print("Peripheral found: " + (peripheral.name ?? "Unknown Name"))
peripheralsFoundNames.append((peripheral.name ?? "Unknown Name"))
peripheralsFoundData.append((advertisementData.description ))
peripheralsFoundCB.append(peripheral)
peripheralsFoundRSSIs.append(RSSI)
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Connected to device!")
displayStatusAlert(localmsg: "Connection Succesful!")
NotificationCenter.default.post(name: Notification.Name(rawValue: DEVICE_READY_KEY), object: self)
data?.length = 0 //clear any data that might be stored
peripheral.discoverServices([BLETemperatureService])
print("Here at didConnect, connected to:" + peripheral.name!)
// Here needs to add code to check if it's a single or multi-channel device via the advertisement data or some other constant, maybe the name?
}
As you can see, I am calling explicitly peripheral.discoverServices, and then i have a print statement that executes. Then I have the following (NOTE NONE OF THE BELOW LINES SEEM TO EXECUTE AT ANY TIME (AT LEAST NOT THE PRINT STATEMENTS):
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
print("here at diddisoverservices")
if ((error) != nil){
displayStatusAlert(localmsg: "Error: \n" + (error?.localizedDescription ?? "Error Unknown" ))
}
guard let services = peripheral.services
else{
return
}
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
}
print ("Discovered!")
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if ((error) != nil){
displayStatusAlert(localmsg: "Error: \n" + (error?.localizedDescription ?? "Error Unknown" ))
}
guard let characteristics = service.characteristics
else{
return
}
for characteristic in characteristics {
//looks for the right characteristic
print("looking for characteristic")
if characteristic.uuid.isEqual(BLERXCharacteristic) {
deviceConnectedRXChar = characteristic
//Once found, subscribe to the this particular characteristic
peripheral.setNotifyValue(true, for: deviceConnectedRXChar!)
peripheral.readValue(for: characteristic)
print("Rx Characteristic: \(characteristic.uuid)")
}
if characteristic.uuid.isEqual(BLETXCharacteristic){
deviceConnectedTXChar = characteristic
print("Tx Characteristic: \(characteristic.uuid)")
}
peripheral.discoverDescriptors(for: characteristic)
}
print ("Characteristic discovered")
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic == deviceConnectedRXChar {
if let ASCIIstring = NSString(data: characteristic.value!, encoding: String.Encoding.utf8.rawValue) {
receivedDataString = ASCIIstring
print("Value Recieved: \((receivedDataString as String))")
NotificationCenter.default.post(name: Notification.Name(rawValue: DEVICE_SENT_DATA), object: nil)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) is a CBPeripheralDelegate method.
So what you were missing is setting the CBPeripheral object delegate.
So just before doing peripheral.discoverServices([BLETemperatureService]), you need to do peripheral.delegate = self.
Related
We have a peripheral device and I am trying to connect it with iOS device. It was working fine with Swift 3.0 But when I am trying to convert it in Swift 5.0 it did not work. It is working perfectly fine with Android device. So it is not an issue of hardware.
I have successfully connected and also discovered services and characteristics. My characteristics give me notify property only, I set that to true which also triggered the didupdateNotificationStateFor delegate method and it showed the characteristic.isnotifying as true. Which meant my subscription is successful. The only problem I am facing is didUpdateValueForCharacteristic is never been called. My code is as under.
extension HRMViewController: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
print(service)
peripheral.discoverCharacteristics(nil, for: service)
// break
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
print(characteristic)
if characteristic.properties.contains(.read) {
print("\(characteristic.uuid): properties contains .read")
peripheral.readValue(for: characteristic)
}
if transferCharacteristic.properties.contains(.notify) {
print("\(characteristic.uuid): properties contains .notify")
peripheral.setNotifyValue(true, for: characteristic)
}
if characteristic.properties.contains(.write){
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
switch characteristic.uuid {
case aCBUUID:
lblResult.text = "UUID: \(characteristic.uuid) Value = \(String(describing: characteristic.value))"
case bCBUUID:
lblResult2.text = "UUID: \(characteristic.uuid) Value = \(String(describing: characteristic.value))"
case f:
print("CORRECT Characteristic UUID: \(characteristic.uuid)")
print(characteristic.value ?? "no value")
default:
print("Unhandled Characteristic UUID: \(characteristic.uuid)")
print(characteristic.value ?? "no value")
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
if error != nil {
print("Error ==",error?.localizedDescription ?? "")
}
print("updateNotification: \(characteristic.isNotifying)")
}
Any help in this regard is highly appreciated.
I have a device which I connect to using BLE. I start by checking if BLE is powered on, if so, I start scanning. Then after the device is discovered, I store the peripheral in a variable, then I try to connect to it. The connection succeeds. After that, I starting discovering the services and characteristics, and I manage to read them successfully. The problem is whenever I try to write data to the device I always get this error.
Domain=CBErrorDomain Code=7 "The specified device has disconnected from us." UserInfo={NSLocalizedDescription=The specified device has disconnected from us.
Here is my code:
public func centralManagerDidUpdateState(_ central: CBCentralManager)
{
switch central.state {
case .unauthorized:
print("App does not support BLE")
case .poweredOff:
print("Bluetooth is turned off")
case .poweredOn:
print("Bluetooth is turned on")
print("Start scanning peripherals...")
central.scanForPeripherals(withServices: nil, options: nil)
default:
break
}
}
// To receive cell broadcast data
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
let device = Device();
if(device.fromScanData(peripheral: peripheral, name: peripheral.name, rssi: RSSI.intValue, advertisementData: advertisementData))
{
if(device.Name.isEmpty || device.Name.count == 0){
return;
}
if(device.SN.isEmpty || device.SN.count != 8){
return;
}
if self.device.SN == device.SN
{
if !isDeviceConnected
{
print("Stop scanning")
print("Connecting...")
self.peripheral = peripheral
self.peripheral?.delegate = self
bluetoothManager.stopScan()
bluetoothManager.connect(self.peripheral!, options: nil)
}
}
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Connected successfully");
print("Trying to get equipment services and features...");
device.Peripheral = peripheral
self.peripheral?.discoverServices(nil)
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
print("Failed to connect");
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("Connection has been disconnected");
isDeviceConnected = false;
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?){
guard let services = peripheral.services else {
if error != nil
{
print("Did Discover Services Error:\(String(describing: error!))")
}
else
{
print("Empty services")
}
return
}
print("Device Services......");
print("-------------- ("+peripheral.identifier.uuidString+")Service Services information: --------------------");
print("Number of services:\(services.count)")
for service in services
{
print("---- Service UUID:\(service.uuid) -----");
self.peripheral?.discoverCharacteristics(nil, for: service);
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?){
guard let characteristics = service.characteristics else {
if error != nil
{
print("Did Discover Characteristics Error:\(String(describing: error!))")
}
else
{
print("Empty Characteristics")
}
return
}
print("Feature Characteristic UUID List:");
for characteristic in characteristics
{
print("UUID: \(characteristic.uuid)");
self.peripheral?.readValue(for: characteristic);
}
}
func checkToken() {
print("Start Writing")
device.IsSaveOverwrite = true
let characteristic = characteristicList[characteristicHandle.GetCharacteristicUUID(type: .IsSaveOverwrite)]
let data = characteristicHandle.GetValue(device: device, type: .IsSaveOverwrite)
peripheral?.writeValue(data, for: characteristic!, type: CBCharacteristicWriteType.withResponse);
}
//Read the property successfully callback
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if(error != nil)
{
print("Did Update Characteristic with UUID \(characteristic.uuid) Error:\(String(describing: error!))")
return;
}
readConfigs.ConfigRespond(uuid: characteristic.uuid.uuidString);
device = characteristicHandle.SetDevice(device: device, type: characteristicHandle.GetCharacteristicType(uuid: characteristic.uuid), bytes: characteristic.value!);
// Check if all characteristics have been read, if so, start writing
if readConfigs.IsComplete() && readConfiguration == false
{
readConfiguration = true
hideActivityIndicator()
print("Read all characeteristics")
checkToken()
}
}
// Never gets called
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
print("Write Config")
}
Could someone please point out what am missing?
Using CoreBluetooth on iOS(10.3) i'm unable to read a characteristics value when bonded(paired?) to a custom BLE HID Device. If I remain unbonded I can read the characteristic fine.
The BLE device implements HID over GATT(HOGP) and an additional service with a single notify characteristic. This additional service/characteristic is what I'm trying to read, not the HID over GATT data which I know is filtered out.
It appears that didDiscoverServices never gets called. I do see didConnect being called.
I'm able to accomplish this on Android so I don't think it's an issue with the BLE device.
Relevant code below:
func centralManagerDidUpdateState(_ central: CBCentralManager) {
var showAlert = true
var message = ""
switch central.state {
case .poweredOff:
message = NSLocalizedString("Bluetooth on this device is currently powered off.", comment: "")
case .unsupported:
message = NSLocalizedString("This device does not support Bluetooth Low Energy.", comment: "")
case .unauthorized:
message = NSLocalizedString("This app is not authorized to use Bluetooth Low Energy.", comment: "")
case .resetting:
message = NSLocalizedString("The BLE Manager is resetting; a state update is pending.", comment: "")
case .unknown:
message = NSLocalizedString("The state of the BLE Manager is unknown.", comment: "")
case .poweredOn:
showAlert = false
message = NSLocalizedString("Bluetooth LE is turned on and ready for communication.", comment: "")
let lastPeripherals = centralManager.retrieveConnectedPeripherals(withServices: [CBUUID(string: Device.DeviceServiceUUID)])
print("count: \(lastPeripherals.count)")
if lastPeripherals.count > 0{
let device = lastPeripherals.last!;
connectingPeripheral = device;
centralManager.connect(connectingPeripheral, options: nil)
} else {
centralManager.scanForPeripherals(withServices: [CBUUID(string: "7340")], options: nil)
}
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if let peripheralName = advertisementData[CBAdvertisementDataLocalNameKey] as? String {
if peripheralName == deviceName {
// save a reference to the device
device= peripheral
device!.delegate = self
// Request a connection to the peripheral
centralManager.connect(device!, options: nil)
}
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.discoverServices(nil)
}
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
if error != nil {
print("****** DISCONNECTION DETAILS: \(error!.localizedDescription)")
}
device = nil
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if error != nil {
print("ERROR DISCOVERING SERVICES: \(error?.localizedDescription)")
return
}
if let services = peripheral.services {
for service in services {
print("DISCOVERED SERVICE: \(service)")
if (service.uuid == CBUUID(string: Device.DeviceServiceUUID)) {
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if error != nil {
print("ERROR DISCOVERING CHARACTERISTICS: \(error?.localizedDescription)")
return
}
if let characteristics = service.characteristics {
for characteristic in characteristics {
// Message Data Characteristic
if characteristic.uuid == CBUUID(string: Device.MessageCharacteristicUUID) {
// Enable the message notifications
messageCharacteristic = characteristic
device?.setNotifyValue(true, for: characteristic)
}
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if error != nil {
print("ERROR ON UPDATING VALUE FOR CHARACTERISTIC: \(characteristic) - \(error?.localizedDescription)")
return
}
// extract the data from the characteristic's value property and display the value based on the characteristic type
if let dataBytes = characteristic.value {
if characteristic.uuid == CBUUID(string: Device.MessageCharacteristicUUID) {
displayMessage(dataBytes)
}
}
}
I found out that when an iPhone is updating a local characteristic value, and also listen to notification for that characteristic, he will get notified even the fact that he was the one that updated the value, so the delegate :
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
Will be called, even that I was the one who change the value, and not the remote server (ble device). When the remote side send data, I will also get this delegate. Is it the way it should be ? I don't remember so .
I found out the same behavior on other 3rd apps that scan for Bluetooth LE.
I also found out that for some reason my code is not always get the delegate , maybe I am doing things wrong here:
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
let foundName=peripheral.name
let deviceName = "Name"
if foundName?.range(of: deviceName) != nil
{
self.centralManager.stopScan()
self.peripheral = peripheral
self.peripheral.delegate = self
self.centralManager.connect(peripheral, options: nil)
NotificationCenter.default.post(name: Notification.Name(rawValue: "Bluetooth"), object: "Detected")
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.discoverServices( nil)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
for service in peripheral.services!
{
let thisService = service as CBService
print(thisService.uuid.uuidString)
if thisService.uuid.uuidString == serviceUUID {
peripheral.discoverCharacteristics(nil, for: thisService)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
for charateristic in service.characteristics!
{
let thisCharacteristic = charateristic as CBCharacteristic
// check for data characteristic
if thisCharacteristic.uuid.uuidString == characteristicUUID {
print("REGISTERED CHARACTERISTIC:",thisCharacteristic)
self.peripheral.setNotifyValue(true, for: thisCharacteristic)
self.characteristic=thisCharacteristic
isConnected=true
NotificationCenter.default.post(name: Notification.Name(rawValue: "Bluetooth"), object: "Connected")
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.uuid.uuidString == characteristicUUID {
if let str = NSString(data: characteristic.value!, encoding: String.Encoding.utf8.rawValue)
{
print("BLE:DATAIN:",str )
NotificationCenter.default.post(name: Notification.Name(rawValue: "Bluetooth"), object: str)
}
}
}
func sendData(data:String)
{
if(peripheral != nil)
{
print("BLE:SENT")
var bytesData = [UInt8](data.utf8)
let writeData = NSData (bytes: &bytesData, length: bytesData.count)
peripheral.writeValue(writeData as Data, for: characteristic, type: CBCharacteristicWriteType.withoutResponse)
}
}
After a day investigation, both the hardware chip and the iOS, I realized that the notify update- will notify you when there is a new value.
So, whats a new value ?
Its when the previous value is different from the current value.
For some hardware configurations, the previous value is kept on cache even after reset, and iOS will see it as a new value, even though the hardware was not updated it at all (!)
So when you register to notifications, iOS will check the initial value , and if it holds something other then zero/nil , a delegate will be called.
Its your job to clean the previous cached value in the chip in some way.
In general, I found out (maybe too late) that the best practice is to connect and stay connected as long as the app is running. This will eliminate all sorts of problems that happens when you connect/disconnect every time you need to send data.
Conclusion: connect once, when disconnected for any reason -reset your hardware automatically (using the host controller software)
I need to connect with a BLE device and then handle data as per sent via different button in it.
For that I wrote following code.
import CoreBluetooth
class HomeViewController: UIViewController,CBPeripheralDelegate,CBCentralManagerDelegate
{
var centralManager : CBCentralManager!
var peri : CBPeripheral!
override func viewDidLoad()
{
super.viewDidLoad()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(central: CBCentralManager) {
if central.state == .Unknown
{
print("Unknown")
}
else if central.state == .Unsupported
{
print("Unsupported")
}
else if central.state == .Unauthorized
{
print("Unauthorized")
}
else if central.state == .Resetting
{
print("Resetting")
}
else if central.state == .PoweredOn
{
print("Powered On")
startScan()
}
else if central.state == .PoweredOff
{
print("Powered Off")
}
}
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
print("Discovered: \(peripheral.name) at \(RSSI)")
print("AdvertisementData:\(advertisementData)")
if peri != peripheral
{
peri = peripheral
centralManager.connectPeripheral(peripheral, options: nil)
}
}
func centralManager(central: CBCentralManager, didFailToConnectPeripheral peripheral: CBPeripheral, error: NSError?) {
print("Failed to connect \(peripheral) cause of \(error)")
}
func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
print("connected to \(peripheral)")
// centralManager.stopScan()
print("Available services:\(peripheral.services)")
}
func peripheral(peripheral: CBPeripheral, didDiscoverIncludedServicesForService service: CBService, error: NSError?) {
print("Services\(service) and error\(error)")
}
func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) {
print("Services and error\(error)")
}
func startScan(){
print("Scanning...")
centralManager.scanForPeripheralsWithServices(nil, options: nil)
}
}
And here is my Log for this code.
Powered On
Scanning...
Discovered: Optional("**** BLE") at 127
AdvertisementData:["kCBAdvDataIsConnectable": 1, "kCBAdvDataServiceUUIDs": (
1802
)]
connected to <CBPeripheral: 0x12756d910, identifier = 6197****-EB0A-F1E8-BEF4-1AFAC629C5BC, name = **** BLE, state = connected>
Available services:nil
This is output is generated when one button is clicked from BLE device. But I am unable to receive or read data when another button is clicked.
Android developer of same app has integrated with both button.
So there is no any problem in device.
Can anyone help me to guide where I'm going wrong in this code??
Pandafox's answer is perfect just one thing is missing from it.
Which is setting delegate of peripheral.
Here is the complete code to discover peripheral, connect to it and discover its services and characteristics.
1.Connect peripheral
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
print("Discovered: \(peripheral.name) at \(RSSI)")
print("AdvertisementData:\(advertisementData)")
if peri != peripheral
{
peri = peripheral
peri.delegate = self
centralManager.connectPeripheral(peri, options: nil)
}
}
Connection failure or success
func centralManager(central: CBCentralManager, didFailToConnectPeripheral peripheral: CBPeripheral, error: NSError?) {
print("Failed to connect \(peripheral) cause of \(error)")
}
func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
print("connected to \(peripheral)")
// centralManager.stopScan()
peripheral.discoverServices(nil)
}
3.DiscoverServices
func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) {
print("Services:\(peripheral.services) and error\(error)")
if let services = peripheral.services {
for service in services {
peripheral.discoverCharacteristics(nil, forService: service)
}
}
}
Discover Characteristics and set notification
func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?)
{
print("peripheral:\(peripheral) and service:\(service)")
for characteristic in service.characteristics!
{
peripheral.setNotifyValue(true, forCharacteristic: characteristic)
}
}
Handle notification for update value of characteristics
func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?)
{
print("characteristic changed:\(characteristic)")
}
You also have to discover the services and characteristics after connecting to the device.
For example, in your "didConnectPeripheral" method, you will have to do something like:
func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
print("connected to \(peripheral)")
peripheral.delegate = self
peripheral.discoverServices(nil)
print("Discovering services!")
}
And then:
func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) {
print("Discovered services: \(peripheral.services), Error\(error)")
if let services = peripheral.services {
for service in services {
peripheral.discoverCharacteristics(nil, forService: service)
}
}
}
And then you have to handle each characteristic:
func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError)
You must remember to store each characteristic manually, as they will be deallocated if you don't.
In order to receive streaming data (notifications) you will have to enable notify for each characteristic.
peripheral.setNotifyValue(true, forCharacteristic: characteristic)
You also have to implement:
func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError)
Order to handle the incoming values.
As you can see, there's quite a bit of boiler plate code required to get started.
After connecting to the peripheral you have to call discoverServices on the peripheral with the UUID of the services you want to discover, you then have to discover the characteristics of the service. If you want updates when a button is clicked, you will have to turn notifications on for the characteristic corresponding to that button
I would highly recommend this link from apple for follow up reading if you still need help. It describes what you need to do step by step in a better fashion than I could ever describe here.
Apple BLE Guide