diff --git a/Sources/SwiftyTeeth/ConnectionState.swift b/Sources/SwiftyTeeth/ConnectionState.swift new file mode 100644 index 0000000..a3eeba0 --- /dev/null +++ b/Sources/SwiftyTeeth/ConnectionState.swift @@ -0,0 +1,15 @@ +// +// ConnectionState.swift +// SwiftyTeeth +// +// Created by SJ on 2020-05-18. +// + +import Foundation + +public enum ConnectionState { + case connecting + case connected + case disconnecting + case disconnected +} diff --git a/Sources/SwiftyTeeth/Models/Device.swift b/Sources/SwiftyTeeth/Device.swift similarity index 92% rename from Sources/SwiftyTeeth/Models/Device.swift rename to Sources/SwiftyTeeth/Device.swift index 8341e6f..b281b27 100644 --- a/Sources/SwiftyTeeth/Models/Device.swift +++ b/Sources/SwiftyTeeth/Device.swift @@ -10,12 +10,8 @@ import Foundation import CoreBluetooth public typealias DiscoveredCharacteristic = (service: Service, characteristics: [Characteristic]) - -public typealias ConnectionHandler = ((Bool) -> Void) public typealias ServiceDiscovery = ((Result<[Service], Error>) -> Void) public typealias CharacteristicDiscovery = ((Result) -> Void) -//public typealias ReadHandler = ((Result) -> Void) -//public typealias WriteHandler = ((Result) -> Void) public enum ConnectionError: Error { case disconnected @@ -30,12 +26,18 @@ open class Device: NSObject { fileprivate let tag = "SwiftyDevice" let peripheral: CBPeripheral - var discoveredServices = [UUID: Service]() private let manager: SwiftyTeeth - private var connectionHandler: ConnectionHandler? + public var connectionStateChangedHandler: ((ConnectionState) -> Void)? { + didSet { + // TODO: Replace this with an observing property on peripheral.state + connectionStateChangedHandler?(connectionState) + } + } + + private var connectionHandler: ((ConnectionState) -> Void)? private var notificationHandler = [CBCharacteristic: ((Result) -> Void)]() // Connection parameters @@ -60,8 +62,23 @@ open class Device: NSObject { // MARK: Computed properties extension Device { + open var connectionState: ConnectionState { + switch peripheral.state { + case .connecting: + return .connecting + case .connected: + return .connected + case .disconnecting: + return .disconnecting + case .disconnected: + return .disconnected + @unknown default: + return .disconnected + } + } + open var isConnected: Bool { - return peripheral.state == .connected + return connectionState == .connected } open var name: String { @@ -72,11 +89,6 @@ extension Device { return peripheral.identifier.uuidString } - // open var connectionState: StateEnumOfSomeSort { - // return peripheral.state - // } - - // open var rssi: Int { // return peripheral. // } @@ -86,8 +98,9 @@ extension Device { extension Device { // Annoyingly, iOS has the connection functionality sitting on the central manager, instead of on the peripheral // TODO: Should the completion be optional? - open func connect(with timeout: TimeInterval? = nil, autoReconnect: Bool = true, complete: ConnectionHandler?) { + open func connect(with timeout: TimeInterval? = nil, autoReconnect: Bool = true, complete: ((ConnectionState) -> Void)?) { Log(v: "Calling connect", tag: tag) + connectionStateChangedHandler?(.connecting) self.connectionHandler = complete self.autoReconnect = autoReconnect self.manager.connect(to: self) @@ -97,6 +110,7 @@ extension Device { open func disconnect(autoReconnect: Bool = false) { // Disable auto reconnection when calling the disconnect API Log(v: "Calling disconnect", tag: tag) + connectionStateChangedHandler?(.disconnecting) self.autoReconnect = autoReconnect self.manager.disconnect(from: self) } @@ -272,12 +286,14 @@ internal extension Device { func didConnect() { Log(v: "didConnect: Calling connection handler: Is handler nil? \(connectionHandler == nil)", tag: tag) - connectionHandler?(true) + connectionHandler?(.connected) + connectionStateChangedHandler?(.connected) } func didDisconnect() { Log(v: "didDisconnect: Calling disconnection handler: Is handler nil? \(connectionHandler == nil)", tag: tag) - connectionHandler?(false) + connectionHandler?(.disconnected) + connectionStateChangedHandler?(.disconnected) if autoReconnect == true { connect(complete: connectionHandler) } diff --git a/Sources/SwiftyTeeth/Extensions/OperationQueue.swift b/Sources/SwiftyTeeth/Extensions/OperationQueue+SwiftyQueue.swift similarity index 100% rename from Sources/SwiftyTeeth/Extensions/OperationQueue.swift rename to Sources/SwiftyTeeth/Extensions/OperationQueue+SwiftyQueue.swift diff --git a/Sources/SwiftyTeeth/Protocols/SwiftyQueue.swift b/Sources/SwiftyTeeth/Internal/SwiftyQueue.swift similarity index 100% rename from Sources/SwiftyTeeth/Protocols/SwiftyQueue.swift rename to Sources/SwiftyTeeth/Internal/SwiftyQueue.swift diff --git a/Sources/SwiftyTeeth/Models/QueueItem.swift b/Sources/SwiftyTeeth/QueueItem.swift similarity index 100% rename from Sources/SwiftyTeeth/Models/QueueItem.swift rename to Sources/SwiftyTeeth/QueueItem.swift diff --git a/Sources/SwiftyTeeth/Protocols/SwiftyTeethable.swift b/Sources/SwiftyTeeth/SwiftyTeethable.swift similarity index 100% rename from Sources/SwiftyTeeth/Protocols/SwiftyTeethable.swift rename to Sources/SwiftyTeeth/SwiftyTeethable.swift diff --git a/SwiftyTeeth Sample/UI/PeripheralView.swift b/SwiftyTeeth Sample/UI/PeripheralView.swift index d0dbe68..42c2dcc 100644 --- a/SwiftyTeeth Sample/UI/PeripheralView.swift +++ b/SwiftyTeeth Sample/UI/PeripheralView.swift @@ -18,6 +18,9 @@ final class PeripheralViewModel: ObservableObject { init(peripheral: Device) { self.peripheral = peripheral + peripheral.connectionStateChangedHandler = { state in + self.log("Connection state is \(state)") + } } private func log(_ text: String) { @@ -28,13 +31,13 @@ final class PeripheralViewModel: ObservableObject { } func connect() { - peripheral.connect { (isConnected) in - guard isConnected == true else { + peripheral.connect { (connectionState) in + guard connectionState == .connected else { self.log("Not connected") return } - self.log("App: Device is connected? \(isConnected)") + self.log("App: Device is connected? \(connectionState == .connected)") self.log("App: Starting service discovery...") self.peripheral.discoverServices { (result) in diff --git a/SwiftyTeeth.xcodeproj/project.pbxproj b/SwiftyTeeth.xcodeproj/project.pbxproj index 1595a6d..d068408 100644 --- a/SwiftyTeeth.xcodeproj/project.pbxproj +++ b/SwiftyTeeth.xcodeproj/project.pbxproj @@ -12,7 +12,7 @@ 1952CBAA238A453E00EBBCD3 /* SwiftyToothLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1952CBA8238A451700EBBCD3 /* SwiftyToothLogger.swift */; }; 195A59141E4834F0002B076A /* CBCharacterisic+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195A59121E482C4F002B076A /* CBCharacterisic+String.swift */; }; 195A59151E4834F5002B076A /* CBService+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 195A59101E482BF4002B076A /* CBService+String.swift */; }; - 19A06869206B130F0023D5EE /* OperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A06868206B130F0023D5EE /* OperationQueue.swift */; }; + 19A06869206B130F0023D5EE /* OperationQueue+SwiftyQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A06868206B130F0023D5EE /* OperationQueue+SwiftyQueue.swift */; }; 19A0686B206B162E0023D5EE /* SwiftyQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A0686A206B162E0023D5EE /* SwiftyQueue.swift */; }; 19A0686D206B18B20023D5EE /* QueueItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A0686C206B18B20023D5EE /* QueueItem.swift */; }; 19B7DC8F1F0D624F0035FDFA /* SwiftyTeethLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B7DC8E1F0D624F0035FDFA /* SwiftyTeethLogger.swift */; }; @@ -30,6 +30,7 @@ 33C3319524735BAB00D90A7A /* Characteristic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33C3318B24735BAB00D90A7A /* Characteristic.swift */; }; 33C3319724735C7400D90A7A /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33C3319624735C7400D90A7A /* Logger.swift */; }; 33C3319824735C7400D90A7A /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33C3319624735C7400D90A7A /* Logger.swift */; }; + 33C3319B247376D400D90A7A /* ConnectionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33C3319A247376D400D90A7A /* ConnectionState.swift */; }; 33FEC8B4242EF0C9001F9BD9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33FEC8B3242EF0C9001F9BD9 /* AppDelegate.swift */; }; 33FEC8B6242EF0C9001F9BD9 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33FEC8B5242EF0C9001F9BD9 /* SceneDelegate.swift */; }; 33FEC8B8242EF0C9001F9BD9 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33FEC8B7242EF0C9001F9BD9 /* ContentView.swift */; }; @@ -89,7 +90,7 @@ 195A59101E482BF4002B076A /* CBService+String.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CBService+String.swift"; sourceTree = ""; }; 195A59121E482C4F002B076A /* CBCharacterisic+String.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CBCharacterisic+String.swift"; sourceTree = ""; }; 1966DC73238A29390085C9A5 /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; - 19A06868206B130F0023D5EE /* OperationQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationQueue.swift; sourceTree = ""; }; + 19A06868206B130F0023D5EE /* OperationQueue+SwiftyQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OperationQueue+SwiftyQueue.swift"; sourceTree = ""; }; 19A0686A206B162E0023D5EE /* SwiftyQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyQueue.swift; sourceTree = ""; }; 19A0686C206B18B20023D5EE /* QueueItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueItem.swift; sourceTree = ""; }; 19B7DC8E1F0D624F0035FDFA /* SwiftyTeethLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyTeethLogger.swift; sourceTree = ""; }; @@ -101,6 +102,7 @@ 33C3318A24735BAB00D90A7A /* UUID+CBUUID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UUID+CBUUID.swift"; sourceTree = ""; }; 33C3318B24735BAB00D90A7A /* Characteristic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Characteristic.swift; sourceTree = ""; }; 33C3319624735C7400D90A7A /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + 33C3319A247376D400D90A7A /* ConnectionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionState.swift; sourceTree = ""; }; 33C34994242C2856006C7383 /* Peripheral */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Peripheral; path = Examples/Peripheral; sourceTree = ""; }; 33FEC8AC242EEE54001F9BD9 /* Central */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Central; path = Examples/Central; sourceTree = ""; }; 33FEC8B1242EF0C9001F9BD9 /* SwiftyTeeth Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SwiftyTeeth Sample.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -168,13 +170,16 @@ 1952CB96238A3E6B00EBBCD3 /* SwiftyTeeth */ = { isa = PBXGroup; children = ( + 33C33199247376A100D90A7A /* Internal */, 8B64D1971DC2E39900D41A2C /* Extensions */, - 8B64D19A1DC2E39900D41A2C /* Models */, - 8B64D19C1DC2E39900D41A2C /* Protocols */, 8B64D1CF1DC2E8FE00D41A2C /* SwiftyTeeth.swift */, + 19A0686C206B18B20023D5EE /* QueueItem.swift */, + 8B64D1DC1DC2F5D000D41A2C /* Device.swift */, + 8B64D1E21DC2FCEB00D41A2C /* SwiftyTeethable.swift */, 19B7DC8E1F0D624F0035FDFA /* SwiftyTeethLogger.swift */, 8B64D18D1DC2E16300D41A2C /* SwiftyTeeth.h */, 8B64D1A11DC2E42000D41A2C /* Info.plist */, + 33C3319A247376D400D90A7A /* ConnectionState.swift */, ); path = SwiftyTeeth; sourceTree = ""; @@ -219,6 +224,14 @@ path = Extensions; sourceTree = ""; }; + 33C33199247376A100D90A7A /* Internal */ = { + isa = PBXGroup; + children = ( + 19A0686A206B162E0023D5EE /* SwiftyQueue.swift */, + ); + path = Internal; + sourceTree = ""; + }; 33FEC8B2242EF0C9001F9BD9 /* SwiftyTeeth Sample */ = { isa = PBXGroup; children = ( @@ -316,31 +329,13 @@ 8B64D1971DC2E39900D41A2C /* Extensions */ = { isa = PBXGroup; children = ( - 195A59101E482BF4002B076A /* CBService+String.swift */, 195A59121E482C4F002B076A /* CBCharacterisic+String.swift */, - 19A06868206B130F0023D5EE /* OperationQueue.swift */, + 195A59101E482BF4002B076A /* CBService+String.swift */, + 19A06868206B130F0023D5EE /* OperationQueue+SwiftyQueue.swift */, ); path = Extensions; sourceTree = ""; }; - 8B64D19A1DC2E39900D41A2C /* Models */ = { - isa = PBXGroup; - children = ( - 8B64D1DC1DC2F5D000D41A2C /* Device.swift */, - 19A0686C206B18B20023D5EE /* QueueItem.swift */, - ); - path = Models; - sourceTree = ""; - }; - 8B64D19C1DC2E39900D41A2C /* Protocols */ = { - isa = PBXGroup; - children = ( - 8B64D1E21DC2FCEB00D41A2C /* SwiftyTeethable.swift */, - 19A0686A206B162E0023D5EE /* SwiftyQueue.swift */, - ); - path = Protocols; - sourceTree = ""; - }; 8B64D1A01DC2E3A100D41A2C /* Meta */ = { isa = PBXGroup; children = ( @@ -576,6 +571,7 @@ buildActionMask = 2147483647; files = ( 195A59141E4834F0002B076A /* CBCharacterisic+String.swift in Sources */, + 33C3319B247376D400D90A7A /* ConnectionState.swift in Sources */, 33C3318E24735BAB00D90A7A /* Service.swift in Sources */, 19A0686B206B162E0023D5EE /* SwiftyQueue.swift in Sources */, 195A59151E4834F5002B076A /* CBService+String.swift in Sources */, @@ -589,7 +585,7 @@ 8B64D1DD1DC2F5D000D41A2C /* Device.swift in Sources */, 19A0686D206B18B20023D5EE /* QueueItem.swift in Sources */, 33C3319424735BAB00D90A7A /* Characteristic.swift in Sources */, - 19A06869206B130F0023D5EE /* OperationQueue.swift in Sources */, + 19A06869206B130F0023D5EE /* OperationQueue+SwiftyQueue.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };