CoreBluetooth Central, Observing Multiple Peripherals

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)
		}
	}
}
  • Actually, I'm not so sure if I should use CoreBluetooth for my objective.

Add a Comment