I am looking to build an iOS application that uses CoreBluetooth to act as a BLE peripheral and a web app that uses WebBluetooth to scan for, find and exchange messages with the iPhone app.
I started off with this Swift code:
import SwiftUI
import CoreBluetooth
class BLE: NSObject, CBPeripheralManagerDelegate {
var peripheralManager: CBPeripheralManager!
override init() {
super.init()
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
switch peripheral.state {
case .unknown: print("unknown")
case .resetting: print("resetting")
case .unsupported: print("unsupported")
case .unauthorized: print("unauthorized")
case .poweredOff:
print("poweredOff")
peripheral.stopAdvertising()
case .poweredOn:
print("poweredOn")
let characteristic1 = CBMutableCharacteristic(
type: CBUUID(nsuuid: UUID()),
properties: [.notify, .write, .read],
value: nil,
permissions: [.readable, .writeable]
)
let characteristic2 = CBMutableCharacteristic(
type: CBUUID(nsuuid: UUID()),
properties: [.read],
value: "test".data(using: .utf8),
permissions: [.readable]
)
let serviceUuid = CBUUID(nsuuid: UUID())
let service = CBMutableService(type: serviceUuid, primary: true)
service.characteristics = [characteristic1, characteristic2]
peripheralManager.add(service)
//let uuid = CBUUID(string: "14b50a17-51be-478f-9b24-7efa8d2a083f")
peripheral.startAdvertising([
CBAdvertisementDataLocalNameKey: "blething",
CBAdvertisementDataServiceUUIDsKey: [serviceUuid]
])
default: print("???")
}
}
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
print(error)
}
}
#main
struct BTtestApp: App {
let ble = BLE()
var body: some Scene {
WindowGroup {
Text("Hello, world!")
.padding()
}
}
}
I believe this is all of the code needed to make my iOS app BLE peripheral discoverable. I run this app on my phone and the advertisement call seems to go through without any runtime errors or crashes. The error printed is nil.
However, when I use https://googlechrome.github.io/samples/web-bluetooth/scan.html?allAdvertisements=true and scan for advertisements, nothing at all comes up.
When I uncheck All Advertisements and fill in Device Name to say blething, nothing comes up still.
I tried using both uuid and [uuid] (an array) for the service UUIDs value as I am not sure which is correct. The docs say the type is string https://developer.apple.com/documentation/corebluetooth/cbadvertisementdataserviceuuidskey but also that it should be an array? Anyway, neither worked.
What is the problem? Why is my BLE peripheral implemented in software using CoreBluetooth not showing up?
Related
I'm learning iOS vs OSX BLE.
I notice that I can't instantiate CBCentralManager in iOS because of:
[CoreBluetooth] XPC connection invalid
Unsupported
Versus via OSX because the iOS platform doesn't have the 'App Sandbox' Characteristic where I can set for BLE use.
Here's my iOS Code:
import SwiftUI
struct ContentView: View {
#ObservedObject var bleManager = BLEManager()
var body: some View {
ZStack {
Color("Background")
Text("Hello")
}
}
}
class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate {
var centralManager: CBCentralManager!
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
public func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
print("power is on")
case .resetting:
print("Resetting")
case .unsupported:
print("Unsupported")
case .unauthorized:
print("UnAuthorized")
case .unknown:
print("UnKnown")
case .poweredOff:
print("Powered OFF")
#unknown default:
print("**** Default ****")
}
}
}
Here's the required .plist entry:
I understand that iPhones can be either a BLE center or peripheral.
Simple Question: How do I code for a bona fide CBCentralMaster for iOS?
I'm merely taking baby steps here: coding for peripheral detection.
... then continue from there.
Are you running on the simulator (where it is unsupported) instead of on device?
See the main Core Bluetooth docs:
Your app will crash if its Info.plist doesn’t include usage description keys for the types of data it needs to access. To access Core Bluetooth APIs on apps linked on or after iOS 13, include the NSBluetoothAlwaysUsageDescription key. In iOS 12 and earlier, include NSBluetoothPeripheralUsageDescription to access Bluetooth peripheral data.
I am trying to make a Bluetooth scanning and connect app using SwiftUI. I am having issues refreshing the list view in SwiftUI as the Bluetooth scanning starts and I get some peripheral names with RSSI values. Any guidance would be useful. The code is as follows:
Firstly I have a SwiftUI view with a list and the text in HorizontalView within that. I will be using ForEach() later on but for now I just kept it simple with one text.
import SwiftUI
struct ContentView: View {
var body: some View {
List{
// ForEach: Loop here to list all BLE Devices in "devices" array
// Monitor "devices" array for changes. As changes happen, Render the Body again.
HStack{
Text("Device-1")
.onTapGesture {
// To Do: Call Connect BLE Device
print("Device-1 Connected.")
}
}
}.navigationBarTitle("BLE Devices")
.onAppear(perform: connectBLEDevice)
}
private func connectBLEDevice(){
let ble = BLEConnection()
// Start Scanning for BLE Devices
ble.startCentralManager()
}
}
// UIHosting Controller
var child = UIHostingController(rootView: ContentView())
For scanning and connecting to the Bluetooth device, this is the code that I use:
import Foundation
import UIKit
import CoreBluetooth
open class BLEConnection: NSObject, CBPeripheralDelegate, CBCentralManagerDelegate {
// Properties
private var centralManager: CBCentralManager! = nil
private var peripheral: CBPeripheral!
public static let bleServiceUUID = CBUUID.init(string: "XXXX")
public static let bleCharacteristicUUID = CBUUID.init(string: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX")
// Array to contain names of BLE devices to connect to.
// Accessable by ContentView for Rendering the SwiftUI Body on change in this array.
var scannedBLEDevices: [String] = []
func startCentralManager() {
self.centralManager = CBCentralManager(delegate: self, queue: nil)
print("Central Manager State: \(self.centralManager.state)")
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.centralManagerDidUpdateState(self.centralManager)
}
}
// Handles BT Turning On/Off
public func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch (central.state) {
case .unsupported:
print("BLE is Unsupported")
break
case .unauthorized:
print("BLE is Unauthorized")
break
case .unknown:
print("BLE is Unknown")
break
case .resetting:
print("BLE is Resetting")
break
case .poweredOff:
print("BLE is Powered Off")
break
case .poweredOn:
print("Central scanning for", BLEConnection.bleServiceUUID);
self.centralManager.scanForPeripherals(withServices: [BLEConnection.bleServiceUUID],options: [CBCentralManagerScanOptionAllowDuplicatesKey : true])
break
}
if(central.state != CBManagerState.poweredOn)
{
// In a real app, you'd deal with all the states correctly
return;
}
}
// Handles the result of the scan
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
print("Peripheral Name: \(String(describing: peripheral.name)) RSSI: \(String(RSSI.doubleValue))")
// We've found it so stop scan
self.centralManager.stopScan()
// Copy the peripheral instance
self.peripheral = peripheral
self.scannedBLEDevices.append(peripheral.name!)
self.peripheral.delegate = self
// Connect!
self.centralManager.connect(self.peripheral, options: nil)
}
// The handler if we do connect successfully
public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
if peripheral == self.peripheral {
print("Connected to your BLE Board")
peripheral.discoverServices([BLEConnection.bleServiceUUID])
}
}
// Handles discovery event
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let services = peripheral.services {
for service in services {
if service.uuid == BLEConnection.bleServiceUUID {
print("BLE Service found")
//Now kick off discovery of characteristics
peripheral.discoverCharacteristics([BLEConnection.bleCharacteristicUUID], for: service)
return
}
}
}
}
// Handling discovery of characteristics
public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let characteristics = service.characteristics {
for characteristic in characteristics {
if characteristic.uuid == BLEConnection.bleServiceUUID {
print("BLE service characteristic found")
} else {
print("Characteristic not found.")
}
}
}
}
}
The task here is to scan for peripherals and show them as they come and go from the range in the SwiftUI List.
Thanks.
You have no state here and no way to update that state. I would probably make BLEConnection an ObservableObject and then #Publish the array of devices:
open class BLEConnection: ..., ObservableObject {
#Publish var scannedBLEDevices: [Device] = [] // keep as [String] for initial debugging if you want
}
then, in your ContentView, subscribe to those changes:
struct ContentView: View {
#ObservedObject var bleConnection = BLEConnection()
var body: some View {
// if still `[String]` then \.self will work, otherwise make `Device` `Identifiable`
List(bleConnection.devices, id: \.self) { device in
Text(verbatim: device)
}
}
}
Now, when your connection adds/removes devices to scannedBLEDevices, it will automatically update in your ContentView.
I am trying to make an app that will be used as a main control for a bluetooth watch(e.g. fitness bracelets, smart watches). I have done my research about this, and although some people managed to do so, they don't give many details about the process. Below are a few of the "solutions" that I found:
Is it possible to switch central and peripheral each other on iOS?
Can iOS do central and peripheral work on same app at same time?
Peripheral and central at the same time on iOS
All of these are in Objective-C and although I am familiar with it, the posts are 3+ years old so things have changed concerning the code. Another problem is that I need to use the app with another Bluetooth device, not an iOS device as the ones above are doing it, and for the moment the connection request can only come from the iPhone, not from the bluetooth device.
The question is if it's possible to achieve the desired result, and if so, what would be the best way to do it? So far, one of the proposed solutions was to connect to the device, acquire the UUID and then switch the iPhone to peripheral mode so that it can advertise it's services. That is not possible(in my opinion), at least in this current stage.
iOS already has a predefined service that can be discovered and accessed by the device (Current Time Service) when the 2 of them connect, without any modifications from my part so there should be a way to accomplish this.
I hope I made myself clear enough about the problem, if you believe I can add more details to clarify the context, let me know. Thanks for your time.
I am posting below some of the key code from the view in which I discover peripherals:
override func viewDidAppear(_ animated: Bool) {
manager = CBCentralManager(delegate: self, queue: nil)
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
peripherals = []
if (manager?.state == CBManagerState.poweredOn) {
scanBLEDevices()
self.tableView.reloadData()
}
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
switch(peripheral.state)
{
case.unsupported:
print("Peripheral is not supported")
case.unauthorized:
print("Peripheral is unauthorized")
case.unknown:
print("Peripheral is Unknown")
case.resetting:
print("Peripheral is Resetting")
case.poweredOff:
print("Peripheral service is powered off")
case.poweredOn:
print("Peripheral service is powered on")
print("Start advertising.")
let serviceUUID:CBUUID = CBUUID(string: self.service_uuid_string)
let locationUUID:CBUUID = CBUUID(string: self.location_and_speed)
// Start with the CBMutableCharacteristic
self.locationCharacteristic = CBMutableCharacteristic(type: locationUUID, properties: .notify , value: nil, permissions: .readable)
// Then the service
let locationService = CBMutableService(type: serviceUUID, primary: true)
// Add the characteristic to the service
locationService.characteristics?.append(locationCharacteristic!)
// And add it to the peripheral manager
self.peripheralManager?.add(locationService)
peripheralManager?.startAdvertising([CBAdvertisementDataServiceUUIDsKey : serviceUUID])
}
}
I'm getting back with the correct way of achieving the required functionality. After initialising the peripheralManager, create a CBMutableService and hold a reference to it(declared at the top of the class).
var globalService:CBMutableService? = nil
Next step is to check for the peripheralManager state, and do all the required work after you receive the poweredOn state:
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
switch(peripheral.state)
case.poweredOn:
print("Peripheral service is powered on")
createServiceWithCharacteristics()
}
func createServiceWithCharacteristics(){
let serviceUUID:CBUUID = CBUUID(string: self.service_uuid_string)
let featureCharacteristicUUID:CBUUID = CBUUID(string: self.feature_characteristic_uuid_string)
// Start with the CBMutableCharacteristic
let permissions: CBAttributePermissions = [.readable, .writeable]
let properties: CBCharacteristicProperties = [.notify, .read, .write]
self.featureCharacteristic = CBMutableCharacteristic(type: featureCharacteristicUUID, properties: properties , value: nil, permissions: permissions)
// Then the service
let localService = CBMutableService(type: serviceUUID, primary: true)
// Add the characteristic to the service
localService.characteristics = [featureCharacteristic!]
globalService = localService
// And add it to the peripheral manager
self.peripheralManager?.add(globalService!)
print("Start advertising.")
peripheralManager?.startAdvertising([CBAdvertisementDataLocalNameKey:"Name"])
}
I am currently trying to develop and application that allows users to bond to a Peripheral via a click of a button and the password will be automatically entered.
Is it possible to Bond and Remove Bond programmatically using swift?
Pairing is initiated any time that you attempt to write to or read from a characteristic on the BLE device. However, if the device is not set to require authentication and/or bonding, you will not see the iOS popup which requests the PIN code.
I struggled with this with my HM-10 because I could write data to the characteristic using the Core Bluetooth (via Swift) function writeValue() without ever seeing the pairing popup.
I couldn't figure it out until I read the HM-10 (implements the IC cc2451) datasheet very closely and found that I needed to set the AT+TYPE to value 3. It defaults to 0 which means that the HM-10 does not require pairing/bonding so you never see the iOS popup.
You can read more about the details where I asked the question and ultimately found the solution and wrote it up: How do I pair and/or bond to BLE on iOS using Swift code and an HM-10 so data sent is encrypted?
Follow the step to connect Ble device into iOS Program.
1) Import
import CoreBluetooth
2) Declared the variables into the class or ViewController.
let kServiceUART = CBUUID(string: "0x1800")
var peripheralHeartRateMonitor: CBPeripheral?
var cbManger: CBCentralManager!
3) Initialize the cbManger into ViewDidLoad function of viewController or initalize function of class.
cbManger = CBCentralManager(delegate: self, queue: .main)
4) Override delegate method of the CBCentralManager .
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .unsupported:
print("BLe Unsupported")
break
case .unauthorized:
print("BLe unauthorized")
break
case .poweredOff:
let alertMessgesInst = AlertMessages.sharedInstance
CommonUtils.showAlert(alertMessgesInst.actofit_Title, message: alertMessgesInst.trun_On_blueTooth)
break
case .poweredOn:
if isNewFirmWareOFImpulse {
let uuidString = StorageServices.readFromDefaults(key: Constants.userDefaultKeys.impulseUUID)
let uuid = UUID(uuidString:uuidString as! String )
let device = cbManger.retrievePeripherals(withIdentifiers: [uuid!])
peripheralHeartRateMonitor = device.first
peripheralHeartRateMonitor!.delegate = self
cbManger?.connect(peripheralHeartRateMonitor!)
}else {
let option:[String: Any] = [CBCentralManagerScanOptionAllowDuplicatesKey: NSNumber(value: false)]
cbManger.scanForPeripherals(withServices: nil, options: option)
}
break
case .unknown:
print("BLe unknown")
break
default:
break
} // End Swith
} // End 'centralManagerDidUpdateState' function.
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if isNewFirmWareOFImpulse {
peripheralHeartRateMonitor = peripheral
print("UUid of band is :- \(peripheralHeartRateMonitor?.identifier.uuidString)")
if impulseName == peripheral.name {
peripheralHeartRateMonitor!.delegate = self
cbManger.stopScan()
// STEP 6: connect to the discovered peripheral of interest
cbManger?.connect(peripheralHeartRateMonitor!)
} // End impulse condition
}else {
let keysArray = advertisementData.keys
if let tempImpulseName = peripheral.name {
print(impulseName + " and " + tempImpulseName )
if impulseName == tempImpulseName {
for key in keysArray {
if key == "kCBAdvDataManufacturerData"{
let manufactureData = advertisementData[key]
if let stringValue = manufactureData.debugDescription as? String {
var heartValue: String = String()
heartValue = stringValue
heartValue.removeLast()
heartValue.removeLast()
let last = heartValue.removeLast()
let secondLast = heartValue.removeLast()
let hR = String([secondLast, last])
if let value = UInt8(hR, radix: 16){
if Int(value) > 60 {
hrArray.append(Int(value))
}
} // End the value block
} // end of if 'stringValue' condition
} // end 'Key' if condition
} // End for each loop
} // End impulse condition
} // End pheripheral if condition
} // end version condition
} // End function 'didDiscover peripheral'.
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
// STEP 8: look for services of interest on peripheral
peripheralHeartRateMonitor?.discoverServices(nil)
} // END func centralManager(... didConnect peripheral
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if error != nil {
I am new to swift language. I have been working on establishing a bluetooth connection between my iOS app and a barcode scanner. The barcode scanner has bluetooth enabled. I tried to establish a global bluetooth connection with my iphone and it works. Based on some references from the Internet, I have written the following sample code.
import UIKit
import CoreBluetooth
class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
var manager : CBCentralManager!
override func viewDidLoad() {
super.viewDidLoad()
manager = CBCentralManager(delegate : self, queue : nil)
}
func centralManagerDidUpdateState(central: CBCentralManager) {
var consoleMsg = "hello"
switch(central.state) {
case .PoweredOff:
consoleMsg = "Bluetooth is powered off"
case .PoweredOn:
consoleMsg = "Bluetooth is powered on"
manager.scanForPeripheralsWithServices(nil, options: nil)
case .Resetting:
consoleMsg = "Bluetooth is Resetting"
case .Unauthorized:
consoleMsg = "Bluetooth is Unauthorized"
case .Unknown:
consoleMsg = "Bluetooth is Unknown"
case .Unsupported:
consoleMsg = "Bluetooth is Unsupported"
}
print("\(consoleMsg)")
}
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber) {
print("Discovered a peripheral \(peripheral)")
}
When I try to run the above code, log displays "Bluetooth is Powered on" and thats it. It is not discovering my barcode scanner. I also made sure that the barcode scanner is in discoverable mode. Why my code is not discovering the near-by bluetooth enabled barcode scanner? Have I done any mistake in the above code?
Thanks
You should absolutely check authorization first !