In this post, I show how to connect a Bluetooth device from SwiftUI and how to receive data.
The code used in this example is here https://github.com/niqt/Blue
Proceed for step: the things to do are: check if the Bluetooth is enabled, scan to search the devices, connect with one and wait for data. The check of the Bluetooth status and the connection is a feature of the connection manager, the others of the peripheral. Let’s see it in details.
Every device has a service, identified with a UUID and any service can have one more property. The properties can be read-only (to read value) or read-write (write is like send a command). Also, the properties are identified with a UUID.
Let’s look at the code:
import Foundation import CoreBluetooth
Then define the peripherical struct:
struct Peripheral: Identifiable { let id: Int let name: String let rssi: Int }
We use this struct to expose the peripherical to SwiftUI.
Now, start to look at the core of the application:
class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate { var centralBE: CBCentralManager! @Published var isSwitchedOn = false @Published var peripherals = [Peripheral]() var peripheralsId = [UUID]() var thermometer: CBPeripheral! var serviceId = CBUUID(string: "00000001-710e-4a5b-8d75-3e5b444b3c3f") var readNotify = CBUUID(string: "00000002-710e-4a5b-8d75-3e5b444b3c3f") var readWrite = CBUUID(string: "00000003-710e-4a5b-8d75-3e5b444b3c3f")
The BLEManger is an NSObject that is observable (from the SwiftUI side) and implements the protocols to manage the Bluetooth status and services. Instead, the CBPeripheralDelegate protocol it’s about peripherical and properties operation.
So, the CBCentralManager is the Bluetooth manager. The isSwitchedOn contains the status of the Bluetooth and the peripherals array contains the list of the peripherals. These last two variables are published, so the value can be get from SwiftUI.
The thermometer peripheral contains the data of the peripheral on that we want to work in this example, the temperature of a RaspberryPI (the code is here https://github.com/Douglas6/cputemp). Thus, with our app, we want to read the CPU temp of a raspberry. The serviceId and properties are from this Raspberry example, you are free to replace them with anything you want.
With the init, simply initialize the manager and the delegate:
override init() { super.init() centralBE = CBCentralManager(delegate: self, queue: nil) centralBE.delegate = self }
This function is called when the status changes:
func centralManagerDidUpdateState(_ central: CBCentralManager) { if central.state == .poweredOn { isSwitchedOn = true } else { isSwitchedOn = false } }
If the state is .poweredOn (the Bluetooth is enabled on the device) we can start the scanning:
func startScanning() { print("startScanning") centralBE.scanForPeripherals(withServices: nil, options: nil) }
After the scanning is started, with this function it’s possible to see the device around us:
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 let newPeripheral = Peripheral(id: peripherals.count, name: peripheralName, rssi: RSSI.intValue) if !peripheralsId.contains(peripheral.identifier) && peripheralName == "Thermometer" { peripheralsId.append(peripheral.identifier) peripherals.append(newPeripheral) stopScanning() self.thermometer = peripheral self.thermometer.delegate = self self.centralBE.connect(peripheral, options: nil) } } else { peripheralName = "Unknown" } }
If the Thermometer peripheral is discovered, it’s added the the array of peripheral, the scanning is stopped, set the delegate for the peripheral to the self and to simplify connect to device (without user interaction).
If the connection has success, this function is called:
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { print("DidConnect") discoverServices(peripheral: peripheral) }
Thus starts to discover the services:
func discoverServices(peripheral: CBPeripheral) { peripheral.discoverServices([serviceId]) }
If at least one service is discovered, start to search characteristics:
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { guard let services = peripheral.services else { print("ERROR didDiscoverServices") return } if services.count > 0 { discoverCharacteristics(peripheral: peripheral) } }
When the characteristic is found:
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { service.characteristics?.forEach { char in print("CHAR id: \(char.uuid.uuidString) VALUE: \(String(describing: char.value) )") peripheral.readValue(for: char) } }
In the BLEManger.swift file you can see also other two lines functions, their name are. self-explanatory.
Take a look to the SwiftUI code:
struct ContentView: View { @ObservedObject var bleManager = BLEManager() @State private var scan = false var body: some View { VStack (spacing: 10) { Text("STATUS") .font(.headline) if bleManager.isSwitchedOn { Text("ON") .foregroundColor(.green) } else { Text("OFF") .foregroundColor(.red) } HStack { Spacer() Toggle("Scan", isOn: $scan) .toggleStyle(SwitchToggleStyle(tint: .red)) .onChange(of: scan) { value in // action... self.bleManager.startScanning() } } Text("Bluetooth Devices") .font(.largeTitle) .frame(maxWidth: .infinity, alignment: .center) List(bleManager.peripherals) { peripheral in HStack { Text(peripheral.name) Spacer() Text(String(peripheral.rssi)) } }.frame(height: 300) Spacer() } } }
We can define an observable object, the BLEManager, so we can get the Bluetooth status, with the toggle button active the scan and in the end, show the peripheral list in a SwiftUI list. Enjoy with your peripheral!
Note: English is not my native language, so I’m sorry for some errors. I appreciate it if your correct me.
2 comments
Do you also need to assign a value to NSBluetoothAlwaysUsageDescription?
Hi, yes!