Skip to content

Commit

Permalink
Feature/43-string-to-UUID - Replace strings with UUIDs (#46)
Browse files Browse the repository at this point in the history
- Refactored out common functionality for SwiftyTeeth/Tooth
- Swapped out strings with UUIDs
- Removed CoreBluetooth from the API (needs more testing)
- Needed to do a really crappy symlink to support Swift package manager's single directory Sources when sharing common code
  • Loading branch information
sureshjoshi authored May 19, 2020
1 parent 9ee9ad3 commit 06dfdc7
Show file tree
Hide file tree
Showing 18 changed files with 189 additions and 244 deletions.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
import CoreBluetooth
import Foundation

public typealias ReadHandler = ((Result<Data?, Error>) -> Void) -> Void
public typealias WriteHandler = (Data?, (Result<Void, Error>) -> Void) -> Void
public typealias WriteNoResponseHandler = (Data?) -> Void
public typealias NotifyHandler = (Result<Data, Error>) -> Void

// Maybe put callback in this enum?
public enum Property {
case read(onRead: ReadHandler)
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
import CoreBluetooth
import Foundation

//extension CBUUID {
// init?(uuid: UUID) {
// self.init
// }
//}

extension UUID {
init?(cbuuid: CBUUID) {
self.init(uuidString: cbuuid.uuidString)
Expand Down
14 changes: 14 additions & 0 deletions Sources/Common/Logger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// Logger.swift
// SwiftyTeeth
//
// Created by SJ on 2020-05-18.
//

public protocol Logger {
func verbose(_ message: String)
func debug(_ message: String)
func info(_ message: String)
func warning(_ message: String)
func error(_ message: String)
}
File renamed without changes.
1 change: 1 addition & 0 deletions Sources/SwiftyTeeth/Common
65 changes: 0 additions & 65 deletions Sources/SwiftyTeeth/Enums/Result.swift

This file was deleted.

12 changes: 0 additions & 12 deletions Sources/SwiftyTeeth/Extensions/CBPeripheral.swift

This file was deleted.

16 changes: 0 additions & 16 deletions Sources/SwiftyTeeth/Models/BluetoothState.swift

This file was deleted.

132 changes: 75 additions & 57 deletions Sources/SwiftyTeeth/Models/Device.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
import Foundation
import CoreBluetooth

public typealias DiscoveredCharacteristic = (service: CBService, characteristics: [CBCharacteristic])
public typealias DiscoveredCharacteristic = (service: Service, characteristics: [Characteristic])

public typealias ConnectionHandler = ((Bool) -> Void)
public typealias ServiceDiscovery = ((Result<[CBService]>) -> Void)
public typealias CharacteristicDiscovery = ((Result<DiscoveredCharacteristic>) -> Void)
public typealias ReadHandler = ((Result<Data>) -> Void)
public typealias WriteHandler = ((Result<Void>) -> Void)
public typealias ServiceDiscovery = ((Result<[Service], Error>) -> Void)
public typealias CharacteristicDiscovery = ((Result<DiscoveredCharacteristic, Error>) -> Void)
//public typealias ReadHandler = ((Result<Data>) -> Void)
//public typealias WriteHandler = ((Result<Void>) -> Void)

public enum ConnectionError: Error {
case disconnected
Expand All @@ -30,14 +30,13 @@ open class Device: NSObject {
fileprivate let tag = "SwiftyDevice"

let peripheral: CBPeripheral

var discoveredServices = [UUID: Service]()

// TODO: Maybe just make this a String of Strings?
open var discoveredServices = [CBService: [CBCharacteristic]]()

fileprivate let manager: SwiftyTeeth
private let manager: SwiftyTeeth

fileprivate var connectionHandler: ConnectionHandler?
fileprivate var notificationHandler = [CBCharacteristic: ReadHandler]()
private var connectionHandler: ConnectionHandler?
private var notificationHandler = [CBCharacteristic: ((Result<Data, Error>) -> Void)]()

// Connection parameters
fileprivate var autoReconnect = false
Expand Down Expand Up @@ -108,8 +107,8 @@ extension Device {
extension Device {

// TODO: Make CBUUID into strings
open func discoverServices(with uuids: [CBUUID]? = nil, complete: ServiceDiscovery?) {
let item = QueueItem<[CBService]>(
open func discoverServices(with uuids: [UUID]? = nil, complete: ServiceDiscovery?) {
let item = QueueItem<[Service]>(
name: "discoverServices", // TODO: Need better than a hardcoded string
execution: { (cb) in
guard self.isConnected == true else {
Expand All @@ -118,7 +117,8 @@ extension Device {
return
}
Log(v: "discoverServices: \(self.peripheral) \n \(String(describing: self.peripheral.delegate))", tag: self.tag)
self.peripheral.discoverServices(uuids)
let cbuuids = uuids == nil ? nil : uuids!.map { CBUUID(nsuuid: $0) }
self.peripheral.discoverServices(cbuuids)
},
callback: { (result, done) in
complete?(result)
Expand All @@ -127,10 +127,8 @@ extension Device {

queue.pushBack(item)
}

// TODO: Make CBUUID into strings
// TODO: Make service a UUID?
open func discoverCharacteristics(with uuids: [CBUUID]? = nil, for service: CBService, complete: CharacteristicDiscovery?) {

open func discoverCharacteristics(with uuids: [UUID]? = nil, for service: Service, complete: CharacteristicDiscovery?) {
let item = QueueItem<DiscoveredCharacteristic>(
name: service.uuid.uuidString,
execution: { (cb) in
Expand All @@ -139,8 +137,16 @@ extension Device {
cb(.failure(ConnectionError.disconnected))
return
}

guard let cbService = self.peripheral.services?.find(uuidString: service.uuid.uuidString) else {
Log(w: "Service not found on peripheral - cannot discoverCharacteristics", tag: self.tag)
cb(.failure(ConnectionError.disconnected)) // TODO: Replace this error
return
}

Log(v: "discoverCharacteristics", tag: self.tag)
self.peripheral.discoverCharacteristics(uuids, for: service)
let cbuuids = uuids == nil ? nil : uuids!.map { CBUUID(nsuuid: $0) }
self.peripheral.discoverCharacteristics(cbuuids, for: cbService)
},
callback: { (result, done) in
complete?(result)
Expand All @@ -150,9 +156,9 @@ extension Device {
queue.pushBack(item)
}

open func read(from characteristic: String, in service: String, complete: ReadHandler?) {
guard let targetService = peripheral.services?.find(uuidString: service),
let targetCharacteristic = targetService.characteristics?.find(uuidString: characteristic) else {
open func read(from characteristic: UUID, in service: UUID, complete: ((Result<Data, Error>) -> Void)?) {
guard let targetService = peripheral.services?.find(uuidString: service.uuidString),
let targetCharacteristic = targetService.characteristics?.find(uuidString: characteristic.uuidString) else {
return
}

Expand All @@ -174,9 +180,9 @@ extension Device {
queue.pushBack(item)
}

open func write(data: Data, to characteristic: String, in service: String, type: CBCharacteristicWriteType = .withResponse, complete: WriteHandler? = nil) {
guard let targetService = peripheral.services?.find(uuidString: service),
let targetCharacteristic = targetService.characteristics?.find(uuidString: characteristic) else {
open func write(data: Data, to characteristic: UUID, in service: UUID, type: CBCharacteristicWriteType = .withResponse, complete: ((Result<Void, Error>) -> Void)? = nil) {
guard let targetService = peripheral.services?.find(uuidString: service.uuidString),
let targetCharacteristic = targetService.characteristics?.find(uuidString: characteristic.uuidString) else {
return
}

Expand All @@ -197,9 +203,9 @@ extension Device {
}

// TODO: Adding some pre-conditions libraries/toolkits could streamline the initial clutter
open func subscribe(to characteristic: String, in service: String, complete: ReadHandler?) {
guard let targetService = peripheral.services?.find(uuidString: service),
let targetCharacteristic = targetService.characteristics?.find(uuidString: characteristic) else {
open func subscribe(to characteristic: UUID, in service: UUID, complete: ((Result<Data, Error>) -> Void)?) {
guard let targetService = peripheral.services?.find(uuidString: service.uuidString),
let targetCharacteristic = targetService.characteristics?.find(uuidString: characteristic.uuidString) else {
return
}

Expand All @@ -219,9 +225,9 @@ extension Device {
}

// TODO: Faster probably to just iterate through the notification handler instead of current method
open func unsubscribe(from characteristic: String, in service: String) {
guard let targetService = peripheral.services?.find(uuidString: service),
let targetCharacteristic = targetService.characteristics?.find(uuidString: characteristic) else {
open func unsubscribe(from characteristic: UUID, in service: UUID) {
guard let targetService = peripheral.services?.find(uuidString: service.uuidString),
let targetCharacteristic = targetService.characteristics?.find(uuidString: characteristic.uuidString) else {
return
}

Expand Down Expand Up @@ -300,40 +306,52 @@ internal extension Device {
}

func didDiscoverServices(error: Error?) {
discoveredServices.removeAll()

peripheral.services?.forEach({ service in
Log(v: "Service Discovered: \(service.uuid.uuidString)", tag: tag)
discoveredServices[service] = [CBCharacteristic]()
})

var result: Result<[CBService]> = .success(Array(discoveredServices.keys))
if let e = error {
result = .failure(e)
// discoveredServices.removeAll()

let services = peripheral.services?.compactMap { (cbService) -> Service? in
Log(v: "Service Discovered: \(cbService.uuid.uuidString)", tag: tag)
guard let uuid = UUID(cbuuid: cbService.uuid) else {
return nil
}
return Service(uuid: uuid, characteristics: [])
} ?? []

discoveredServices = Dictionary(uniqueKeysWithValues: zip(services.map {$0.uuid}, services))

var result: Result<[Service], Error> = .success(Array(discoveredServices.values))
if let error = error {
result = .failure(error)
}

let item = queue.items.first { (operation) -> Bool in
operation.isExecuting && operation.name == "discoverServices"
} as? QueueItem<[CBService]>
} as? QueueItem<[Service]>
item?.notify(result)
}

func didDiscoverIncludedServicesFor(service: CBService, error: Error?) {
}

func didDiscoverCharacteristicsFor(service: CBService, error: Error?) {
discoveredServices[service]?.removeAll()

var characteristics = [CBCharacteristic]()
service.characteristics?.forEach({ characteristic in
Log(v: "Characteristic Discovered: \(characteristic.uuid.uuidString)", tag: tag)
characteristics.append(characteristic)
})

discoveredServices[service]? = characteristics
var result: Result<DiscoveredCharacteristic> = .success((service: service, characteristics: characteristics))
if let e = error {
result = .failure(e)
guard let uuid = UUID(cbuuid: service.uuid) else {
// TODO: What to do? Maybe a defer?
return
}

let characteristics = service.characteristics?.compactMap { cbCharacteristic -> Characteristic? in
Log(v: "Characteristic Discovered: \(cbCharacteristic.uuid.uuidString)", tag: tag)
guard let uuid = UUID(cbuuid: cbCharacteristic.uuid) else {
return nil
}
return Characteristic(uuid: uuid, properties: [])
} ?? []

let service = Service(uuid: uuid, characteristics: characteristics)
discoveredServices[uuid] = service

var result: Result<DiscoveredCharacteristic, Error> = .success((service: service, characteristics: characteristics))
if let error = error {
result = .failure(error)
}

let item = queue.items.first { (operation) -> Bool in
Expand All @@ -345,7 +363,7 @@ internal extension Device {
func didUpdateValueFor(characteristic: CBCharacteristic, error: Error?) {
Log(v: "didUpdateValueFor: \(characteristic.uuid.uuidString) with: \(String(describing: characteristic.value))", tag: tag)

var result: Result<Data> = .success(characteristic.value ?? Data())
var result: Result<Data, Error> = .success(characteristic.value ?? Data())
if let e = error {
result = .failure(e)
}
Expand All @@ -360,7 +378,7 @@ internal extension Device {
func didWriteValueFor(characteristic: CBCharacteristic, error: Error?) {
Log(v: "didWriteValueFor: \(characteristic.uuid.uuidString)", tag: tag)

var result: Result<Void> = .success(())
var result: Result<Void, Error> = .success(())
if let e = error {
result = .failure(e)
}
Expand All @@ -374,7 +392,7 @@ internal extension Device {
// This is equivalent to a direct READ from the characteristic
func didUpdateNotificationStateFor(characteristic: CBCharacteristic, error: Error?) {
Log(v: "didUpdateNotificationStateFor: \(characteristic.uuid.uuidString)", tag: tag)
var result: Result<Data> = .success(characteristic.value ?? Data())
var result: Result<Data, Error> = .success(characteristic.value ?? Data())
if let e = error {
result = .failure(e)
}
Expand Down
Loading

0 comments on commit 06dfdc7

Please sign in to comment.