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.
Related
I created a new app with swiftUI and trying to use coreBluetooth library to connect Raspberry Pi 4. I searched all the google and I stuck at connecting Raspberry Pi 4. I can find all the devices (peripherals) but I'm not able to connect them.
Here is the code;
class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate {
var myCentral: CBCentralManager!
#Published var isSwitchedOn = false
#Published var peripherals = [Peripheral]()
override init() {
super.init()
myCentral = CBCentralManager(delegate: self, queue: nil)
myCentral.delegate = self
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
isSwitchedOn = true
}
else {
isSwitchedOn = false
}
}
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"
}
if peripheral.identifier.uuidString == "6D915395-3E79-0072-22A3-009DDC331F7C" {
myCentral.stopScan()
myCentral.connect(peripheral, options: nil)
print("okokokokok")
}
let newPeripheral = Peripheral(id: peripherals.count, name: peripheralName, rssi: RSSI.intValue)
print(newPeripheral)
peripherals.append(newPeripheral)
}
func centralManager(central: CBCentralManager!,didConnectPeripheral peripheral: CBPeripheral!)
{
peripheral.discoverServices(nil)
print("Connected")
print(peripheral.name!)
print(peripheral.services!)
}
}
Deleted some of code parts bc of stackoverfow
Your problem for the connection was not holding a reference to the CBPeripheral object to which you want to connect. So you've solved it by referencing to it in your BLEManager class.
Now how can you send and get data to / from your Raspberry? Well you must implement the CBPeripheralDelegate in your BLEManager class.
After the central manager finishes discovering the available services it will call didDiscoverServices method of CBPeripheralDelegate; where you need to invoke discoverCharacteristics for the discovered service.
Again, when characteristics has been discovered for the given service with the discoverCharacteristics(_:for:) method which you invoked in the didDiscoverServices callback, you can set the notify value if you want to know about characteristic updates.
After the characteristics has been discovered you can send values by using writeValue method of CBPeripheral instance to which you've referenced it when connected.
When you send data from the Raspberry for a certain charachteristic, the didUpdateValueFor method of CBPeripheralDelegate will get called. In this callback; you can obtain the received data from the CBCharacteristic.value property. Note that the value property is optional, hence you need to handle it with care.
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?
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!
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 quite new to iOS programming and also to bluetooth protocol.
I have found a sample code written in swift and trying to modify it to work with my own bluetooth module. The module I have is DBM01 from dorji.
The service I need to use is FFF0 and the characteristics is FFF1 for sending a ASCII value.
When I use the LightBlue app on my macbook and connect to the board I have designed, which has the DBM01 module on it, I can send the char value of "1" and I get the expected response (Turning on a LED) and when I send value of "0" it turns the LED off.
Now with the code I have, I can connect to the DBM01 module. I can print its name. However, I cannot disconnect from it with the following function. I am also not sure if this is for disconnecting from the device or it is called automatically when the device is disconnected. Regardless, it does not work either way.
func centralManager(central: CBCentralManager,
didDisconnectPeripheral peripheral: CBPeripheral,
error: NSError?)
My main problem is I really didn't understand how I specify the service and the characteristics that I am interested in and connect to specific device that has them.
I am also unable to send a message. When I try I got the predefined error, since the following condition did't hold
if writeCharacteristic != nil
Below is my full code.
Appreciate if you can point out where I am doing wrong and how I can achieve connecting to specific device with specific service and characteristics information and sending data.
//
// ViewController.swift
// bleSwift
//
import UIKit
import CoreBluetooth
class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate {
var centralManager: CBCentralManager!
var peripheral: CBPeripheral!
var writeCharacteristic: CBCharacteristic!
var service: CBService!
var characteristic: CBCharacteristic!
var bluetoothAvailable = false
let message = "1"
#IBOutlet weak var deviceName: UILabel!
#IBOutlet weak var ServiceName: UILabel!
#IBOutlet weak var CharacteristicsName: UILabel!
func centralManagerDidUpdateState(central: CBCentralManager)
{
print("Checking state")
switch (central.state)
{
case .PoweredOff:
print("CoreBluetooth BLE hardware is powered off")
case .PoweredOn:
print("CoreBluetooth BLE hardware is powered on and ready")
bluetoothAvailable = true;
case .Resetting:
print("CoreBluetooth BLE hardware is resetting")
case .Unauthorized:
print("CoreBluetooth BLE state is unauthorized")
case .Unknown:
print("CoreBluetooth BLE state is unknown");
case .Unsupported:
print("CoreBluetooth BLE hardware is unsupported on this platform");
}
if bluetoothAvailable == true
{
discoverDevices()
}
}
func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral, advertisementData: [String : AnyObject], RSSI: NSNumber)
{
// Stop scanning
self.centralManager.stopScan()
print("Stopped Scanning")
// Set as the peripheral to use and establish connection
//self.peripheral = peripheral
//self.peripheral.delegate = self
//self.centralManager.connectPeripheral(peripheral, options: nil)
peripheral.discoverServices([CBUUID(string: "FFF0")])
print("CONNECTED!!")
print(peripheral.name)
deviceName.text = peripheral.name
}
func discoverDevices() {
print("Discovering devices")
centralManager.scanForPeripheralsWithServices(nil, options: nil)
}
#IBAction func disconnectDevice(sender: AnyObject) {
func centralManager(central: CBCentralManager,
didDisconnectPeripheral peripheral: CBPeripheral,
error: NSError?)
{
print("CONNECTION WAS DISCONNECTED")
deviceName.text = "Disconnected"
}
}
#IBAction func Scan(sender: AnyObject)
{
print("Scan")
centralManager = CBCentralManager(delegate: self, queue: nil)
}
#IBAction func Send(sender: AnyObject)
{
let data = message.dataUsingEncoding(NSUTF8StringEncoding)
if writeCharacteristic != nil
{
print("Sent")
peripheral!.writeValue(data!, forCharacteristic: writeCharacteristic, type: CBCharacteristicWriteType.WithoutResponse)
}
else
{
print("Couldn't Send")
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
In order to send data to your ble peripheral, you should:
Start scanning.
In your ble central manager delegate you'll recieve didDiscoverPeripheral callback with discovered peripheral. Set yourself as its delegate and connect to it: centralManager.connectPeripheral(...)
Receive didConnectPeripheral in delegate. Now call discoverServices for this peripheral.
Recieve didDiscoverServices in your delegate. Finaly, call discoverCharacteristics for your service.
You'll recieve characteristic in didDiscoverCharacteristic delegate method. After that, you'll be able to send data to exact characteristic of your peripheral.
To disconnect peripheral, call method centralManager.cancelPeripheralConnection(...)