Prettify main view, add temperature history
This commit is contained in:
@@ -55,7 +55,9 @@ final class BluetoothClient: ObservableObject {
|
||||
private var runningTransfer: TemperatureDataTransfer?
|
||||
|
||||
func updateDeviceInfo() {
|
||||
addRequest(.getInfo)
|
||||
if case .configured = deviceState {
|
||||
addRequest(.getInfo)
|
||||
}
|
||||
}
|
||||
|
||||
private var dataUpdateTimer: Timer?
|
||||
|
228
TempTrack/Bluetooth/DeviceManager.swift
Normal file
228
TempTrack/Bluetooth/DeviceManager.swift
Normal file
@@ -0,0 +1,228 @@
|
||||
import Foundation
|
||||
import CoreBluetooth
|
||||
|
||||
final class DeviceManager: NSObject, CBCentralManagerDelegate {
|
||||
|
||||
static let serviceUUID = CBUUID(string: "22071991-cccc-cccc-cccc-000000000001")
|
||||
|
||||
static let characteristicUUID = CBUUID(string: "22071991-cccc-cccc-cccc-000000000002")
|
||||
|
||||
private var manager: CBCentralManager! = nil
|
||||
|
||||
private(set) var lastRSSI: Int = 0
|
||||
|
||||
weak var delegate: DeviceManagerDelegate?
|
||||
|
||||
var state: DeviceState = .disconnected {
|
||||
didSet {
|
||||
delegate?.deviceManager(didChangeState: state)
|
||||
}
|
||||
}
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
self.manager = CBCentralManager(delegate: self, queue: nil)
|
||||
}
|
||||
|
||||
|
||||
private var dataUpdateTimer: Timer?
|
||||
|
||||
@discardableResult
|
||||
func connect() -> Bool {
|
||||
switch state {
|
||||
case .bluetoothDisabled:
|
||||
print("Can't connect, bluetooth disabled")
|
||||
return false
|
||||
case .disconnected, .bluetoothEnabled:
|
||||
break
|
||||
default:
|
||||
return true
|
||||
}
|
||||
guard !manager.isScanning else {
|
||||
state = .scanning
|
||||
return true
|
||||
}
|
||||
shouldConnectIfPossible = true
|
||||
state = .scanning
|
||||
manager.scanForPeripherals(withServices: [DeviceManager.serviceUUID])
|
||||
return true
|
||||
}
|
||||
|
||||
private var shouldConnectIfPossible = true
|
||||
|
||||
func disconnect() {
|
||||
shouldConnectIfPossible = false
|
||||
switch state {
|
||||
case .bluetoothDisabled, .bluetoothEnabled:
|
||||
return
|
||||
case .scanning:
|
||||
manager.stopScan()
|
||||
state = .disconnected
|
||||
return
|
||||
case .connecting(let device),
|
||||
.discoveringCharacteristic(let device),
|
||||
.discoveringServices(device: let device),
|
||||
.configured(let device, _):
|
||||
manager.cancelPeripheralConnection(device)
|
||||
manager.stopScan()
|
||||
state = .disconnected
|
||||
return
|
||||
case .disconnected:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func send(_ data: Data) -> Bool {
|
||||
guard case .configured(let device, let characteristic) = state else {
|
||||
return false
|
||||
}
|
||||
device.writeValue(data, for: characteristic, type: .withResponse)
|
||||
//DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { }
|
||||
return self.read()
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func read() -> Bool {
|
||||
guard case .configured(let device, let characteristic) = state else {
|
||||
return false
|
||||
}
|
||||
device.readValue(for: characteristic)
|
||||
return true
|
||||
}
|
||||
|
||||
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
|
||||
//print("Found device '\(peripheral.name ?? "NO_NAME")'")
|
||||
peripheral.delegate = self
|
||||
manager.connect(peripheral)
|
||||
manager.stopScan()
|
||||
state = .connecting(device: peripheral)
|
||||
}
|
||||
|
||||
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
||||
switch central.state {
|
||||
case .poweredOff:
|
||||
state = .bluetoothDisabled
|
||||
case .poweredOn:
|
||||
state = .bluetoothEnabled
|
||||
connect()
|
||||
case .unsupported:
|
||||
state = .bluetoothDisabled
|
||||
print("Bluetooth is not supported")
|
||||
case .unknown:
|
||||
state = .bluetoothDisabled
|
||||
print("Bluetooth state is unknown")
|
||||
case .resetting:
|
||||
state = .bluetoothDisabled
|
||||
print("Bluetooth is resetting")
|
||||
case .unauthorized:
|
||||
state = .bluetoothDisabled
|
||||
print("Bluetooth is not authorized")
|
||||
@unknown default:
|
||||
state = .bluetoothDisabled
|
||||
print("Unknown state \(central.state)")
|
||||
}
|
||||
}
|
||||
|
||||
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
||||
//print("Connected to " + peripheral.name!)
|
||||
peripheral.discoverServices([DeviceManager.serviceUUID])
|
||||
state = .discoveringServices(device: peripheral)
|
||||
}
|
||||
|
||||
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
|
||||
print("Disconnected from " + peripheral.name!)
|
||||
state = .disconnected
|
||||
// Attempt to reconnect
|
||||
if shouldConnectIfPossible {
|
||||
connect()
|
||||
}
|
||||
}
|
||||
|
||||
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
||||
print("Failed to connect device '\(peripheral.name ?? "NO_NAME")'")
|
||||
if let error = error {
|
||||
print(error)
|
||||
}
|
||||
state = manager.isScanning ? .scanning : .disconnected
|
||||
// Attempt to reconnect
|
||||
if shouldConnectIfPossible {
|
||||
connect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DeviceManager: CBPeripheralDelegate {
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
|
||||
guard let services = peripheral.services, !services.isEmpty else {
|
||||
print("No services found for device '\(peripheral.name ?? "NO_NAME")'")
|
||||
manager.cancelPeripheralConnection(peripheral)
|
||||
return
|
||||
}
|
||||
guard let service = services.first(where: { $0.uuid.uuidString == DeviceManager.serviceUUID.uuidString }) else {
|
||||
print("Required service not found for '\(peripheral.name ?? "NO_NAME")': \(services.map { $0.uuid.uuidString})")
|
||||
manager.cancelPeripheralConnection(peripheral)
|
||||
return
|
||||
}
|
||||
peripheral.discoverCharacteristics([DeviceManager.characteristicUUID], for: service)
|
||||
state = .discoveringCharacteristic(device: peripheral)
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
||||
if let error = error {
|
||||
print("Failed to discover characteristics: \(error)")
|
||||
manager.cancelPeripheralConnection(peripheral)
|
||||
return
|
||||
}
|
||||
guard let characteristics = service.characteristics, !characteristics.isEmpty else {
|
||||
print("No characteristics found for device")
|
||||
manager.cancelPeripheralConnection(peripheral)
|
||||
return
|
||||
}
|
||||
for characteristic in characteristics {
|
||||
guard characteristic.uuid == DeviceManager.characteristicUUID else {
|
||||
print("Unused characteristic \(characteristic.uuid.uuidString)")
|
||||
continue
|
||||
}
|
||||
state = .configured(device: peripheral, characteristic: characteristic)
|
||||
peripheral.setNotifyValue(true, for: characteristic)
|
||||
}
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
|
||||
if let error = error {
|
||||
print("Peripheral failed to write value for \(characteristic.uuid.uuidString): \(error)")
|
||||
}
|
||||
//print("Peripheral did write value for \(characteristic.uuid.uuidString)")
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
|
||||
if let error = error {
|
||||
print("Failed to get RSSI: \(error)")
|
||||
return
|
||||
}
|
||||
lastRSSI = RSSI.intValue
|
||||
print("RSSI: \(lastRSSI)")
|
||||
}
|
||||
|
||||
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
|
||||
if let error = error {
|
||||
print("Failed to read value update: \(error)")
|
||||
return
|
||||
}
|
||||
guard case .configured(device: _, characteristic: let storedCharacteristic) = state else {
|
||||
print("Received data while not properly configured")
|
||||
return
|
||||
}
|
||||
guard characteristic.uuid == storedCharacteristic.uuid else {
|
||||
print("Read unknown characteristic \(characteristic.uuid.uuidString)")
|
||||
return
|
||||
}
|
||||
guard let data = characteristic.value else {
|
||||
print("No data")
|
||||
return
|
||||
}
|
||||
delegate?.deviceManager(didReceive: data)
|
||||
}
|
||||
}
|
8
TempTrack/Bluetooth/DeviceManagerDelegate.swift
Normal file
8
TempTrack/Bluetooth/DeviceManagerDelegate.swift
Normal file
@@ -0,0 +1,8 @@
|
||||
import Foundation
|
||||
|
||||
protocol DeviceManagerDelegate: AnyObject {
|
||||
|
||||
func deviceManager(didReceive data: Data)
|
||||
|
||||
func deviceManager(didChangeState state: DeviceState)
|
||||
}
|
82
TempTrack/Bluetooth/DeviceState.swift
Normal file
82
TempTrack/Bluetooth/DeviceState.swift
Normal file
@@ -0,0 +1,82 @@
|
||||
import Foundation
|
||||
import CoreBluetooth
|
||||
|
||||
enum DeviceState {
|
||||
|
||||
case bluetoothDisabled
|
||||
|
||||
case bluetoothEnabled
|
||||
|
||||
case scanning
|
||||
|
||||
case connecting(device: CBPeripheral)
|
||||
|
||||
case discoveringServices(device: CBPeripheral)
|
||||
|
||||
case discoveringCharacteristic(device: CBPeripheral)
|
||||
|
||||
case configured(device: CBPeripheral, characteristic: CBCharacteristic)
|
||||
|
||||
case disconnected
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
case .bluetoothDisabled:
|
||||
return "Bluetooth is disabled"
|
||||
case .bluetoothEnabled:
|
||||
return "Bluetooth enabled"
|
||||
case .scanning:
|
||||
return "Scanning for devices..."
|
||||
case .connecting(let device):
|
||||
guard let name = device.name else {
|
||||
return "Connecting to device..."
|
||||
}
|
||||
return "Connecting to \(name)..."
|
||||
case .discoveringServices(let device):
|
||||
guard let name = device.name else {
|
||||
return "Setting up device..."
|
||||
}
|
||||
return "Setting up \(name)..."
|
||||
case .discoveringCharacteristic(let device):
|
||||
guard let name = device.name else {
|
||||
return "Setting up device..."
|
||||
}
|
||||
return "Setting up \(name)..."
|
||||
case .configured(let device, _):
|
||||
guard let name = device.name else {
|
||||
return "Connected"
|
||||
}
|
||||
return "Connected to \(name)"
|
||||
case .disconnected:
|
||||
return "Not connected"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DeviceState: CustomStringConvertible {
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .bluetoothDisabled:
|
||||
return "Bluetooth disabled"
|
||||
case .bluetoothEnabled:
|
||||
return "Bluetooth enabled"
|
||||
case .scanning:
|
||||
return "Searching for device"
|
||||
case .connecting:
|
||||
return "Connecting to device"
|
||||
case .discoveringServices:
|
||||
return "Discovering services"
|
||||
case .discoveringCharacteristic:
|
||||
return "Discovering characteristics"
|
||||
case .configured:
|
||||
return "Connected"
|
||||
case .disconnected:
|
||||
return "Disconnected"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DeviceState: Equatable {
|
||||
|
||||
}
|
Reference in New Issue
Block a user