I am attempting to create a simple Bluetooth LE peripheral to connect to a pre-existing Central. From what I can tell, my Peripheral Manager is set up correctly and the call to begin advertising is in place with the correct service UUID. However, when I run a check to make sure my peripheral is actually advertising, I get a negative result
I have double checked my UUID and ensured that Bluetooth on my test device is on. I am also using a physical device for testing, not the simulator.
Here is the code used to set up my peripheral advertisement:
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state == CBManagerState.poweredOn {
service = CBMutableService.init(type: SERVICE_UUID, primary: true)
characteristic = CBMutableCharacteristic.init(type: CHARACTERISTIC_UUID, properties: .read, value: nil, permissions: .readable)
service?.characteristics = [characteristic!]
peripheralManager?.add(service!)
peripheralManager?.delegate = self
let adData = [CBAdvertisementDataLocalNameKey : "MackJohn_Bluetooth_On_iOS", CBAdvertisementDataServiceUUIDsKey : SERVICE_UUID] as [String : Any]
peripheralManager?.startAdvertising(adData)
}
}
Here is where I'm checking to see if my code is actually advertising and getting a false result:
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
print(peripheral.isAdvertising)
}
I've also noticed that this function is not calling at all:
func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) {
print("Added Service")
}
You have made one slight error; the value for CBAdvertisementDataServiceUUIDsKey should be an array of CBUUIDs, not a single CBUUID;
let adData = [CBAdvertisementDataLocalNameKey : "MackJohn_Bluetooth_On_iOS", CBAdvertisementDataServiceUUIDsKey : [SERVICE_UUID]] as [String : Any]
Related
I am using an iPhone as my central. Is it possible to use CoreBluetooth to connect to a Google Home, Amazon Echo, or really any non Bluetooth LE devices as a peripheral in order to read said peripheral's RSSI? I do not need to connect to access any services; I'm doing a research project, and simply need to be able to use an iPhone as a sensor for RSSI values, so to speak. Right now I have this code, which scans for and discovers peripherals that are advertising indiscriminately.
//
// BLEManager.swift
// RSSIappSwiftUI
//
// Created by bee-mcc on 3/30/21.
//
import Foundation
import CoreBluetooth
//will hold the information needed about Our Peripherals
struct Peripheral: Identifiable {
let id: Int
let name: String
let rssi: Int
}
class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate{
//the object that represents the Bluetooth controls of the iphone
var myCentral: CBCentralManager!
#Published var peripherals = [Peripheral]()
//var letting us know if myCentral has bluetooth switched on
#Published var isSwitchedOn = false
override init() {
super.init()
myCentral = CBCentralManager(delegate: self, queue: nil)
myCentral.delegate = self
}
//this is called whenever the central manager updates its state
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
isSwitchedOn = true
}
else {
isSwitchedOn = false
}
}
//this is called any time a central manager discovers any peripherals
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
var peripheralName: String!
if let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String {
peripheralName = name
}
else {
peripheralName = "Unknown"
}
let newPeripheral = Peripheral(id: peripherals.count, name: peripheralName, rssi: RSSI.intValue)
print(newPeripheral)
peripherals.append(newPeripheral)
}
//buttons to start and stop scanning
func startScanning() {
print("startScanning")
myCentral.scanForPeripherals(withServices: nil, options: nil)
}
func stopScanning() {
print("stopScanning")
myCentral.stopScan()
}
}
I would like to somehow be able to connect to a bluetooth device by specifying it's MAC address in the code somewhere, and then to collect the device with that MAC address's RSSI with regards to the iPhone.
Thanks in advance!
Im new to programming in swift, & I need help with working with bluetooth.
Im working on a project that involves sending a string to a computer via bluetooth, and Im able to enter the receiving device's MAC address beforehand so it has that to know where to send it.
My only problem at this stage is connecting to said device, & sending the data. I tried looking up tutorials, but they were either for Android (Which I already got working, I need one for iOS now), or they were about how to connect via service UUID (what?).
Heres the code I have so far:
import UIKit
import CoreBluetooth
class transmitter: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
var manager:CBCentralManager!
var peripheral:CBPeripheral!
let SCRATCH_UUID = UUID.init(uuidString: "00001101-0000-1000-8000-00805F9B34FB")
let SERVICE_UUID = CBUUID(string: "00001101-0000-1000-8000-00805F9B34FB")
override func viewDidLoad() {
super.viewDidLoad()
// Define manager
manager = CBCentralManager(delegate: self, queue: nil)
print(globals.data)
// Do any additional setup after loading the view.
}
#IBOutlet weak var console: UILabel!
// Check if teh bluetooth is enabled
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == CBManagerState.poweredOn {
central.scanForPeripherals(withServices:nil, options: nil)
print (central.isScanning)
console.text = String(describing: central.retrievePeripherals(withIdentifiers: [SCRATCH_UUID!]))
} else {
//print("Bluetooth not available.")
let alert = UIAlertController(title: "Bluetooth unavalible", message: "Bluetooth is unavalibe for this device. Is it even turned on?", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
// Pair with device....
// TODO: Change to be based on MAC Address instead of name...?
private func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
let device = (advertisementData as NSDictionary).object(forKey: CBAdvertisementDataLocalNameKey) as? NSString
// console.text = peripheral.name
/*
if device?.contains(globals.macAddress) == true {
self.manager.stopScan()
self.peripheral = peripheral
self.peripheral.delegate = self
manager.connect(peripheral, options: nil)
}
*/
}
//
// The rest is copied from a tutorial
//
// Once you are connected to a device, you can get a list of services on that device.
func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
peripheral.discoverServices(nil)
}
// Once you get a list of the services offered by the device, you will want to get a list of the characteristics. You can get crazy here, or limit listing of characteristics to just a specific service. If you go crazy watch for threading issues.
private func peripheral(peripheral: CBPeripheral,didDiscoverServices error: NSError?) {
for service in peripheral.services! {
let thisService = service as CBService
if service.uuid == SERVICE_UUID {
peripheral.discoverCharacteristics(nil, for: thisService)
}
}
}
// There are different ways to approach getting data from the BLE device. One approach would be to read changes incrementally. Another approach, the approach I used in my application, would be to have the BLE device notify you whenever a characteristic value has changed.
private func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) {
for characteristic in service.characteristics! {
let thisCharacteristic = characteristic as CBCharacteristic
if thisCharacteristic.uuid == SERVICE_UUID {
self.peripheral.setNotifyValue(true, for: thisCharacteristic)
}
}
}
// This is an optional step, but hey, let us be good programmers and clean up after ourselves. Also a good place to start scanning all over again.
private func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) {
central.scanForPeripherals(withServices: nil, options: nil)
}
}
What I was trying to do at this stage was:
Check to make sure bluetooth is enabled (Works)
List devices available, as I was not able to connect via MAC address (Fails)
What would be nice though is that if I didn't have to do step 2, & just connect via the provided MAC address instead of scanning for devices, which didn't show any.
Help?
Ok, so it seems that SPP is not supported. Crap :/
Ill look into what both John Doe & Paulw11 suggested
Thanks!
I am writing an app that allows me to send an image through BLE to the app of someone else.
In order to test it out properly, I would need to be able to connect to an device, but my scanned peripherals are always disconnected and have nil as value. I am uncertain as to whether I am doing this right. I have been reading through guides and I am following the right procedure so could someone point out to me what I might be doing wrong? What are the peripherals I'm detecting?
output:
<CBPeripheral: 0x165b9c90, identifier = 6B74A074-6F5B-0E3A-94EB-6E7BB890569C, name = (null), state = disconnected>
<CBPeripheral: 0x165a0940, identifier = A35AC32E-5668-BD0D-4DBC-D4BF959B9242, name = (null), state = disconnected>
<CBPeripheral: 0x166a8220, identifier = 4D9FA1A1-0090-465F-B53D-363B0F3BBD27, name = (null), state = disconnected>
Edit: Added more code
Here is my code
import Foundation
import CoreBluetooth
import UIKit
class BluetoothController: UITableViewController, CBCentralManagerDelegate, CBPeripheralManagerDelegate, CBPeripheralDelegate {
var centralManager: CBCentralManager!
var peripheralManager: CBPeripheralManager!
var service : CBMutableService!
let uuid:CBUUID = CBUUID(string: "09d921ff-b80c-47b1-bc2b-5bbaadf62010")
let charaUuid:CBUUID = CBUUID(string: "09d921ff-b80c-47b1-bc2b-5bbaadf62021")
override func viewDidLoad() {
print("Initialzing managers")
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
centralManager = CBCentralManager(delegate: self, queue: nil)
service = CBMutableService(type: uuid, primary: true) //<--Probably not causing it but without a service...
let properties: CBCharacteristicProperties = [.notify, .read, .write]
let permissions: CBAttributePermissions = [.readable, .writeable]
let characteristic = CBMutableCharacteristic(
type: charaUuid,
properties: properties,
value: nil,
permissions: permissions)
service.characteristics = [characteristic];
peripheralManager.add(service)
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
print("peripheral state updated")
print("\(peripheral.description)")
}
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?)
{
print("started advertising")
}
// Check if the bluetooth is powered on
// Start looking for devices
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
print("Scanning for devices")
centralManager.scanForPeripherals(withServices: nil, options: nil)
startAdvert()
} else {
print("Bluetooth not available.")
}
}
func startAdvert(){
let advertisingData = [CBAdvertisementDataLocalNameKey:"Test Device", CBAdvertisementDataServiceUUIDsKey: uuid] as [String : Any]
peripheralManager.startAdvertising(advertisingData)
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber){
print(peripheral)
//didReadPeripheral(peripheral, rssi: RSSI)
}
}
As Paulw11 notes, you're not connected because you never call connect(). You're discovering random BLE devices around you that have nothing to do with your app. That's because you're scanning for all possible services:
centralManager.scanForPeripherals(withServices: nil, options: nil)
You should almost never do that. It's bad for the battery and it's rarely what you want. You want to scan for your service.
centralManager.scanForPeripherals(withServices: [uuid], options: nil)
And then when you discover other devices advertising your service, then you need to connect to them.
To #WholeCheese's comment, the details of the question didn't really relate to the nil name, but to address the question in the title, you will not see a name from advertising if the devices does not advertise a local name over BLE. This is quite common. There is not a lot of space in the advertising packet for data (roughly 30 bytes), and local names can be quite large.
If you're building a scanning app and want to display those names, you'll need to connect to the device. At that point, Core Bluetooth will read the GAP name (rather than the advertised Local Name). In most cases the GAP names should be there (it's not promised from all devices, but it generally should be there). Core Bluetooth caches a lot of information, so if you've ever connected to the device before, it is possible that peripheral.name will be filled out without connecting even if it's not advertised. But if it isn't, you'll need to connect.
I am currently writing an app that connects via BLE to an external device. All operations are fine when the app is in foreground.....including connecting, obtaining data, and reconnecting (in cases of the device going out of range) via a reconnect protocol I wrote. The app also functions properly when it is backgrounded but the BLE connection remains alive.
However, the only instance in which the app does not function is if the app is backgrounded and then the BLE device goes out of range. Once the connection is broken, the app seems to be suspended by iOS after a few seconds and none of the code I wrote will continue to function...even if I bring the device back into range. The only way to restore functionality is to bring the app back into the foreground again. (Note: I have the info.plist file and all other settings configured appropriately for centralManager background functionality)
I've read some documentation and it seems that this comes down to not having state preservation/restore code properly implemented. I went ahead and implemented the "willRestoreState" and "didUpdateState" commands, but the app still doesn't reconnect to a device once it has been suspended when in background mode.
I've shown some relevant code below, including the willRestoreState, didUpdateState, and didDisconnect methods. Any ideas or suggestions? Thanks!
//define service+characteristic UUIDS
let serviceUUID = CBUUID(string: "xxxxxxxxx")
let streamingCharacteristicUUID = CBUUID(string: "xxxxxxxxx")
//Local dictionary of UUIDs for connected devices (the ble code updates this var with each connected device)
var devicesUniqueId:[UUID:String] = [UUID:String]()
//Local dictionary of connected peripherals, with respect to each of their UUIDS (the ble code updates this var with each connected device)
var sensorPeripheral = [UUID:CBPeripheral]()
///restoreState function
func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
if let peripheralsObject = dict[CBCentralManagerRestoredStatePeripheralsKey] {
let peripherals = peripheralsObject as! Array<CBPeripheral>
print ("starting restorestate code")
if peripherals.count > 0 {
for i in 0 ..< peripherals.count {
print ("starting restorecheck")
//Check if the peripheral exists within our list of connected peripherals, and assign delegate if it does
if self.devicesUniqueId.keys.contains(peripherals[i].identifier) {
peripherals[i].delegate = self
}
}
}
}
}
func centralManagerDidUpdateState(_ central: CBCentralManager)
{
if central.state != .poweredOn
{
return
}
self.startScanning()
//////Preservation + Restoration code////////
//Iterate through array of connected UUIDS
let keysArray = Array(self.patchDevicesUniqueId.keys)
for i in 0..<keysArray.count {
//Check if peripheral exists for given UUID
if let peripheral = self.sensorPeripheral[keysArray[i]] {
print("peripheral exists")
//Check if services exist within the peripheral
if let services = peripheral.services {
print("services exist")
//Check if predefined serviceUUID exists within services
if let serviceIndex = services.index(where: {$0.uuid == serviceUUID}) {
print("serviceUUID exists within services")
let transferService = services[serviceIndex]
let characteristicUUID = streamingCharacteristicUUID
//Check if predefined characteristicUUID exists within serviceUUID
if let characteristics = transferService.characteristics {
print("characteristics exist within serviceUUID")
if let characteristicIndex = characteristics.index(where: {$0.uuid == characteristicUUID}) {
print("characteristcUUID exists within serviceUUID")
let characteristic = characteristics[characteristicIndex]
//If characteristicUUID exists, begin getting notifications from it
if !characteristic.isNotifying {
print("subscribe if not notifying already")
peripheral.setNotifyValue(true, for: characteristic)
}
else {
print("invoke discover characteristics")
peripheral.discoverCharacteristics([characteristicUUID], for: transferService)
}
}
}
}
else {
print("invoke discover characteristics")
peripheral.discoverServices([serviceUUID])
}
}
}
}
}
//didDisconnect method to handle a connect command issue
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?)
{
//commented out unnecessary code
self.removePeripheralData(peripheral: peripheral)
if(sensorCount>0){
sensorCount -= 1
}
}
//removePeripheralData function used in didDisconnect
func removePeripheralData ( peripheral: CBPeripheral) {
//Commented out unnecessary code
//Issue reconnect command
print ("issuing reconnect command")
centralManager.connect(peripheral, options: nil)
//Commented out unnecessary code
handleDidRemoveDevice()
}
Working on iBecon signal using Core Bluetooth i am able to search with CBCentralManager scan optionn nil :-
Shared.sharedInstance.centralManager?.scanForPeripherals(withServices: nil, options:[CBCentralManagerScanOptionAllowDuplicatesKey:true])
But when i provide my desirable service ID i.e :-
Shared.sharedInstance.centralManager?.scanForPeripherals(withServices: [serviceID], options:[CBCentralManagerScanOptionAllowDuplicatesKey:true])
it never calls didDiscoverPeripheral Delegate method, I need to scan the peripheral in background mode too and according to apple documentation you need to provide service id explicitly whenever you need to scan in background mode. Anyone can help what i am doing wrong here.
I am using like this,
connect on button click event
and use CBCentralManagerDelegate, CBPeripheralDelegate Delegate
func connectDevice(sender:UIButton){
if peripheral != nil {
manager.cancelPeripheralConnection(peripheral)
manager = CBCentralManager(delegate: self, queue: nil)
}
}
func centralManagerDidUpdateState(central: CBCentralManager) {
if central.state == CBCentralManagerState.PoweredOn {
central.scanForPeripheralsWithServices(nil, options: nil)
} else {
self.showAlert(Messages().alert , message: "Bluetooth is not on.")
}
}
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
let device = (advertisementData as NSDictionary).objectForKey(CBAdvertisementDataLocalNameKey) as? NSString
print(device)
if device?.containsString(BEAN_NAME) == true {
self.manager.stopScan()
self.peripheral = peripheral
self.peripheral.delegate = self
manager.connectPeripheral(peripheral, options: nil)
}
}