A device running with the following lines of code can receive a message from a peripheral. In this manner, though, I can only receive messages from one peripheral since the service and characteristic IDs are hardcoded in CentralViewModel.swift. So my question is how I can observe messages from multiple peripherals. Thanks.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
VStack {
NavigationLink(destination: CentralView()) {
Text("Central")
}
.buttonStyle(.borderedProminent)
.padding()
}
}
}
}
// CentralView.swift //
import SwiftUI
struct CentralView: View {
@StateObject var central: CentralViewModel = CentralViewModel()
var body: some View {
Text(central.message)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.padding(20)
.onDisappear {
central.stopAction()
}
}
}
// CentralViewModel.swift //
import Foundation
import CoreBluetooth
class CentralViewModel: NSObject, ObservableObject {
@Published var message: String = ""
var serviceUUID: CBUUID!
var characteristicUUID: CBUUID!
var centralManager: CBCentralManager!
var discoveredPeripheral: CBPeripheral?
var transferCharacteristic: CBCharacteristic?
var writeIterationsComplete = 0
//var connectionIterationsComplete = 0
let defaultIterations = 5
var data: Data = Data()
override init() {
super.init()
self.serviceUUID = CBUUID(string: "994F8A12-FE8E-4CCB-BD7B-1AE989A32853")
self.characteristicUUID = CBUUID(string: "F4BD0CA2-7581-40E2-A517-1CE275A3A749")
centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionShowPowerAlertKey: true])
}
func stopAction() {
centralManager.stopScan()
}
private func cleanup() {
guard let discoveredPeripheral = discoveredPeripheral, case .connected = discoveredPeripheral.state else { return }
for service in (discoveredPeripheral.services ?? [] as [CBService]) {
for characteristic in (service.characteristics ?? [] as [CBCharacteristic]) {
if characteristic.uuid == characteristicUUID && characteristic.isNotifying {
self.discoveredPeripheral?.setNotifyValue(false, for: characteristic)
}
}
}
centralManager.cancelPeripheralConnection(discoveredPeripheral)
}
private func writeData() {
guard let discoveredPeripheral = discoveredPeripheral, let transferCharacteristic = transferCharacteristic
else {
return
}
while writeIterationsComplete < defaultIterations && discoveredPeripheral.canSendWriteWithoutResponse {
writeIterationsComplete += 1
}
if writeIterationsComplete == defaultIterations {
discoveredPeripheral.setNotifyValue(false, for: transferCharacteristic)
}
}
}
extension CentralViewModel: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
print("Power on")
startScanningForPeripherals()
return
case .poweredOff :
print("Power off")
return
case .resetting:
print("Resetting")
return
case .unauthorized:
print("Unauthorized")
return
case .unknown:
print("Unknown")
return
case .unsupported:
print("Unsupported")
return
@unknown default:
print("An unknown central manager state has occurred")
return
}
}
func startScanningForPeripherals() {
self.centralManager.scanForPeripherals(withServices: [self.serviceUUID], options: nil)
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
guard RSSI.intValue >= -50 else {
return
}
if discoveredPeripheral != peripheral {
print("Peripheral discovered")
discoveredPeripheral = peripheral
centralManager.connect(peripheral, options: nil)
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices([serviceUUID])
print("Service discovered")
}
}
extension CentralViewModel: CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if error != nil {
cleanup()
return
}
guard let peripheralServices = peripheral.services else {
return
}
for service in peripheralServices {
peripheral.discoverCharacteristics([characteristicUUID], for: service)
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let error = error {
print("Error discovering characteristics: \(error.localizedDescription)")
cleanup()
return
}
guard let serviceCharacteristics = service.characteristics else {
return
}
for characteristic in serviceCharacteristics where characteristic.uuid == characteristicUUID {
transferCharacteristic = characteristic
peripheral.setNotifyValue(true, for: characteristic)
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
if let error = error {
print("Error changing notification state: \(error.localizedDescription)")
return
}
guard characteristic.uuid == characteristicUUID else {
return
}
if characteristic.isNotifying {
print("Notification began on \(characteristic)")
} else {
print("Notification stopped on \(characteristic). Disconnecting")
cleanup()
}
}
func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {
print("Peripheral is ready to send data to YOU!")
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if let error = error {
print("Error discovering characteristics: \(error.localizedDescription)")
cleanup()
return
}
guard let characteristicData = characteristic.value,
let stringFromData = String(data: characteristicData, encoding: .utf8) else {
return
}
print("Received \(characteristicData.count) bytes: \(stringFromData)")
if stringFromData == "EOM" {
message = String(data: data, encoding: .utf8) ?? ""
writeData()
} else {
data.append(characteristicData)
}
}
}