Repository: jordanebelanger/SwiftyBluetooth Branch: master Commit: d15668564829 Files: 22 Total size: 152.3 KB Directory structure: gitextract_tl8xnbwt/ ├── .gitignore ├── LICENSE ├── Package.swift ├── README.md ├── Sources/ │ ├── CBExtensions.swift │ ├── CBUUIDConvertible.swift │ ├── CBUUIDPath.swift │ ├── Central.swift │ ├── CentralProxy.swift │ ├── DescriptorValue.swift │ ├── Info.plist │ ├── Peripheral.swift │ ├── PeripheralProxy.swift │ ├── SBError.swift │ ├── SwiftyBluetooth.h │ ├── SwiftyBluetooth.swift │ └── Util.swift ├── SwiftyBluetooth.podspec └── SwiftyBluetooth.xcodeproj/ ├── project.pbxproj ├── project.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ └── IDEWorkspaceChecks.plist └── xcshareddata/ └── xcschemes/ └── SwiftyBluetooth.xcscheme ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ DerivedData/ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ ## Other *.moved-aside *.xcuserstate ## Obj-C/Swift specific *.hmap *.ipa *.dSYM.zip *.dSYM ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ .build/ .swiftpm/ # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # # Pods/ # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build # fastlane # # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md fastlane/report.xml fastlane/Preview.html fastlane/screenshots fastlane/test_output .DS_Store ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2016 Jordane Belanger Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Package.swift ================================================ // swift-tools-version:5.5 import PackageDescription let package = Package( name: "SwiftyBluetooth", platforms: [ .iOS(.v10), .macOS(.v10_13), .tvOS(.v10) ], products: [ .library(name: "SwiftyBluetooth", targets: ["SwiftyBluetooth"]) ], targets: [ .target(name: "SwiftyBluetooth", path: "Sources", exclude: ["Info.plist"]) ] ) ================================================ FILE: README.md ================================================ # SwiftyBluetooth Closures based APIs for CoreBluetooth. ## Features - Replace the delegate based interface with a closure based interface for every `CBCentralManager` and `CBPeripheral` operation. - Notification based event for CBCentralManager state changes and state restoration. - Notification based event for CBPeripheral name updates, characteristic value updates and services updates. - Precise errors and guaranteed timeout for every Bluetooth operation. - Will automatically connect to a CBPeripheral and attempt to discover the required BLE services and characteristics required for a read or write operation if necessary. ## Usage The Library has 2 important class: - The `Central` class, a Singleton wrapper around `CBCentralManager` used to scan for peripherals with a closure callback and restore previous sessions. - The `Peripheral` class, a wrapper around `CBPeripheral` used to call `CBPeripheral` functions with closure callbacks. Below are a couple examples of operations that might be of interest to you. ### Scanning for Peripherals Scan for peripherals by calling `scanWithTimeout(...)` while passing a `timeout` in seconds and a `callback` closure to receive `Peripheral` result callbacks as well as update on the status of your scan: ```swift // You can pass in nil if you want to discover all Peripherals SwiftyBluetooth.scanForPeripherals(withServiceUUIDs: nil, timeoutAfter: 15) { scanResult in switch scanResult { case .scanStarted: // The scan started meaning CBCentralManager scanForPeripherals(...) was called case .scanResult(let peripheral, let advertisementData, let RSSI): // A peripheral was found, your closure may be called multiple time with a .ScanResult enum case. // You can save that peripheral for future use, or call some of its functions directly in this closure. case .scanStopped(let error): // The scan stopped, an error is passed if the scan stopped unexpectedly } } ``` Note that the callback closure can be called multiple times, but always start and finish with a callback containing a `.scanStarted` and `.scanStopped` result respectively. Your callback will be called with a `.scanResult` for every unique peripheral found during the scan. ### Connecting to a peripheral ```swift peripheral.connect { result in switch result { case .success: break // You are now connected to the peripheral case .failure(let error): break // An error happened while connecting } } ``` ### Reading from a peripheral's service's characteristic If you already know the characteristic and service UUIDs you want to read from, once a peripheral has been found you can read from it right away like this: ```swift peripheral.readValue(ofCharacWithUUID: "2A29", fromServiceWithUUID: "180A") { result in switch result { case .success(let data): break // The data was read and is returned as an NSData instance case .failure(let error): break // An error happened while attempting to read the data } } ``` This will connect to the peripheral if necessary and ensure that the characteristic and service needed are discovered before reading from the characteristic matching `characteristicUUID`. If the charac/service cannot be retrieved you will receive an error specifying which charac/service could not be found. If you have a reference to a `CBCharacteristic`, you can read using the characteristic directly: ```swift peripheral.readValue(ofCharac: charac) { result in switch result { case .success(let data): break // The data was read and is returned as a Data instance case .failure(let error): break // An error happened while attempting to read the data } } ``` ### Writing to a Peripheral's service's characteristic If you already know the characteristic and service UUID you want to write to, once a peripheral has been found, you can write to that characteristic right away like this: ```swift let data = String(0b1010).dataUsingEncoding(NSUTF8StringEncoding)! peripheral.writeValue(ofCharacWithUUID: "1d5bc11d-e28c-4157-a7be-d8b742a013d8", fromServiceWithUUID: "4011e369-5981-4dae-b686-619dc656c7ba", value: data) { result in switch result { case .success: break // The write was succesful. case .failure(let error): break // An error happened while writting the data. } } ``` ### Receiving Characteristic update notifications Receiving characteristic value updates is done through notifications on the default `NotificationCenter`. All supported `Peripheral` notifications are part of the `PeripheralEvent` enum. Use this enum's raw values as the notification string when registering for notifications: ```swift // First we prepare ourselves to receive update notifications let peripheral = somePeripheral NotificationCenter.default.addObserver(forName: Peripheral.PeripheralCharacteristicValueUpdate, object: peripheral, queue: nil) { (notification) in let charac = notification.userInfo!["characteristic"] as! CBCharacteristic if let error = notification.userInfo?["error"] as? SBError { // Deal with error } } // We can then set a characteristic's notification value to true and start receiving updates to that characteristic peripheral.setNotifyValue(toEnabled: true, forCharacWithUUID: "2A29", ofServiceWithUUID: "180A") { (isNotifying, error) in // If there were no errors, you will now receive Notifications when that characteristic value gets updated. } ``` ### Discovering services Discover services using the `discoverServices(...)` function: ```swift peripheral.discoverServices(withUUIDs: nil) { result in switch result { case .success(let services): break // An array containing all the services requested case .failure(let error): break // A connection error or an array containing the UUIDs of the services that we're not found } } ``` Like the CBPeripheral discoverServices(...) function, passing nil instead of an array of service UUIDs will discover all of this Peripheral's services. ### Discovering characteristics Discover characteristics using the `discoverCharacteristics(...)` function. If the service on which you are attempting to discover characteristics from has not been discovered, an attempt will first be made to discover that service for you: ```swift peripheral.discoverCharacteristics(withUUIDs: nil, ofServiceWithUUID: "180A") { result in // The characteristics discovered or an error if something went wrong. switch result { case .success(let services): break // An array containing all the characs requested. case .failure(let error): break // A connection error or an array containing the UUIDs of the charac/services that we're not found. } } ``` Like the CBPeripheral discoverCharacteristics(...) function, passing nil instead of an array of service UUIDs will discover all of this service's characteristics. ### State preservation SwiftyBluetooth is backed by a CBCentralManager singleton wrapper and does not give you direct access to the underlying CBCentralManager. But, you can still setup the underlying CBCentralManager for state restoration by calling `setSharedCentralInstanceWith(restoreIdentifier: )` and use the restoreIdentifier of your choice. Take note that this method can only be called once and must be called before anything else in the library otherwise the Central sharedInstance will be lazily initiated the first time you access it. As such, it is recommended to call it in your App Delegate's `didFinishLaunchingWithOptions(:)` ```swift SwiftyBluetooth.setSharedCentralInstanceWith(restoreIdentifier: "MY_APP_BLUETOOTH_STATE_RESTORE_IDENTIFIER") ``` Register for state preservation notifications on the default NotificationCenter. Those notifications will contain an array of restored `Peripheral`. ```swift NotificationCenter.default.addObserver(forName: Central.CentralManagerWillRestoreStateNotification, object: Central.sharedInstance, queue: nil) { (notification) in if let restoredPeripherals = notification.userInfo?["peripherals"] as? [Peripheral] { } } ``` ## Installation ### CocoaPods Add this to your Podfile: ```ruby pod 'SwiftyBluetooth', '~> 3.1.0' ``` ### Swift Package Manager Simply add the library to your xcode project as a "Package Dependency" ### Carthage Add this to your Cartfile ```ogdl github "jordanebelanger/SwiftyBluetooth" ``` ## Requirements SwiftyBluetooth requires iOS 10.0+ ## License SwiftyBluetooth is released under the MIT License. ================================================ FILE: Sources/CBExtensions.swift ================================================ // // CBExtensions.swift // // Copyright (c) 2016 Jordane Belanger // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. import CoreBluetooth extension CBPeripheral { public func serviceWithUUID(_ uuid: CBUUID) -> CBService? { guard let services = self.services else { return nil } return services.filter { $0.uuid == uuid }.first } public func servicesWithUUIDs(_ servicesUUIDs: [CBUUID]) -> (foundServices: [CBService], missingServicesUUIDs: [CBUUID]) { assert(servicesUUIDs.count > 0) guard let currentServices = self.services , currentServices.count > 0 else { return (foundServices: [], missingServicesUUIDs: servicesUUIDs) } let currentServicesUUIDs = currentServices.map { $0.uuid } let currentServicesUUIDsSet = Set(currentServicesUUIDs) let requestedServicesUUIDsSet = Set(servicesUUIDs) let foundServicesUUIDsSet = requestedServicesUUIDsSet.intersection(currentServicesUUIDsSet) let missingServicesUUIDsSet = requestedServicesUUIDsSet.subtracting(currentServicesUUIDsSet) let foundServices = currentServices.filter { foundServicesUUIDsSet.contains($0.uuid) } return (foundServices: foundServices, missingServicesUUIDs: Array(missingServicesUUIDsSet)) } } extension CBService { public func characteristicWithUUID(_ uuid: CBUUID) -> CBCharacteristic? { guard let characteristics = self.characteristics else { return nil } return characteristics.filter { $0.uuid == uuid }.first } public func characteristicsWithUUIDs(_ characteristicsUUIDs: [CBUUID]) -> (foundCharacteristics: [CBCharacteristic], missingCharacteristicsUUIDs: [CBUUID]) { assert(characteristicsUUIDs.count > 0) guard let currentCharacteristics = self.characteristics , currentCharacteristics.count > 0 else { return (foundCharacteristics: [], missingCharacteristicsUUIDs: characteristicsUUIDs) } let currentCharacteristicsUUID = currentCharacteristics.map { $0.uuid } let currentCharacteristicsUUIDSet = Set(currentCharacteristicsUUID) let requestedCharacteristicsUUIDSet = Set(characteristicsUUIDs) let foundCharacteristicsUUIDSet = requestedCharacteristicsUUIDSet.intersection(currentCharacteristicsUUIDSet) let missingCharacteristicsUUIDSet = requestedCharacteristicsUUIDSet.subtracting(currentCharacteristicsUUIDSet) let foundCharacteristics = currentCharacteristics.filter { foundCharacteristicsUUIDSet.contains($0.uuid) } return (foundCharacteristics: foundCharacteristics, missingCharacteristicsUUIDs: Array(missingCharacteristicsUUIDSet)) } } extension CBCharacteristic { public func descriptorWithUUID(_ uuid: CBUUID) -> CBDescriptor? { guard let descriptors = self.descriptors else { return nil } return descriptors.filter { $0.uuid == uuid }.first } public func descriptorsWithUUIDs(_ descriptorsUUIDs: [CBUUID]) -> (foundDescriptors: [CBDescriptor], missingDescriptorsUUIDs: [CBUUID]) { assert(descriptorsUUIDs.count > 0) guard let currentDescriptors = self.descriptors , currentDescriptors.count > 0 else { return (foundDescriptors: [], missingDescriptorsUUIDs: descriptorsUUIDs) } let currentDescriptorsUUIDs = currentDescriptors.map { $0.uuid } let currentDescriptorsUUIDsSet = Set(currentDescriptorsUUIDs) let requestedDescriptorsUUIDsSet = Set(descriptorsUUIDs) let foundDescriptorsUUIDsSet = requestedDescriptorsUUIDsSet.intersection(currentDescriptorsUUIDsSet) let missingDescriptorsUUIDsSet = requestedDescriptorsUUIDsSet.subtracting(currentDescriptorsUUIDsSet) let foundDescriptors = currentDescriptors.filter { foundDescriptorsUUIDsSet.contains($0.uuid) } return (foundDescriptors: foundDescriptors, missingDescriptorsUUIDs: Array(missingDescriptorsUUIDsSet)) } } ================================================ FILE: Sources/CBUUIDConvertible.swift ================================================ // // CBUUIDConvertible.swift // // Copyright (c) 2016 Jordane Belanger // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. import CoreBluetooth /// Instead of directly using CBUUIDs in the Central and Peripheral class function parameters, you can use class/struct /// implementing the CBUUIDConvertible protocol. The protocol is used to give any object a way of converting itself to a CBUUID object. /// An implementation of the protocol is already provided for the following class: String, NSUUID, CBUUID, CBAttribute /// /// Using this, you could call discoverServices(...) using multiple different kind of parameters for the "servicesUUIDs" array: /// /// - discoverServices(["01AF"], ...) /// - discoverServices(["CBUUID(string: 01AF)"], ...) /// - discoverServices([CBService], ...) /// /// See also the list of object already implementing the protocol public protocol CBUUIDConvertible { var CBUUIDRepresentation: CBUUID { get } } extension String: CBUUIDConvertible { public var CBUUIDRepresentation: CBUUID { return CBUUID(string: self) } } extension UUID: CBUUIDConvertible { public var CBUUIDRepresentation: CBUUID { return CBUUID(nsuuid: self) } } extension CBUUID: CBUUIDConvertible { public var CBUUIDRepresentation: CBUUID { return self } } extension CBAttribute: CBUUIDConvertible { public var CBUUIDRepresentation: CBUUID { return self.uuid } } func ExtractCBUUIDs(_ CBUUIDConvertibles: [CBUUIDConvertible]?) -> [CBUUID]? { if let CBUUIDConvertibles = CBUUIDConvertibles , CBUUIDConvertibles.count > 0 { return CBUUIDConvertibles.map { $0.CBUUIDRepresentation } } else { return nil } } ================================================ FILE: Sources/CBUUIDPath.swift ================================================ // // CBUUIDPath.swift // // Copyright (c) 2016 Jordane Belanger // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. import CoreBluetooth struct CBUUIDPath: Hashable { let hash: Int init(uuids: CBUUID...) { var stringPath: String = String() for uuid in uuids { stringPath.append(uuid.uuidString) } self.hash = stringPath.hashValue } } func ==(lhs: CBUUIDPath, rhs: CBUUIDPath) -> Bool { return lhs.hashValue == rhs.hashValue } func servicePath(service: CBUUIDConvertible) -> CBUUIDPath { return CBUUIDPath(uuids: service.CBUUIDRepresentation) } func characteristicPath(service: CBUUIDConvertible, characteristic: CBUUIDConvertible) -> CBUUIDPath { return CBUUIDPath(uuids: service.CBUUIDRepresentation, characteristic.CBUUIDRepresentation) } func descriptorPath(service: CBUUIDConvertible, characteristic: CBUUIDConvertible, descriptor: CBUUIDConvertible) -> CBUUIDPath { return CBUUIDPath(uuids: service.CBUUIDRepresentation, characteristic.CBUUIDRepresentation, descriptor.CBUUIDRepresentation) } extension CBService { var uuidPath: CBUUIDPath { return servicePath(service: self) } } extension CBCharacteristic { var uuidPath: CBUUIDPath { return characteristicPath(service: self.service!, characteristic: self) } } extension CBDescriptor { var uuidPath: CBUUIDPath { return descriptorPath(service: self.characteristic!.service!, characteristic: self.characteristic!, descriptor: self) } } ================================================ FILE: Sources/Central.swift ================================================ // // Central.swift // // Copyright (c) 2016 Jordane Belanger // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. import CoreBluetooth /** The different results returned in the closure of the Central scanWithTimeout(...) function. - ScanStarted: The scan just started. - ScanResult: A Peripheral found result. `RSSI` will be nil if it could not be read. - ScanStopped: The scan ended. */ public enum PeripheralScanResult { case scanStarted case scanResult(peripheral: Peripheral, advertisementData: [String: Any], RSSI: Int?) case scanStopped(peripherals: [Peripheral], error: SBError?) } /** An enum type whoses rawValues mirror the CBCentralManagerState enum owns Integer values but without the ".Resetting" and ".Unknown" temporary values. - Unsupported: CBCentralManagerState.Unsupported - Unauthorized: CBCentralManagerState.Unauthorized - PoweredOff: CBCentralManagerState.PoweredOff - PoweredOn: CBCentralManagerState.PoweredOn */ public enum AsyncCentralState: Int { case unsupported = 2 case unauthorized = 3 case poweredOff = 4 case poweredOn = 5 case unknown = -1 } public typealias AsyncCentralStateCallback = (AsyncCentralState) -> Void public typealias BluetoothStateCallback = (CBManagerState) -> Void public typealias PeripheralScanCallback = (PeripheralScanResult) -> Void public typealias ConnectPeripheralCallback = (Result) -> Void public typealias DisconnectPeripheralCallback = (Result) -> Void /// A singleton wrapping a CBCentralManager instance to run CBCentralManager related functions with closures based callbacks instead of the usual CBCentralManagerDelegate interface. public final class Central { private static var _sharedInstance: Central? /// The name of a `Notification` posted by the Central sharedInstance when the app comes back from the background and restores the /// underlying CBCentralManager state after the centralManager:willRestoreState: delegate method is called. /// The userInfo contains an Array of `Peripheral` that were restored. /// Unwrap the peripherals with `notification.userInfo?["peripherals"] as? [Peripheral]` public static let CentralManagerWillRestoreState = Notification.Name("SwiftyBluetooth_CentralManagerWillRestoreStateNotification") /// The name of a `Notification` posted by the Central sharedInstance when its underlying CBCentralManager state changes. Take note that if the `CBCentralManager` state /// goes from poweredOn to something lower, all your peripherals will be invalidated and need to be discovered again. /// The new `CBCentralManagerState` can be found in the notification's userInfo. /// Unwrap with `notification.userInfo?["state"] as? CBCentralManagerState` public static let CentralStateChange = Notification.Name("SwiftyBluetooth_CentralStateChange") static let CentralCBPeripheralDisconnected = Notification.Name("SwiftyBluetooth_CentralCBPeripheralDisconnected") /// The sharedInstance Singleton, you can instantiate it yourself by /// calling `setSharedInstanceWith(restoreIdentifier: )` which will allow you /// to pass in a state preservation identifier. Otherwise, this sharedInstance /// be lazily initialized the first time you call this getter. public static var sharedInstance: Central { if let sharedInstance = _sharedInstance { return sharedInstance } _sharedInstance = Central() return _sharedInstance! } /// Allows you to initially set the sharedInstance and use the restore /// identifier string of your choice for state preservation between app /// launches. @discardableResult public static func setSharedInstanceWith(restoreIdentifier: String) -> Central { assert(_sharedInstance == nil, "You can only set the sharedInstance of the Central once and you must do so before calling any other SwiftyBluetooth functions.") _sharedInstance = Central(stateRestoreIdentifier: restoreIdentifier) return _sharedInstance! } fileprivate let centralProxy: CentralProxy private init() { self.centralProxy = CentralProxy() } private init(stateRestoreIdentifier: String) { self.centralProxy = CentralProxy(stateRestoreIdentifier: stateRestoreIdentifier) } } // MARK: Internal typealias InitializeBluetoothCallback = (_ error: SBError?) -> Void extension Central { func initializeBluetooth(completion: @escaping InitializeBluetoothCallback) { centralProxy.initializeBluetooth(completion) } func connect(peripheral: CBPeripheral, timeout: TimeInterval = 10, completion: @escaping ConnectPeripheralCallback) { centralProxy.connect(peripheral: peripheral, timeout: timeout, completion) } func disconnect(peripheral: CBPeripheral, timeout: TimeInterval = 10, completion: @escaping DisconnectPeripheralCallback) { centralProxy.disconnect(peripheral: peripheral, timeout: timeout, completion) } } // MARK: Public extension Central { /// The underlying CBCentralManager CBManagerState public var state: CBManagerState { switch self.centralProxy.centralManager.state.rawValue { case 0: return .unknown case 1: return .resetting case 2: return .unsupported case 3: return .unauthorized case 4: return .poweredOff case 5: return .poweredOn default: assertionFailure("Unhandlable bluetooth state") return .unknown } } /// The underlying CBCentralManager isScanning value public var isScanning: Bool { return self.centralProxy.centralManager.isScanning } /// Attempts to return the periperals from a list of identifier "UUID"s public func retrievePeripherals(withUUIDs uuids: [CBUUIDConvertible]) -> [Peripheral] { let uuids = uuids.compactMap { UUID(uuidString: $0.CBUUIDRepresentation.uuidString) } let cbPeripherals = self.centralProxy.centralManager.retrievePeripherals(withIdentifiers: uuids) let peripherals = cbPeripherals.map { cbPeripheral -> Peripheral in return Peripheral(peripheral: cbPeripheral) } return peripherals } /// Attempts to return the connected peripheral having the specific service CBUUIDs public func retrieveConnectedPeripherals(withServiceUUIDs uuids: [CBUUIDConvertible]) -> [Peripheral] { let cbUUIDs = ExtractCBUUIDs(uuids) ?? [] let cbPeripherals = self.centralProxy.centralManager.retrieveConnectedPeripherals(withServices: cbUUIDs) let peripherals = cbPeripherals.map { cbPeripheral -> Peripheral in return Peripheral(peripheral: cbPeripheral) } return peripherals } /// Scans for Peripherals through a CBCentralManager scanForPeripheralsWithServices(...) function call. /// /// - Parameter timeout: The scanning time in seconds before the scan is stopped and the completion closure is called with a scanStopped result. /// - Parameter serviceUUIDs: The service UUIDs to search peripherals for or nil if looking for all peripherals. /// - Parameter completion: The closures, called multiple times throughout a scan. public func scanForPeripherals(withServiceUUIDs serviceUUIDs: [CBUUIDConvertible]? = nil, options: [String : Any]? = nil, timeoutAfter timeout: TimeInterval, completion: @escaping PeripheralScanCallback) { // Passing in an empty array will act the same as if you passed nil and discover all peripherals but // it is recommended to pass in nil for those cases similarly to how the CoreBluetooth scan method works assert(serviceUUIDs == nil || serviceUUIDs!.count > 0) centralProxy.scanWithTimeout(timeout, serviceUUIDs: ExtractCBUUIDs(serviceUUIDs), options: options, completion) } /// Will stop the current scan through a CBCentralManager stopScan() function call and invokes the completion /// closures of the original scanWithTimeout function call with a scanStopped result containing an error if something went wrong. public func stopScan() { centralProxy.stopScan() } /// Sometime, the bluetooth state of your iOS Device/CBCentralManagerState is in an inbetween state of either /// ".Unknown" or ".Reseting". This function will wait until the bluetooth state is stable and return a subset /// of the CBCentralManager state value which does not includes these values in its completion closure. public func asyncState(completion: @escaping AsyncCentralStateCallback) { self.centralProxy.asyncState(completion) } } ================================================ FILE: Sources/CentralProxy.swift ================================================ // // CentralProxy.swift // // Copyright (c) 2016 Jordane Belanger // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. import CoreBluetooth final class CentralProxy: NSObject { fileprivate lazy var asyncStateCallbacks: [AsyncCentralStateCallback] = [] fileprivate var scanRequest: PeripheralScanRequest? fileprivate lazy var connectRequests: [UUID: ConnectPeripheralRequest] = [:] fileprivate lazy var disconnectRequests: [UUID: DisconnectPeripheralRequest] = [:] var centralManager: CBCentralManager! override init() { super.init() self.centralManager = CBCentralManager(delegate: self, queue: nil) } init(stateRestoreIdentifier: String) { super.init() self.centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionRestoreIdentifierKey: stateRestoreIdentifier]) } fileprivate func postCentralEvent(_ event: NSNotification.Name, userInfo: [AnyHashable: Any]? = nil) { NotificationCenter.default.post( name: event, object: Central.sharedInstance, userInfo: userInfo) } } // MARK: Initialize Bluetooth requests extension CentralProxy { func asyncState(_ completion: @escaping AsyncCentralStateCallback) { switch centralManager.state { case .unknown: self.asyncStateCallbacks.append(completion) case .resetting: self.asyncStateCallbacks.append(completion) case .unsupported: completion(.unsupported) case .unauthorized: completion(.unauthorized) case .poweredOff: completion(.poweredOff) case .poweredOn: completion(.poweredOn) @unknown default: completion(.unknown) } } func initializeBluetooth(_ completion: @escaping InitializeBluetoothCallback) { self.asyncState { (state) in switch state { case .unsupported: completion(.bluetoothUnavailable(reason: .unsupported)) case .unauthorized: completion(.bluetoothUnavailable(reason: .unauthorized)) case .poweredOff: completion(.bluetoothUnavailable(reason: .poweredOff)) case .poweredOn: completion(nil) case .unknown: completion(.bluetoothUnavailable(reason: .unknown)) } } } func callAsyncCentralStateCallback(_ state: AsyncCentralState) { let callbacks = self.asyncStateCallbacks self.asyncStateCallbacks.removeAll() for callback in callbacks { callback(state) } } } // MARK: Scan requests private final class PeripheralScanRequest { let callback: PeripheralScanCallback var peripherals: [UUID: Peripheral] = [:] init(callback: @escaping PeripheralScanCallback) { self.callback = callback } } extension CentralProxy { func scanWithTimeout(_ timeout: TimeInterval, serviceUUIDs: [CBUUID]?, options: [String : Any]?, _ callback: @escaping PeripheralScanCallback) { initializeBluetooth { [unowned self] (error) in if let error = error { callback(PeripheralScanResult.scanStopped(peripherals: [], error: error)) } else { if self.scanRequest != nil { self.centralManager.stopScan() } let scanRequest = PeripheralScanRequest(callback: callback) self.scanRequest = scanRequest scanRequest.callback(.scanStarted) self.centralManager.scanForPeripherals(withServices: serviceUUIDs, options: options) Timer.scheduledTimer( timeInterval: timeout, target: self, selector: #selector(self.onScanTimerTick), userInfo: Weak(value: scanRequest), repeats: false) } } } func stopScan(error: SBError? = nil) { // avoid an API MISUSE warning on the console if bluetooth is powered off or unsupported if self.centralManager.state != .poweredOff, self.centralManager.state != .unsupported { self.centralManager.stopScan() } if let scanRequest = self.scanRequest { self.scanRequest = nil scanRequest.callback(.scanStopped(peripherals: scanRequest.peripherals.values.map({$0}), error: error)) } } @objc fileprivate func onScanTimerTick(_ timer: Timer) { defer { if timer.isValid { timer.invalidate() } } let weakRequest = timer.userInfo as! Weak if weakRequest.value != nil { self.stopScan() } } } // MARK: Connect Peripheral requests private final class ConnectPeripheralRequest { var callbacks: [ConnectPeripheralCallback] = [] let peripheral: CBPeripheral init(peripheral: CBPeripheral, callback: @escaping ConnectPeripheralCallback) { self.callbacks.append(callback) self.peripheral = peripheral } func invokeCallbacks(error: Error?) { let result: Result = { if let error = error { return .failure(error) } else { return .success(()) } }() for callback in callbacks { callback(result) } } } extension CentralProxy { func connect(peripheral: CBPeripheral, timeout: TimeInterval, _ callback: @escaping ConnectPeripheralCallback) { initializeBluetooth { [unowned self] (error) in if let error = error { callback(.failure(error)) return } let uuid = peripheral.identifier if let cbPeripheral = self.centralManager.retrievePeripherals(withIdentifiers: [uuid]).first , cbPeripheral.state == .connected { callback(.success(())) return } if let request = self.connectRequests[uuid] { request.callbacks.append(callback) } else { let request = ConnectPeripheralRequest(peripheral: peripheral, callback: callback) self.connectRequests[uuid] = request self.centralManager.connect(peripheral, options: nil) Timer.scheduledTimer( timeInterval: timeout, target: self, selector: #selector(self.onConnectTimerTick), userInfo: Weak(value: request), repeats: false) } } } @objc fileprivate func onConnectTimerTick(_ timer: Timer) { defer { if timer.isValid { timer.invalidate() } } let weakRequest = timer.userInfo as! Weak guard let request = weakRequest.value else { return } let uuid = request.peripheral.identifier self.connectRequests[uuid] = nil self.centralManager.cancelPeripheralConnection(request.peripheral) request.invokeCallbacks(error: SBError.operationTimedOut(operation: .connectPeripheral)) } } // MARK: Disconnect Peripheral requests private final class DisconnectPeripheralRequest { var callbacks: [ConnectPeripheralCallback] = [] let peripheral: CBPeripheral init(peripheral: CBPeripheral, callback: @escaping DisconnectPeripheralCallback) { self.callbacks.append(callback) self.peripheral = peripheral } func invokeCallbacks(error: Error?) { let result: Result = { if let error = error { return .failure(error) } else { return .success(()) } }() for callback in callbacks { callback(result) } } } extension CentralProxy { func disconnect(peripheral: CBPeripheral, timeout: TimeInterval, _ callback: @escaping DisconnectPeripheralCallback) { initializeBluetooth { [unowned self] (error) in if let error = error { callback(.failure(error)) return } let uuid = peripheral.identifier if let cbPeripheral = self.centralManager.retrievePeripherals(withIdentifiers: [uuid]).first, (cbPeripheral.state == .disconnected || cbPeripheral.state == .disconnecting) { callback(.success(())) return } if let request = self.disconnectRequests[uuid] { request.callbacks.append(callback) } else { let request = DisconnectPeripheralRequest(peripheral: peripheral, callback: callback) self.disconnectRequests[uuid] = request self.centralManager.cancelPeripheralConnection(peripheral) Timer.scheduledTimer( timeInterval: timeout, target: self, selector: #selector(self.onDisconnectTimerTick), userInfo: Weak(value: request), repeats: false) } } } @objc fileprivate func onDisconnectTimerTick(_ timer: Timer) { defer { if timer.isValid { timer.invalidate() } } let weakRequest = timer.userInfo as! Weak guard let request = weakRequest.value else { return } let uuid = request.peripheral.identifier self.disconnectRequests[uuid] = nil request.invokeCallbacks(error: SBError.operationTimedOut(operation: .disconnectPeripheral)) } } extension CentralProxy: CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { postCentralEvent(Central.CentralStateChange, userInfo: ["state": central.state]) switch central.state.rawValue { case 0: // .unknown self.stopScan(error: .scanningEndedUnexpectedly) case 1: // .resetting self.stopScan(error: .scanningEndedUnexpectedly) case 2: // .unsupported self.callAsyncCentralStateCallback(.unsupported) self.stopScan(error: .scanningEndedUnexpectedly) case 3: // .unauthorized self.callAsyncCentralStateCallback(.unauthorized) self.stopScan(error: .scanningEndedUnexpectedly) case 4: // .poweredOff self.callAsyncCentralStateCallback(.poweredOff) self.stopScan(error: .scanningEndedUnexpectedly) case 5: // .poweredOn self.callAsyncCentralStateCallback(.poweredOn) default: fatalError("Unsupported BLE CentralState") } } func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { let uuid = peripheral.identifier guard let request = connectRequests[uuid] else { return } connectRequests[uuid] = nil request.invokeCallbacks(error: nil) } func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { let uuid = peripheral.identifier var userInfo: [AnyHashable: Any] = ["identifier": uuid] if let error = error { userInfo["error"] = error } postCentralEvent(Central.CentralCBPeripheralDisconnected, userInfo: userInfo) guard let request = disconnectRequests[uuid] else { return } disconnectRequests[uuid] = nil request.invokeCallbacks(error: error) } func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { let uuid = peripheral.identifier guard let request = connectRequests[uuid] else { return } let resolvedError: Error = error ?? SBError.peripheralFailedToConnectReasonUnknown connectRequests[uuid] = nil request.invokeCallbacks(error: resolvedError) } func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) { guard let scanRequest = scanRequest else { return } guard scanRequest.peripherals[peripheral.identifier] == nil else { return } let peripheral = Peripheral(peripheral: peripheral) scanRequest.peripherals[peripheral.identifier] = peripheral var rssiOptional: Int? = Int(truncating: RSSI) if let rssi = rssiOptional, rssi == 127 { rssiOptional = nil } scanRequest.callback(.scanResult(peripheral: peripheral, advertisementData: advertisementData, RSSI: rssiOptional)) } func centralManager(_ central: CBCentralManager, willRestoreState dict: [String: Any]) { let peripherals = ((dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral]) ?? []).map { Peripheral(peripheral: $0) } postCentralEvent(Central.CentralManagerWillRestoreState, userInfo: ["peripherals": peripherals]) } } ================================================ FILE: Sources/DescriptorValue.swift ================================================ // // DescriptorValue.swift // // Copyright (c) 2016 Jordane Belanger // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. import CoreBluetooth /** Wrapper around common GATT descriptor values. Automatically unwrap and cast your descriptor values for standard GATT descriptor UUIDs. - CharacteristicExtendedProperties: Case for Descriptor with UUID CBUUIDCharacteristicExtendedPropertiesString - CharacteristicUserDescription: Case for Descriptor with UUID CBUUIDCharacteristicUserDescriptionString - ClientCharacteristicConfigurationString: Case for Descriptor with UUID CBUUIDClientCharacteristicConfigurationString - ServerCharacteristicConfigurationString: Case for Descriptor with UUID CBUUIDServerCharacteristicConfigurationString - CharacteristicFormatString: Case for Descriptor with UUID CBUUIDCharacteristicFormatString - CharacteristicAggregateFormatString: Case for Descriptor with UUID CBUUIDCharacteristicAggregateFormatString - CustomValue: Case for descriptor with a non standard UUID */ public enum DescriptorValue { case characteristicExtendedProperties(value: UInt16) case characteristicUserDescription(value: String) case clientCharacteristicConfigurationString(value: UInt16) case serverCharacteristicConfigurationString(value: UInt16) case characteristicFormatString(value: Data) case characteristicAggregateFormatString(value: UInt16) case customValue(value: AnyObject) init(descriptor: CBDescriptor) throws { guard let value = descriptor.value else { throw SBError.invalidDescriptorValue(descriptor: descriptor) } switch descriptor.CBUUIDRepresentation.uuidString { case CBUUIDCharacteristicExtendedPropertiesString: guard let value = UInt16(uncastedUnwrappedNSNumber: descriptor.value as AnyObject?) else { throw SBError.invalidDescriptorValue(descriptor: descriptor) } self = .characteristicExtendedProperties(value: value) case CBUUIDCharacteristicUserDescriptionString: guard let value = descriptor.value as? String else { throw SBError.invalidDescriptorValue(descriptor: descriptor) } self = .characteristicUserDescription(value: value) case CBUUIDClientCharacteristicConfigurationString: guard let value = UInt16(uncastedUnwrappedNSNumber: descriptor.value as AnyObject?) else { throw SBError.invalidDescriptorValue(descriptor: descriptor) } self = .clientCharacteristicConfigurationString(value: value) case CBUUIDServerCharacteristicConfigurationString: guard let value = UInt16(uncastedUnwrappedNSNumber: descriptor.value as AnyObject?) else { throw SBError.invalidDescriptorValue(descriptor: descriptor) } self = .serverCharacteristicConfigurationString(value: value) case CBUUIDCharacteristicFormatString: guard let value = descriptor.value as? Data else { throw SBError.invalidDescriptorValue(descriptor: descriptor) } self = .characteristicFormatString(value: value) case CBUUIDCharacteristicAggregateFormatString: guard let value = UInt16(uncastedUnwrappedNSNumber: descriptor.value as AnyObject?) else { throw SBError.invalidDescriptorValue(descriptor: descriptor) } self = .characteristicAggregateFormatString(value: value) default: self = .customValue(value: value as AnyObject) } } } ================================================ FILE: Sources/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType FMWK CFBundleShortVersionString 1.0.0 CFBundleSignature ???? CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass ================================================ FILE: Sources/Peripheral.swift ================================================ // // Peripheral.swift // // Copyright (c) 2016 Jordane Belanger // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. import CoreBluetooth /** The Peripheral notifications sent through the default 'NotificationCenter' by Peripherals. Use the PeripheralEvent enum rawValue as the notification string when registering for notifications. - peripheralModifedServices: Update to a peripheral's CBPeripheral services, userInfo: ["invalidatedServices": [CBService]] - characteristicValueUpdate: An update to the value of a characteristic you're peripherals is subscribed for updates from, userInfo: ["characteristic": CBCharacteristic, "error": SBError?] */ public typealias rssi = Int public typealias isNotifying = Bool public typealias ReadRSSIRequestCallback = (_ result: Result) -> Void public typealias ServiceRequestCallback = (_ result: Result<[CBService], Error>) -> Void public typealias CharacteristicRequestCallback = (_ result: Result<[CBCharacteristic], Error>) -> Void public typealias DescriptorRequestCallback = (_ result: Result<[CBDescriptor], Error>) -> Void public typealias ReadCharacRequestCallback = (_ result: Result) -> Void public typealias ReadDescriptorRequestCallback = (_ result: Result) -> Void public typealias WriteRequestCallback = (_ result: Result) -> Void public typealias UpdateNotificationStateCallback = (_ result: Result) -> Void /// An interface on top of a CBPeripheral instance used to run CBPeripheral related functions with closures based callbacks instead of the usual CBPeripheralDelegate interface. public final class Peripheral { fileprivate var peripheralProxy: PeripheralProxy! init(peripheral: CBPeripheral) { self.peripheralProxy = PeripheralProxy(cbPeripheral: peripheral, peripheral: self) } } // MARK: Public extension Peripheral { /// The name of a `Notification` posted by a `Peripheral` instance when its `CBPeripheral` name value changes. /// Unwrap the new name if available with `notification.userInfo?["name"] as? String` public static let PeripheralNameUpdate = Notification.Name(rawValue: "SwiftyBluetooth_PeripheralNameUpdate") /// The name of a `Notification` posted by a `Peripheral` instance when some of its `CBPeripheral` services are invalidated. /// Unwrap the invalidated services with `notification.userInfo?["invalidatedServices"] as? [CBSErvice]` public static let PeripheralModifedServices = Notification.Name(rawValue: "SwiftyBluetooth_PeripheralModifedServices") /// The name of a `Notification` posted by a `Peripheral` instance when one of the characteristic you have subcribed for update from /// changes its value. /// Unwrap the new charac value with `notification.userInfo?["characteristic"] as? CBCharacteristic` /// Unwrap the error if any with `notification.userInfo?["error"] as? SBError` public static let PeripheralCharacteristicValueUpdate = Notification.Name(rawValue: "SwiftyBluetooth_PharacteristicValueUpdate") /// The name of a `Notification` posted by a `Peripheral` instance when it becomes disconnected /// Unwrap the new charac value with `notification.userInfo?["characteristic"] as? CBCharacteristic` /// Unwrap the error if any with `notification.userInfo?["error"] as? SBError` public static let PeripheralDisconnected = Notification.Name(rawValue: "SwiftyBluetooth_PeripheralDisconnected") /// The underlying CBPeripheral identifier public var identifier: UUID { return self.peripheralProxy.cbPeripheral.identifier } /// The underlying CBPeripheral name public var name: String? { return self.peripheralProxy.cbPeripheral.name } /// The underlying CBPeripheral state public var state: CBPeripheralState { return self.peripheralProxy.cbPeripheral.state } /// The underlying CBPeripheral services public var services: [CBService]? { return self.peripheralProxy.cbPeripheral.services } /// Returns the service requested if it exists and has been discovered public func service(withUUID serviceUUID: CBUUIDConvertible) -> CBService? { return self.peripheralProxy.cbPeripheral .serviceWithUUID(serviceUUID.CBUUIDRepresentation) } /// Returns the characteristic requested if it exists and has been discovered public func characteristic(withUUID characteristicUUID: CBUUIDConvertible, ofServiceWithUUID serviceUUID: CBUUIDConvertible) -> CBCharacteristic? { return self.peripheralProxy.cbPeripheral .serviceWithUUID(serviceUUID.CBUUIDRepresentation)? .characteristicWithUUID(characteristicUUID.CBUUIDRepresentation) } /// Returns the descriptor requested if it exists and has been discovered public func descriptor(withUUID descriptorUUID: CBUUIDConvertible, ofCharacWithUUID characUUID: CBUUIDConvertible, fromServiceWithUUID serviceUUID: CBUUIDConvertible) -> CBDescriptor? { return self.peripheralProxy.cbPeripheral .serviceWithUUID(serviceUUID.CBUUIDRepresentation)? .characteristicWithUUID(characUUID.CBUUIDRepresentation)? .descriptorWithUUID(descriptorUUID.CBUUIDRepresentation) } /// Connect to the peripheral through Ble to our Central sharedInstance public func connect(withTimeout timeout: TimeInterval?, completion: @escaping ConnectPeripheralCallback) { if let timeout = timeout { self.peripheralProxy.connect(timeout: timeout, completion) } else { self.peripheralProxy.connect(timeout: TimeInterval.infinity, completion) } } /// Disconnect the peripheral from our Central sharedInstance public func disconnect(completion: @escaping DisconnectPeripheralCallback) { self.peripheralProxy.disconnect(completion) } /// Connects to the peripheral and update the Peripheral's RSSI through a 'CBPeripheral' readRSSI() function call /// /// - Parameter completion: A closure containing the integer value of the updated RSSI or an error. public func readRSSI(completion: @escaping ReadRSSIRequestCallback) { self.peripheralProxy.readRSSI(completion) } /// Connects to the peripheral and discover the requested services through a 'CBPeripheral' discoverServices(...) function call /// /// - Parameter serviceUUIDs: The UUIDs of the services you want to discover or nil if you want to discover all services. /// - Parameter completion: A closures containing an array of the services found or an error. public func discoverServices(withUUIDs serviceUUIDs: [CBUUIDConvertible]? = nil, completion: @escaping ServiceRequestCallback) { // Passing in an empty array will act the same as if you passed nil and discover all the services. // But it is recommended to pass in nil for those cases similarly to how the CoreBluetooth discoverServices method works assert(serviceUUIDs == nil || serviceUUIDs!.count > 0) self.peripheralProxy.discoverServices(ExtractCBUUIDs(serviceUUIDs), completion: completion) } /// Connects to the peripheral and discover the requested included services of a service through a 'CBPeripheral' discoverIncludedServices(...) function call /// /// - Parameter serviceUUIDs: The UUIDs of the included services you want to discover or nil if you want to discover all included services. /// - Parameter serviceUUID: The service to request included services from. /// - Parameter completion: A closures containing an array of the services found or an error. public func discoverIncludedServices(withUUIDs includedServiceUUIDs: [CBUUIDConvertible]? = nil, ofServiceWithUUID serviceUUID: CBUUIDConvertible, completion: @escaping ServiceRequestCallback) { // Passing in an empty array will act the same as if you passed nil and discover all the services. // But it is recommended to pass in nil for those cases similarly to how the CoreBluetooth discoverServices method works assert(includedServiceUUIDs == nil || includedServiceUUIDs!.count > 0) self.peripheralProxy.discoverIncludedServices(ExtractCBUUIDs(includedServiceUUIDs), forService: serviceUUID.CBUUIDRepresentation, completion: completion) } /// Connects to the peripheral and discover the requested characteristics through a 'CBPeripheral' discoverCharacteristics(...) function call. /// Will first discover the service of the requested characteristics if necessary. /// /// - Parameter serviceUUID: The UUID of the service of the characteristics requested. /// - Parameter characteristicUUIDs: The UUIDs of the characteristics you want to discover or nil if you want to discover all characteristics. /// - Parameter completion: A closures containing an array of the characteristics found or an error. public func discoverCharacteristics(withUUIDs characteristicUUIDs: [CBUUIDConvertible]? = nil, ofServiceWithUUID serviceUUID: CBUUIDConvertible, completion: @escaping CharacteristicRequestCallback) { // Passing in an empty array will act the same as if you passed nil and discover all the characteristics. // But it is recommended to pass in nil for those cases similarly to how the CoreBluetooth discoverCharacteristics method works assert(characteristicUUIDs == nil || characteristicUUIDs!.count > 0) self.peripheralProxy.discoverCharacteristics(ExtractCBUUIDs(characteristicUUIDs), forService: serviceUUID.CBUUIDRepresentation, completion: completion) } /// Connects to the peripheral and discover the requested descriptors through a 'CBPeripheral' discoverDescriptorsForCharacteristic(...) function call. /// Will first discover the service and characteristic for which you want to discover descriptors from. /// /// - Parameter characteristicUUID: The UUID of the characteristic you want to discover descriptors from. /// - Parameter serviceUUID: The UUID of the service of the characteristic above. /// - Parameter completion: A closures containing an array of the descriptors found or an error. public func discoverDescriptors(ofCharacWithUUID characUUID: CBUUIDConvertible, fromServiceWithUUID serviceUUID: CBUUIDConvertible, completion: @escaping DescriptorRequestCallback) { self.peripheralProxy.discoverDescriptorsForCharacteristic(characUUID.CBUUIDRepresentation, serviceUUID: serviceUUID.CBUUIDRepresentation, completion: completion) } /// Connects to the peripheral and discover the requested descriptors through a 'CBPeripheral' discoverDescriptorsForCharacteristic(...) function call. /// Will first discover the service and characteristic for which you want to discover descriptors from. /// /// - Parameter charac: The characteristic to discover descriptors from. /// - Parameter completion: A closures containing an array of the descriptors found or an error. public func discoverDescriptors(ofCharac charac: CBCharacteristic, completion: @escaping DescriptorRequestCallback) { self.discoverDescriptors(ofCharacWithUUID: charac, fromServiceWithUUID: charac.service!, completion: completion) } /// Connect to the peripheral and read the value of the characteristic requested through a 'CBPeripheral' readValueForCharacteristic(...) function call. /// Will first discover the service and characteristic you want to read from if necessary. /// /// - Parameter characteristicUUID: The UUID of the characteristic you want to read from. /// - Parameter serviceUUID: The UUID of the service of the characteristic above. /// - Parameter completion: A closures containing the data read or an error. public func readValue(ofCharacWithUUID characUUID: CBUUIDConvertible, fromServiceWithUUID serviceUUID: CBUUIDConvertible, completion: @escaping ReadCharacRequestCallback) { self.peripheralProxy.readCharacteristic(characUUID.CBUUIDRepresentation, serviceUUID: serviceUUID.CBUUIDRepresentation, completion: completion) } /// Connect to the peripheral and read the value of the passed characteristic through a 'CBPeripheral' readValueForCharacteristic(...) function call. /// /// - Parameter charac: The characteristic you want to read from. /// - Parameter completion: A closures containing the data read or an error. public func readValue(ofCharac charac: CBCharacteristic, completion: @escaping ReadCharacRequestCallback) { self.readValue(ofCharacWithUUID: charac, fromServiceWithUUID: charac.service!, completion: completion) } /// Connect to the peripheral and read the value of the descriptor requested through a 'CBPeripheral' readValueForDescriptor(...) function call. /// Will first discover the service, characteristic and descriptor you want to read from if necessary. /// /// - Parameter descriptorUUID: The UUID of the descriptor you want to read from. /// - Parameter characteristicUUID: The UUID of the descriptor above. /// - Parameter serviceUUID: The UUID of the service of the characteristic above. /// - Parameter completion: A closures containing the data read or an error. public func readValue(ofDescriptorWithUUID descriptorUUID: CBUUIDConvertible, fromCharacUUID characUUID: CBUUIDConvertible, ofServiceUUID serviceUUID: CBUUIDConvertible, completion: @escaping ReadDescriptorRequestCallback) { self.peripheralProxy.readDescriptor(descriptorUUID.CBUUIDRepresentation, characteristicUUID: characUUID.CBUUIDRepresentation, serviceUUID: serviceUUID.CBUUIDRepresentation, completion: completion) } /// Connect to the peripheral and read the value of the passed descriptor through a 'CBPeripheral' readValueForDescriptor(...) function call. /// /// - Parameter descriptor: The descriptor you want to read from. /// - Parameter characteristicUUID: The UUID of the descriptor above. /// - Parameter serviceUUID: The UUID of the service of the characteristic above. /// - Parameter completion: A closures containing the data read or an error. public func readValue(ofDescriptor descriptor: CBDescriptor, completion: @escaping ReadDescriptorRequestCallback) { self.readValue(ofDescriptorWithUUID: descriptor, fromCharacUUID: descriptor.characteristic!, ofServiceUUID: descriptor.characteristic!.service!, completion: completion) } /// Connect to the peripheral and write a value to the characteristic requested through a 'CBPeripheral' writeValue:forCharacteristic(...) function call. /// Will first discover the service and characteristic you want to write to if necessary. /// /// - Parameter characUUID: The UUID of the characteristic you want to write to. /// - Parameter serviceUUID: The UUID of the service of the characteristic above. /// - Parameter value: The data being written to the characteristic /// - Parameter type: The type of the CBPeripheral write, wither with or without response in which case the closure is called right away /// - Parameter completion: A closures containing an error if something went wrong public func writeValue(ofCharacWithUUID characUUID: CBUUIDConvertible, fromServiceWithUUID serviceUUID: CBUUIDConvertible, value: Data, type: CBCharacteristicWriteType = .withResponse, completion: @escaping WriteRequestCallback) { self.peripheralProxy.writeCharacteristicValue(characUUID.CBUUIDRepresentation, serviceUUID: serviceUUID.CBUUIDRepresentation, value: value, type: type, completion: completion) } /// Connect to the peripheral and write a value to the passed characteristic through a 'CBPeripheral' writeValue:forCharacteristic(...) function call. /// /// - Parameter charac: The characteristic you want to write to. /// - Parameter value: The data being written to the characteristic /// - Parameter type: The type of the CBPeripheral write, wither with or without response in which case the closure is called right away /// - Parameter completion: A closures containing an error if something went wrong public func writeValue(ofCharac charac: CBCharacteristic, value: Data, type: CBCharacteristicWriteType = .withResponse, completion: @escaping WriteRequestCallback) { self.writeValue(ofCharacWithUUID: charac, fromServiceWithUUID: charac.service!, value: value, type: type, completion: completion) } /// Connect to the peripheral and write a value to the descriptor requested through a 'CBPeripheral' writeValue:forDescriptor(...) function call. /// Will first discover the service, characteristic and descriptor you want to write to if necessary. /// /// - Parameter descriptorUUID: The UUID of the descriptor you want to write to. /// - Parameter characUUID: The UUID of the characteristic of the descriptor above. /// - Parameter serviceUUID: The UUID of the service of the characteristic above. /// - Parameter value: The data being written to the characteristic. /// - Parameter completion: A closures containing an error if something went wrong. public func writeValue(ofDescriptorWithUUID descriptorUUID: CBUUIDConvertible, fromCharacWithUUID characUUID: CBUUIDConvertible, ofServiceWithUUID serviceUUID: CBUUIDConvertible, value: Data, completion: @escaping WriteRequestCallback) { self.peripheralProxy.writeDescriptorValue(descriptorUUID.CBUUIDRepresentation, characteristicUUID: characUUID.CBUUIDRepresentation, serviceUUID: serviceUUID.CBUUIDRepresentation, value: value, completion: completion) } /// Connect to the peripheral and write a value to the passed descriptor through a 'CBPeripheral' writeValue:forDescriptor(...) function call. /// /// - Parameter descriptor: The descriptor you want to write to. /// - Parameter value: The data being written to the descriptor. /// - Parameter completion: A closures containing an error if something went wrong. public func writeValue(ofDescriptor descriptor: CBDescriptor, value: Data, completion: @escaping WriteRequestCallback) { self.writeValue(ofDescriptorWithUUID: descriptor, fromCharacWithUUID: descriptor.characteristic!, ofServiceWithUUID: descriptor.characteristic!.service!, value: value, completion: completion) } /// Connect to the peripheral and set the notification value of the characteristic requested through a 'CBPeripheral' setNotifyValueForCharacteristic function call. /// Will first discover the service and characteristic you want to either, start, or stop, getting notifcations from. /// /// - Parameter enabled: If enabled is true, this peripherals will register for change notifcations to the characteristic /// and notify listeners through the default 'NotificationCenter' with a 'PeripheralEvent.characteristicValueUpdate' notification. /// - Parameter characUUID: The UUID of the characteristic you want set the notify value of. /// - Parameter serviceUUID: The UUID of the service of the characteristic above. /// - Parameter completion: A closures containing the updated notification value of the characteristic or an error if something went wrong. public func setNotifyValue(toEnabled enabled: Bool, forCharacWithUUID characUUID: CBUUIDConvertible, ofServiceWithUUID serviceUUID: CBUUIDConvertible, completion: @escaping UpdateNotificationStateCallback) { self.peripheralProxy.setNotifyValueForCharacteristic(enabled, characteristicUUID: characUUID.CBUUIDRepresentation, serviceUUID: serviceUUID.CBUUIDRepresentation, completion: completion) } /// Connect to the peripheral and set the notification value of the passed characteristic through a 'CBPeripheral' setNotifyValueForCharacteristic function call. /// /// If set to true, this peripheral will emit characteristic change updates through the default NotificationCenter using the "characteristicValueUpdate" notification. /// /// - Parameter enabled: The notify state of the charac, set enabled to true to receive change notifications through the default Notification center /// - Parameter charac: The characteristic you want set the notify value of. /// - Parameter completion: A closures containing the updated notification value of the characteristic or an error if something went wrong. public func setNotifyValue(toEnabled enabled: Bool, ofCharac charac: CBCharacteristic, completion: @escaping UpdateNotificationStateCallback) { self.setNotifyValue(toEnabled: enabled, forCharacWithUUID: charac, ofServiceWithUUID: charac.service!, completion: completion) } } ================================================ FILE: Sources/PeripheralProxy.swift ================================================ // // PeripheralProxy.swift // // Copyright (c) 2016 Jordane Belanger // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. import CoreBluetooth final class PeripheralProxy: NSObject { static let defaultTimeoutInS: TimeInterval = 10 fileprivate lazy var readRSSIRequests: [ReadRSSIRequest] = [] fileprivate lazy var serviceRequests: [ServiceRequest] = [] fileprivate lazy var includedServicesRequests: [IncludedServicesRequest] = [] fileprivate lazy var characteristicRequests: [CharacteristicRequest] = [] fileprivate lazy var descriptorRequests: [DescriptorRequest] = [] fileprivate lazy var readCharacteristicRequests: [CBUUIDPath: [ReadCharacteristicRequest]] = [:] fileprivate lazy var readDescriptorRequests: [CBUUIDPath: [ReadDescriptorRequest]] = [:] fileprivate lazy var writeCharacteristicValueRequests: [CBUUIDPath: [WriteCharacteristicValueRequest]] = [:] fileprivate lazy var writeDescriptorValueRequests: [CBUUIDPath: [WriteDescriptorValueRequest]] = [:] fileprivate lazy var updateNotificationStateRequests: [CBUUIDPath: [UpdateNotificationStateRequest]] = [:] fileprivate weak var peripheral: Peripheral? let cbPeripheral: CBPeripheral // Peripheral that are no longer valid must be rediscovered again (happens when for example the Bluetooth is turned off // from a user's phone and turned back on var valid: Bool = true init(cbPeripheral: CBPeripheral, peripheral: Peripheral) { self.cbPeripheral = cbPeripheral self.peripheral = peripheral super.init() cbPeripheral.delegate = self NotificationCenter.default.addObserver(forName: Central.CentralCBPeripheralDisconnected, object: Central.sharedInstance, queue: nil) { [weak self] (notification) in if let identifier = notification.userInfo?["identifier"] as? UUID, identifier == self?.cbPeripheral.identifier { self?.postPeripheralEvent(Peripheral.PeripheralDisconnected, userInfo: notification.userInfo) } } NotificationCenter.default.addObserver(forName: Central.CentralStateChange, object: Central.sharedInstance, queue: nil) { [weak self] (notification) in if let state = notification.userInfo?["state"] as? CBManagerState, state == .poweredOff { self?.valid = false } } } deinit { NotificationCenter.default.removeObserver(self) } fileprivate func postPeripheralEvent(_ event: Notification.Name, userInfo: [AnyHashable: Any]?) { guard let peripheral = self.peripheral else { return } NotificationCenter.default.post( name: event, object: peripheral, userInfo: userInfo ) } } // MARK: Connect/Disconnect Requests extension PeripheralProxy { func connect(timeout: TimeInterval = 10, _ completion: @escaping ConnectPeripheralCallback) { if self.valid { Central.sharedInstance.connect(peripheral: self.cbPeripheral, timeout: timeout, completion: completion) } else { completion(.failure(SBError.invalidPeripheral)) } } func disconnect(_ completion: @escaping DisconnectPeripheralCallback) { Central.sharedInstance.disconnect(peripheral: self.cbPeripheral, completion: completion) } } // MARK: RSSI Requests private final class ReadRSSIRequest { let callback: ReadRSSIRequestCallback init(callback: @escaping ReadRSSIRequestCallback) { self.callback = callback } } extension PeripheralProxy { func readRSSI(_ completion: @escaping ReadRSSIRequestCallback) { self.connect { (result) in if let error = result.error { completion(.failure(error)) return } let request = ReadRSSIRequest(callback: completion) self.readRSSIRequests.append(request) if self.readRSSIRequests.count == 1 { self.runRSSIRequest() } } } fileprivate func runRSSIRequest() { guard let request = self.readRSSIRequests.first else { return } self.cbPeripheral.readRSSI() Timer.scheduledTimer( timeInterval: PeripheralProxy.defaultTimeoutInS, target: self, selector: #selector(self.onReadRSSIOperationTick), userInfo: Weak(value: request), repeats: false) } @objc fileprivate func onReadRSSIOperationTick(_ timer: Timer) { defer { if timer.isValid { timer.invalidate() } } let weakRequest = timer.userInfo as! Weak guard let request = weakRequest.value else { return } self.readRSSIRequests.removeFirst() request.callback(.failure(SBError.operationTimedOut(operation: .readRSSI))) self.runRSSIRequest() } } // MARK: Service requests private final class ServiceRequest { let serviceUUIDs: [CBUUID]? let callback: ServiceRequestCallback init(serviceUUIDs: [CBUUID]?, callback: @escaping ServiceRequestCallback) { self.callback = callback if let serviceUUIDs = serviceUUIDs { self.serviceUUIDs = serviceUUIDs } else { self.serviceUUIDs = nil } } } extension PeripheralProxy { func discoverServices(_ serviceUUIDs: [CBUUID]?, completion: @escaping ServiceRequestCallback) { self.connect { (result) in if let error = result.error { completion(.failure(error)) return } // Checking if the peripheral has already discovered the services requested if let serviceUUIDs = serviceUUIDs { let servicesTuple = self.cbPeripheral.servicesWithUUIDs(serviceUUIDs) if servicesTuple.missingServicesUUIDs.count == 0 { completion(.success(servicesTuple.foundServices)) return } } let request = ServiceRequest(serviceUUIDs: serviceUUIDs) { result in completion(result) } self.serviceRequests.append(request) if self.serviceRequests.count == 1 { self.runServiceRequest() } } } fileprivate func runServiceRequest() { guard let request = self.serviceRequests.first else { return } self.cbPeripheral.discoverServices(request.serviceUUIDs) Timer.scheduledTimer( timeInterval: PeripheralProxy.defaultTimeoutInS, target: self, selector: #selector(self.onServiceRequestTimerTick), userInfo: Weak(value: request), repeats: false) } @objc fileprivate func onServiceRequestTimerTick(_ timer: Timer) { defer { if timer.isValid { timer.invalidate() } } let weakRequest = timer.userInfo as! Weak // If the original rssi read operation callback is still there, this should mean the operation went // through and this timer can be ignored guard let request = weakRequest.value else { return } self.serviceRequests.removeFirst() request.callback(.failure(SBError.operationTimedOut(operation: .discoverServices))) self.runServiceRequest() } } // MARK: Included services Request private final class IncludedServicesRequest { let serviceUUIDs: [CBUUID]? let parentService: CBService let callback: ServiceRequestCallback init(serviceUUIDs: [CBUUID]?, forService service: CBService, callback: @escaping ServiceRequestCallback) { self.callback = callback if let serviceUUIDs = serviceUUIDs { self.serviceUUIDs = serviceUUIDs } else { self.serviceUUIDs = nil } self.parentService = service } } extension PeripheralProxy { func discoverIncludedServices(_ serviceUUIDs: [CBUUID]?, forService serviceUUID: CBUUID, completion: @escaping ServiceRequestCallback) { self.discoverServices([serviceUUID]) { result in if let error = result.error { completion(.failure(error)) return } let parentService = result.value!.first! let request = IncludedServicesRequest(serviceUUIDs: serviceUUIDs, forService: parentService) { result in completion(result) } self.includedServicesRequests.append(request) if self.includedServicesRequests.count == 1 { self.runIncludedServicesRequest() } } } fileprivate func runIncludedServicesRequest() { guard let request = self.includedServicesRequests.first else { return } self.cbPeripheral.discoverIncludedServices(request.serviceUUIDs, for: request.parentService) Timer.scheduledTimer( timeInterval: PeripheralProxy.defaultTimeoutInS, target: self, selector: #selector(self.onIncludedServicesRequestTimerTick), userInfo: Weak(value: request), repeats: false) } @objc fileprivate func onIncludedServicesRequestTimerTick(_ timer: Timer) { defer { if timer.isValid { timer.invalidate() } } let weakRequest = timer.userInfo as! Weak // If the original discover included services callback is still there, this means the operation went // through and this timer can be ignored guard let request = weakRequest.value else { return } self.includedServicesRequests.removeFirst() request.callback(.failure(SBError.operationTimedOut(operation: .discoverIncludedServices))) self.runIncludedServicesRequest() } } // MARK: Discover Characteristic requests private final class CharacteristicRequest{ let service: CBService let characteristicUUIDs: [CBUUID]? let callback: CharacteristicRequestCallback init(service: CBService, characteristicUUIDs: [CBUUID]?, callback: @escaping CharacteristicRequestCallback) { self.callback = callback self.service = service if let characteristicUUIDs = characteristicUUIDs { self.characteristicUUIDs = characteristicUUIDs } else { self.characteristicUUIDs = nil } } } extension PeripheralProxy { func discoverCharacteristics(_ characteristicUUIDs: [CBUUID]?, forService serviceUUID: CBUUID, completion: @escaping CharacteristicRequestCallback) { self.discoverServices([serviceUUID]) { result in if let error = result.error { completion(.failure(error)) return } // It would be a bug if we received an empty service array without an error from the discoverServices function // when asking for a specific service let service = result.value!.first! // Checking if this service already has the characteristic requested if let characteristicUUIDs = characteristicUUIDs { let characTuple = service.characteristicsWithUUIDs(characteristicUUIDs) if (characTuple.missingCharacteristicsUUIDs.count == 0) { completion(.success(characTuple.foundCharacteristics)) return } } let request = CharacteristicRequest(service: service, characteristicUUIDs: characteristicUUIDs) { result in completion(result) } self.characteristicRequests.append(request) if self.characteristicRequests.count == 1 { self.runCharacteristicRequest() } } } fileprivate func runCharacteristicRequest() { guard let request = self.characteristicRequests.first else { return } self.cbPeripheral.discoverCharacteristics(request.characteristicUUIDs, for: request.service) Timer.scheduledTimer( timeInterval: PeripheralProxy.defaultTimeoutInS, target: self, selector: #selector(self.onCharacteristicRequestTimerTick), userInfo: Weak(value: request), repeats: false) } @objc fileprivate func onCharacteristicRequestTimerTick(_ timer: Timer) { defer { if timer.isValid { timer.invalidate() } } let weakRequest = timer.userInfo as! Weak // If the original rssi read operation callback is still there, this should mean the operation went // through and this timer can be ignored guard let request = weakRequest.value else { return } self.characteristicRequests.removeFirst() request.callback(.failure(SBError.operationTimedOut(operation: .discoverCharacteristics))) self.runCharacteristicRequest() } } // MARK: Discover Descriptors requets private final class DescriptorRequest { let service: CBService let characteristic: CBCharacteristic let callback: DescriptorRequestCallback init(characteristic: CBCharacteristic, callback: @escaping DescriptorRequestCallback) { self.callback = callback self.service = characteristic.service! self.characteristic = characteristic } } extension PeripheralProxy { func discoverDescriptorsForCharacteristic(_ characteristicUUID: CBUUID, serviceUUID: CBUUID, completion: @escaping DescriptorRequestCallback) { self.discoverCharacteristics([characteristicUUID], forService: serviceUUID) { result in if let error = result.error { completion(.failure(error)) return } // It would be a terrible bug in the first place if the discover characteristic returned an empty array // with no error message when searching for a specific characteristic, I want to crash if it happens :) let characteristic = result.value!.first! let request = DescriptorRequest(characteristic: characteristic) { result in completion(result) } self.descriptorRequests.append(request) if self.descriptorRequests.count == 1 { self.runDescriptorRequest() } } } fileprivate func runDescriptorRequest() { guard let request = self.descriptorRequests.first else { return } self.cbPeripheral.discoverDescriptors(for: request.characteristic) Timer.scheduledTimer( timeInterval: PeripheralProxy.defaultTimeoutInS, target: self, selector: #selector(self.onDescriptorRequestTimerTick), userInfo: Weak(value: request), repeats: false) } @objc fileprivate func onDescriptorRequestTimerTick(_ timer: Timer) { defer { if timer.isValid { timer.invalidate() } } let weakRequest = timer.userInfo as! Weak guard let request = weakRequest.value else { return } self.descriptorRequests.removeFirst() request.callback(.failure(SBError.operationTimedOut(operation: .discoverDescriptors))) self.runDescriptorRequest() } } // MARK: Read Characteristic value requests private final class ReadCharacteristicRequest { let service: CBService let characteristic: CBCharacteristic let callback: ReadCharacRequestCallback init(characteristic: CBCharacteristic, callback: @escaping ReadCharacRequestCallback) { self.callback = callback self.service = characteristic.service! self.characteristic = characteristic } } extension PeripheralProxy { func readCharacteristic(_ characteristicUUID: CBUUID, serviceUUID: CBUUID, completion: @escaping ReadCharacRequestCallback) { self.discoverCharacteristics([characteristicUUID], forService: serviceUUID) { result in if let error = result.error { completion(.failure(error)) return } // Having no error yet not having the characteristic should never happen and would be considered a bug, // I'd rather crash here than not notice the bug let characteristic = result.value!.first! let request = ReadCharacteristicRequest(characteristic: characteristic) { result in completion(result) } let readPath = characteristic.uuidPath if var currentPathRequests = self.readCharacteristicRequests[readPath] { currentPathRequests.append(request) self.readCharacteristicRequests[readPath] = currentPathRequests } else { self.readCharacteristicRequests[readPath] = [request] self.runReadCharacteristicRequest(readPath) } } } fileprivate func runReadCharacteristicRequest(_ readPath: CBUUIDPath) { guard let request = self.readCharacteristicRequests[readPath]?.first else { return } self.cbPeripheral.readValue(for: request.characteristic) Timer.scheduledTimer( timeInterval: PeripheralProxy.defaultTimeoutInS, target: self, selector: #selector(self.onReadCharacteristicTimerTick), userInfo: Weak(value: request), repeats: false) } @objc fileprivate func onReadCharacteristicTimerTick(_ timer: Timer) { defer { if timer.isValid { timer.invalidate() } } let weakRequest = timer.userInfo as! Weak guard let request = weakRequest.value else { return } let readPath = request.characteristic.uuidPath self.readCharacteristicRequests[readPath]?.removeFirst() if self.readCharacteristicRequests[readPath]?.count == 0 { self.readCharacteristicRequests[readPath] = nil } request.callback(.failure(SBError.operationTimedOut(operation: .readCharacteristic))) self.runReadCharacteristicRequest(readPath) } } // MARK: Read Descriptor value requests private final class ReadDescriptorRequest { let service: CBService let characteristic: CBCharacteristic let descriptor: CBDescriptor let callback: ReadDescriptorRequestCallback init(descriptor: CBDescriptor, callback: @escaping ReadDescriptorRequestCallback) { self.callback = callback self.descriptor = descriptor self.characteristic = descriptor.characteristic! self.service = descriptor.characteristic!.service! } } extension PeripheralProxy { func readDescriptor(_ descriptorUUID: CBUUID, characteristicUUID: CBUUID, serviceUUID: CBUUID, completion: @escaping ReadDescriptorRequestCallback) { self.discoverDescriptorsForCharacteristic(characteristicUUID, serviceUUID: serviceUUID) { result in if let error = result.error { completion(.failure(error)) return } guard let descriptor = result.value?.first else { completion(.failure(SBError.peripheralDescriptorsNotFound(missingDescriptorsUUIDs: [descriptorUUID]))) return } let request = ReadDescriptorRequest(descriptor: descriptor, callback: completion) let readPath = descriptor.uuidPath if var currentPathRequests = self.readDescriptorRequests[readPath] { currentPathRequests.append(request) self.readDescriptorRequests[readPath] = currentPathRequests } else { self.readDescriptorRequests[readPath] = [request] self.runReadDescriptorRequest(readPath) } } } fileprivate func runReadDescriptorRequest(_ readPath: CBUUIDPath) { guard let request = self.readDescriptorRequests[readPath]?.first else { return } self.cbPeripheral.readValue(for: request.descriptor) Timer.scheduledTimer( timeInterval: PeripheralProxy.defaultTimeoutInS, target: self, selector: #selector(self.onReadDescriptorTimerTick), userInfo: Weak(value: request), repeats: false) } @objc fileprivate func onReadDescriptorTimerTick(_ timer: Timer) { defer { if timer.isValid { timer.invalidate() } } let weakRequest = timer.userInfo as! Weak guard let request = weakRequest.value else { return } let readPath = request.descriptor.uuidPath self.readDescriptorRequests[readPath]?.removeFirst() if self.readDescriptorRequests[readPath]?.count == 0 { self.readDescriptorRequests[readPath] = nil } request.callback(.failure(SBError.operationTimedOut(operation: .readDescriptor))) self.runReadDescriptorRequest(readPath) } } // MARK: Write Characteristic value requests private final class WriteCharacteristicValueRequest { let service: CBService let characteristic: CBCharacteristic let value: Data let type: CBCharacteristicWriteType let callback: WriteRequestCallback init(characteristic: CBCharacteristic, value: Data, type: CBCharacteristicWriteType, callback: @escaping WriteRequestCallback) { self.callback = callback self.value = value self.type = type self.characteristic = characteristic self.service = characteristic.service! } } extension PeripheralProxy { func writeCharacteristicValue(_ characteristicUUID: CBUUID, serviceUUID: CBUUID, value: Data, type: CBCharacteristicWriteType, completion: @escaping WriteRequestCallback) { self.discoverCharacteristics([characteristicUUID], forService: serviceUUID) { result in if let error = result.error { completion(.failure(error)) return } // Having no error yet not having the characteristic should never happen and would be considered a bug, // I'd rather crash here than not notice the bug hence the forced unwrap let characteristic = result.value!.first! let request = WriteCharacteristicValueRequest(characteristic: characteristic, value: value, type: type) { result in completion(result) } let writePath = characteristic.uuidPath if var currentPathRequests = self.writeCharacteristicValueRequests[writePath] { currentPathRequests.append(request) self.writeCharacteristicValueRequests[writePath] = currentPathRequests } else { self.writeCharacteristicValueRequests[writePath] = [request] self.runWriteCharacteristicValueRequest(writePath) } } } fileprivate func runWriteCharacteristicValueRequest(_ writePath: CBUUIDPath) { guard let request = self.writeCharacteristicValueRequests[writePath]?.first else { return } self.cbPeripheral.writeValue(request.value, for: request.characteristic, type: request.type) if request.type == CBCharacteristicWriteType.withResponse { Timer.scheduledTimer( timeInterval: PeripheralProxy.defaultTimeoutInS, target: self, selector: #selector(self.onWriteCharacteristicValueRequestTimerTick), userInfo: Weak(value: request), repeats: false) } else { // If no response is expected, we execute the callback and clear the request right away self.writeCharacteristicValueRequests[writePath]?.removeFirst() if self.writeCharacteristicValueRequests[writePath]?.count == 0 { self.writeCharacteristicValueRequests[writePath] = nil } request.callback(.success(())) self.runWriteCharacteristicValueRequest(writePath) } } @objc fileprivate func onWriteCharacteristicValueRequestTimerTick(_ timer: Timer) { defer { if timer.isValid { timer.invalidate() } } let weakRequest = timer.userInfo as! Weak guard let request = weakRequest.value else { return } let writePath = request.characteristic.uuidPath self.writeCharacteristicValueRequests[writePath]?.removeFirst() if self.writeCharacteristicValueRequests[writePath]?.count == 0 { self.writeCharacteristicValueRequests[writePath] = nil } request.callback(.failure(SBError.operationTimedOut(operation: .writeCharacteristic))) self.runWriteCharacteristicValueRequest(writePath) } } // MARK: Write Descriptor value requests private final class WriteDescriptorValueRequest { let service: CBService let characteristic: CBCharacteristic let descriptor: CBDescriptor let value: Data let callback: WriteRequestCallback init(descriptor: CBDescriptor, value: Data, callback: @escaping WriteRequestCallback) { self.callback = callback self.value = value self.descriptor = descriptor self.characteristic = descriptor.characteristic! self.service = descriptor.characteristic!.service! } } extension PeripheralProxy { func writeDescriptorValue(_ descriptorUUID: CBUUID, characteristicUUID: CBUUID, serviceUUID: CBUUID, value: Data, completion: @escaping WriteRequestCallback) { self.discoverDescriptorsForCharacteristic(characteristicUUID, serviceUUID: serviceUUID) { result in if let error = result.error { completion(.failure(error)) return } guard let descriptor = result.value?.filter({ $0.uuid == descriptorUUID }).first else { completion(.failure(SBError.peripheralDescriptorsNotFound(missingDescriptorsUUIDs: [descriptorUUID]))) return } let request = WriteDescriptorValueRequest(descriptor: descriptor, value: value) { result in completion(result) } let writePath = descriptor.uuidPath if var currentPathRequests = self.writeDescriptorValueRequests[writePath] { currentPathRequests.append(request) self.writeDescriptorValueRequests[writePath] = currentPathRequests } else { self.writeDescriptorValueRequests[writePath] = [request] self.runWriteDescriptorValueRequest(writePath) } } } fileprivate func runWriteDescriptorValueRequest(_ writePath: CBUUIDPath) { guard let request = self.writeDescriptorValueRequests[writePath]?.first else { return } self.cbPeripheral.writeValue(request.value, for: request.descriptor) Timer.scheduledTimer( timeInterval: PeripheralProxy.defaultTimeoutInS, target: self, selector: #selector(self.onWriteDescriptorValueRequestTimerTick), userInfo: Weak(value: request), repeats: false) } @objc fileprivate func onWriteDescriptorValueRequestTimerTick(_ timer: Timer) { defer { if timer.isValid { timer.invalidate() } } let weakRequest = timer.userInfo as! Weak guard let request = weakRequest.value else { return } let writePath = request.descriptor.uuidPath self.writeDescriptorValueRequests[writePath]?.removeFirst() if self.writeDescriptorValueRequests[writePath]?.count == 0 { self.writeDescriptorValueRequests[writePath] = nil } request.callback(.failure(SBError.operationTimedOut(operation: .writeDescriptor))) self.runWriteDescriptorValueRequest(writePath) } } // MARK: Update Characteristic Notification State requests private final class UpdateNotificationStateRequest { let service: CBService let characteristic: CBCharacteristic let enabled: Bool let callback: UpdateNotificationStateCallback init(enabled: Bool, characteristic: CBCharacteristic, callback: @escaping UpdateNotificationStateCallback) { self.enabled = enabled self.characteristic = characteristic self.service = characteristic.service! self.callback = callback } } extension PeripheralProxy { func setNotifyValueForCharacteristic(_ enabled: Bool, characteristicUUID: CBUUID, serviceUUID: CBUUID, completion: @escaping UpdateNotificationStateCallback) { self.discoverCharacteristics([characteristicUUID], forService: serviceUUID) { result in if let error = result.error { completion(.failure(error)) return } // Having no error yet not having the characteristic should never happen and would be considered a bug, // I'd rather crash here than not notice the bug hence the forced unwrap let characteristic = result.value!.first! let request = UpdateNotificationStateRequest(enabled: enabled, characteristic: characteristic) { result in completion(result) } let path = characteristic.uuidPath if var currentPathRequests = self.updateNotificationStateRequests[path] { currentPathRequests.append(request) self.updateNotificationStateRequests[path] = currentPathRequests } else { self.updateNotificationStateRequests[path] = [request] self.runUpdateNotificationStateRequest(path) } } } fileprivate func runUpdateNotificationStateRequest(_ path: CBUUIDPath) { guard let request = self.updateNotificationStateRequests[path]?.first else { return } self.cbPeripheral.setNotifyValue(request.enabled, for: request.characteristic) Timer.scheduledTimer( timeInterval: PeripheralProxy.defaultTimeoutInS, target: self, selector: #selector(self.onUpdateNotificationStateRequestTick), userInfo: Weak(value: request), repeats: false) } @objc fileprivate func onUpdateNotificationStateRequestTick(_ timer: Timer) { defer { if timer.isValid { timer.invalidate() } } let weakRequest = timer.userInfo as! Weak guard let request = weakRequest.value else { return } let path = request.characteristic.uuidPath self.updateNotificationStateRequests[path]?.removeFirst() if self.updateNotificationStateRequests[path]?.count == 0 { self.updateNotificationStateRequests[path] = nil } request.callback(.failure(SBError.operationTimedOut(operation: .updateNotificationStatus))) self.runUpdateNotificationStateRequest(path) } } // MARK: CBPeripheralDelegate extension PeripheralProxy: CBPeripheralDelegate { @objc func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) { guard let readRSSIRequest = self.readRSSIRequests.first else { return } self.readRSSIRequests.removeFirst() let result: Result = { if let error = error { return .failure(error) } else { return .success(RSSI.intValue) } }() readRSSIRequest.callback(result) self.runRSSIRequest() } @objc func peripheralDidUpdateName(_ peripheral: CBPeripheral) { var userInfo: [AnyHashable: Any]? if let name = peripheral.name { userInfo = ["name": name] } self.postPeripheralEvent(Peripheral.PeripheralNameUpdate, userInfo: userInfo) } @objc func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) { self.postPeripheralEvent(Peripheral.PeripheralModifedServices, userInfo: ["invalidatedServices": invalidatedServices]) } @objc func peripheral(_ peripheral: CBPeripheral, didDiscoverIncludedServicesFor service: CBService, error: Error?) { guard let includedServicesRequest = self.includedServicesRequests.first else { return } defer { self.runIncludedServicesRequest() } self.includedServicesRequests.removeFirst() if let error = error { includedServicesRequest.callback(.failure(error)) return } if let serviceUUIDs = includedServicesRequest.serviceUUIDs { let servicesTuple = peripheral.servicesWithUUIDs(serviceUUIDs) if servicesTuple.missingServicesUUIDs.count > 0 { includedServicesRequest.callback(.failure(SBError.peripheralServiceNotFound(missingServicesUUIDs: servicesTuple.missingServicesUUIDs))) } else { // This implies that all the services we're found through Set logic in the servicesWithUUIDs function includedServicesRequest.callback(.success(servicesTuple.foundServices)) } } else { includedServicesRequest.callback(.success(service.includedServices ?? [])) } } @objc func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { guard let serviceRequest = self.serviceRequests.first else { return } defer { self.runServiceRequest() } self.serviceRequests.removeFirst() if let error = error { serviceRequest.callback(.failure(error)) return } if let serviceUUIDs = serviceRequest.serviceUUIDs { let servicesTuple = peripheral.servicesWithUUIDs(serviceUUIDs) if servicesTuple.missingServicesUUIDs.count > 0 { serviceRequest.callback(.failure(SBError.peripheralServiceNotFound(missingServicesUUIDs: servicesTuple.missingServicesUUIDs))) } else { // This implies that all the services we're found through Set logic in the servicesWithUUIDs function serviceRequest.callback(.success(servicesTuple.foundServices)) } } else { serviceRequest.callback(.success(peripheral.services ?? [])) } } @objc func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { guard let characteristicRequest = self.characteristicRequests.first else { return } defer { self.runCharacteristicRequest() } self.characteristicRequests.removeFirst() if let error = error { characteristicRequest.callback(.failure(error)) return } if let characteristicUUIDs = characteristicRequest.characteristicUUIDs { let characteristicsTuple = service.characteristicsWithUUIDs(characteristicUUIDs) if characteristicsTuple.missingCharacteristicsUUIDs.count > 0 { characteristicRequest.callback(.failure(SBError.peripheralCharacteristicNotFound(missingCharacteristicsUUIDs: characteristicsTuple.missingCharacteristicsUUIDs))) } else { characteristicRequest.callback(.success(characteristicsTuple.foundCharacteristics)) } } else { characteristicRequest.callback(.success(service.characteristics ?? [])) } } @objc func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) { guard let descriptorRequest = self.descriptorRequests.first else { return } defer { self.runDescriptorRequest() } self.descriptorRequests.removeFirst() if let error = error { descriptorRequest.callback(.failure(error)) } else { descriptorRequest.callback(.success(characteristic.descriptors ?? [])) } } @objc func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { let readPath = characteristic.uuidPath guard let request = self.readCharacteristicRequests[readPath]?.first else { if characteristic.isNotifying { var userInfo: [AnyHashable: Any] = ["characteristic": characteristic] if let error = error { userInfo["error"] = error } self.postPeripheralEvent(Peripheral.PeripheralCharacteristicValueUpdate, userInfo: userInfo) } return } defer { self.runReadCharacteristicRequest(readPath) } self.readCharacteristicRequests[readPath]?.removeFirst() if self.readCharacteristicRequests[readPath]?.count == 0 { self.readCharacteristicRequests[readPath] = nil } if let error = error { request.callback(.failure(error)) } else { request.callback(.success(characteristic.value!)) } } @objc func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { let writePath = characteristic.uuidPath guard let request = self.writeCharacteristicValueRequests[writePath]?.first else { return } defer { self.runWriteCharacteristicValueRequest(writePath) } self.writeCharacteristicValueRequests[writePath]?.removeFirst() if self.writeCharacteristicValueRequests[writePath]?.count == 0 { self.writeCharacteristicValueRequests[writePath] = nil } if let error = error { request.callback(.failure(error)) } else { request.callback(.success(())) } } @objc func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { let path = characteristic.uuidPath guard let request = self.updateNotificationStateRequests[path]?.first else { return } defer { self.runUpdateNotificationStateRequest(path) } self.updateNotificationStateRequests[path]?.removeFirst() if self.updateNotificationStateRequests[path]?.count == 0 { self.updateNotificationStateRequests[path] = nil } if let error = error { request.callback(.failure(error)) } else { request.callback(.success(characteristic.isNotifying)) } } @objc func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) { let readPath = descriptor.uuidPath guard let request = self.readDescriptorRequests[readPath]?.first else { return } defer { self.runReadCharacteristicRequest(readPath) } self.readDescriptorRequests[readPath]?.removeFirst() if self.readDescriptorRequests[readPath]?.count == 0 { self.readDescriptorRequests[readPath] = nil } if let error = error { request.callback(.failure(error)) } else { do { let value = try DescriptorValue(descriptor: descriptor) request.callback(.success(value)) } catch let error { request.callback(.failure(error)) } } } @objc func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) { let writePath = descriptor.uuidPath guard let request = self.writeDescriptorValueRequests[writePath]?.first else { return } defer { self.runWriteDescriptorValueRequest(writePath) } self.writeDescriptorValueRequests[writePath]?.removeFirst() if self.writeDescriptorValueRequests[writePath]?.count == 0 { self.writeDescriptorValueRequests[writePath] = nil } if let error = error { request.callback(.failure(error)) } else { request.callback(.success(())) } } } ================================================ FILE: Sources/SBError.swift ================================================ // // SBError.swift // // Copyright (c) 2016 Jordane Belanger // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. import CoreBluetooth public enum SBError: Error { public enum SBBluetoothUnavailbleFailureReason { case unsupported case unauthorized case poweredOff case unknown } public enum SBOperation: String { case connectPeripheral = "Connect peripheral" case disconnectPeripheral = "Disconnect peripheral" case readRSSI = "Read RSSI" case discoverServices = "Discover services" case discoverIncludedServices = "Discover included services" case discoverCharacteristics = "Discover characteristics" case discoverDescriptors = "Discover descriptors" case readCharacteristic = "Read characteristic" case readDescriptor = "Read descriptor" case writeCharacteristic = "Write characteristic" case writeDescriptor = "Write descriptor" case updateNotificationStatus = "Update notification status" } case bluetoothUnavailable(reason: SBBluetoothUnavailbleFailureReason) case scanningEndedUnexpectedly case operationTimedOut(operation: SBOperation) case invalidPeripheral case peripheralFailedToConnectReasonUnknown case peripheralServiceNotFound(missingServicesUUIDs: [CBUUID]) case peripheralCharacteristicNotFound(missingCharacteristicsUUIDs: [CBUUID]) case peripheralDescriptorsNotFound(missingDescriptorsUUIDs: [CBUUID]) case invalidDescriptorValue(descriptor: CBDescriptor) } extension SBError: LocalizedError { public var errorDescription: String? { switch self { case .bluetoothUnavailable(let reason): return reason.localizedDescription case .scanningEndedUnexpectedly: return "Your peripheral scan ended unexpectedly." case .operationTimedOut(let operation): return "Bluetooth operation timed out: \(operation.rawValue)" case .invalidPeripheral: return "Invalid Bluetooth peripheral, you must rediscover this peripheral to use it again." case .peripheralFailedToConnectReasonUnknown: return "Failed to connect to your peripheral for an unknown reason." case .peripheralServiceNotFound(let missingUUIDs): let missingUUIDsString = missingUUIDs.map { $0.uuidString }.joined(separator: ",") return "Peripheral service(s) not found: \(missingUUIDsString)" case .peripheralCharacteristicNotFound(let missingUUIDs): let missingUUIDsString = missingUUIDs.map { $0.uuidString }.joined(separator: ",") return "Peripheral charac(s) not found: \(missingUUIDsString)" case .peripheralDescriptorsNotFound(let missingUUIDs): let missingUUIDsString = missingUUIDs.map { $0.uuidString }.joined(separator: ",") return "Peripheral descriptor(s) not found: \(missingUUIDsString)" case .invalidDescriptorValue(let descriptor): return "Failed to parse value for descriptor with uuid: \(descriptor.uuid.uuidString)" } } } extension SBError.SBBluetoothUnavailbleFailureReason { public var localizedDescription: String { switch self { case .unsupported: return "Your iOS device does not support Bluetooth." case .unauthorized: return "Unauthorized to use Bluetooth." case .poweredOff: return "Bluetooth is disabled, enable bluetooth and try again." case .unknown: return "Bluetooth is currently unavailable (unknown reason)." } } } ================================================ FILE: Sources/SwiftyBluetooth.h ================================================ // // SwiftyBluetooth.h // // Copyright (c) 2016 Jordane Belanger // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #import //! Project version number for SwiftyBluetooth. FOUNDATION_EXPORT double SwiftyBluetoothVersionNumber; //! Project version string for SwiftyBluetooth. FOUNDATION_EXPORT const unsigned char SwiftyBluetoothVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import ================================================ FILE: Sources/SwiftyBluetooth.swift ================================================ // // SwiftyBluetooth.swift // // Copyright (c) 2016 Jordane Belanger // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. import CoreBluetooth // MARK: Shorthands for the Central singleton instance interface /// Allows you to initially set the Central sharedInstance and use the restore /// identifier string of your choice for state preservation between app /// launches. Must be called before anything else from the library and can only be called once. @discardableResult public func setSharedCentralInstanceWith(restoreIdentifier: String) -> Central { return Central.setSharedInstanceWith(restoreIdentifier: restoreIdentifier) } /// Scans for Peripherals through a CBCentralManager scanForPeripheralsWithServices(...) function call. /// /// - Parameter timeout: The scanning time in seconds before the scan is stopped and the completion closure is called with a scanStopped result. /// - Parameter serviceUUIDs: The service UUIDs to search peripherals for or nil if looking for all peripherals. /// - Parameter completion: The closures, called multiple times throughout a scan. public func scanForPeripherals(withServiceUUIDs serviceUUIDs: [CBUUIDConvertible]? = nil, options: [String : Any]? = nil, timeoutAfter timeout: TimeInterval, completion: @escaping PeripheralScanCallback) { Central.sharedInstance.scanForPeripherals(withServiceUUIDs: serviceUUIDs, options: options, timeoutAfter: timeout, completion: completion) } /// Will stop the current scan through a CBCentralManager stopScan() function call and invokes the completion /// closures of the original scanWithTimeout function call with a scanStopped result containing an error if something went wrong. public func stopScan() { Central.sharedInstance.stopScan() } /// Sometimes, the bluetooth state of your iOS Device/CBCentralManagerState is in an inbetween state of either /// ".Unknown" or ".Reseting". This function will wait until the bluetooth state is stable and return a subset /// of the CBCentralManager state value which does not includes these values in its completion closure. public func asyncState(completion: @escaping AsyncCentralStateCallback) { Central.sharedInstance.asyncState(completion: completion) } /// The Central singleton CBCentralManager isScanning value public var isScanning: Bool { return Central.sharedInstance.isScanning } /// Attempts to return the periperals from a list of identifier "UUID"s public func retrievePeripherals(withUUIDs uuids: [UUID]) -> [Peripheral] { return Central.sharedInstance.retrievePeripherals(withUUIDs: uuids) } /// Attempts to return the connected peripheral having the specific service CBUUIDs public func retrieveConnectedPeripherals(withServiceUUIDs uuids: [CBUUIDConvertible]) -> [Peripheral] { return Central.sharedInstance.retrieveConnectedPeripherals(withServiceUUIDs: uuids) } ================================================ FILE: Sources/Util.swift ================================================ // // Util.swift // // Copyright (c) 2016 Jordane Belanger // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. import Foundation public final class Box { let value: T init(value: T) { self.value = value } } final class Weak { weak var value : T? init (value: T) { self.value = value } } extension UInt16 { init?(uncastedUnwrappedNSNumber: AnyObject?) { guard let numberValue = uncastedUnwrappedNSNumber as? NSNumber else { return nil } self = UInt16(truncatingIfNeeded: numberValue.intValue) } } extension Result { var value: Success? { return try? self.get() } var error: Error? { do { let _ = try self.get() return nil } catch { return error } } } ================================================ FILE: SwiftyBluetooth.podspec ================================================ Pod::Spec.new do |s| s.name = 'SwiftyBluetooth' s.version = '3.1.0' s.license = 'MIT' s.homepage = 'https://github.com/jordanebelanger/SwiftyBluetooth' s.authors = { 'Jordane Belanger' => 'jordane.belanger@gmail.com' } s.summary = 'Fully featured closures based library for CoreBluetooth' s.source = { :git => 'https://github.com/jordanebelanger/SwiftyBluetooth.git', :tag => s.version } s.source_files = 'Sources/*.swift' s.requires_arc = true s.ios.deployment_target = '10.0' s.osx.deployment_target = '10.15' s.tvos.deployment_target = '10.15' end ================================================ FILE: SwiftyBluetooth.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 096409BD1CC610CD00ADD4D9 /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 096409BC1CC610CD00ADD4D9 /* CoreBluetooth.framework */; }; DBDCCA6825192D1900A1912A /* SwiftyBluetooth.h in Headers */ = {isa = PBXBuildFile; fileRef = DBDCCA5B25192D1900A1912A /* SwiftyBluetooth.h */; settings = {ATTRIBUTES = (Public, ); }; }; DBDCCA6925192D1900A1912A /* CentralProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA5C25192D1900A1912A /* CentralProxy.swift */; }; DBDCCA6A25192D1900A1912A /* Peripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA5D25192D1900A1912A /* Peripheral.swift */; }; DBDCCA6B25192D1900A1912A /* CBUUIDConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA5E25192D1900A1912A /* CBUUIDConvertible.swift */; }; DBDCCA6C25192D1900A1912A /* Central.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA5F25192D1900A1912A /* Central.swift */; }; DBDCCA6D25192D1900A1912A /* PeripheralProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA6025192D1900A1912A /* PeripheralProxy.swift */; }; DBDCCA6E25192D1900A1912A /* SBError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA6125192D1900A1912A /* SBError.swift */; }; DBDCCA6F25192D1900A1912A /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA6225192D1900A1912A /* Util.swift */; }; DBDCCA7025192D1900A1912A /* SwiftyBluetooth.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA6325192D1900A1912A /* SwiftyBluetooth.swift */; }; DBDCCA7125192D1900A1912A /* CBUUIDPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA6425192D1900A1912A /* CBUUIDPath.swift */; }; DBDCCA7225192D1900A1912A /* DescriptorValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA6525192D1900A1912A /* DescriptorValue.swift */; }; DBDCCA7325192D1900A1912A /* CBExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA6625192D1900A1912A /* CBExtensions.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 096409AE1CC6103D00ADD4D9 /* SwiftyBluetooth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftyBluetooth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 096409BC1CC610CD00ADD4D9 /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; }; 09D5152E1D2DA8F900049B59 /* SwiftyBluetooth.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SwiftyBluetooth.podspec; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 09D515301D2DA90A00049B59 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 09D515311D2DA90A00049B59 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; DBDCCA5B25192D1900A1912A /* SwiftyBluetooth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwiftyBluetooth.h; sourceTree = ""; }; DBDCCA5C25192D1900A1912A /* CentralProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CentralProxy.swift; sourceTree = ""; }; DBDCCA5D25192D1900A1912A /* Peripheral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Peripheral.swift; sourceTree = ""; }; DBDCCA5E25192D1900A1912A /* CBUUIDConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBUUIDConvertible.swift; sourceTree = ""; }; DBDCCA5F25192D1900A1912A /* Central.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Central.swift; sourceTree = ""; }; DBDCCA6025192D1900A1912A /* PeripheralProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeripheralProxy.swift; sourceTree = ""; }; DBDCCA6125192D1900A1912A /* SBError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SBError.swift; sourceTree = ""; }; DBDCCA6225192D1900A1912A /* Util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = ""; }; DBDCCA6325192D1900A1912A /* SwiftyBluetooth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyBluetooth.swift; sourceTree = ""; }; DBDCCA6425192D1900A1912A /* CBUUIDPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBUUIDPath.swift; sourceTree = ""; }; DBDCCA6525192D1900A1912A /* DescriptorValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DescriptorValue.swift; sourceTree = ""; }; DBDCCA6625192D1900A1912A /* CBExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBExtensions.swift; sourceTree = ""; }; DBDCCA6725192D1900A1912A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 096409AA1CC6103D00ADD4D9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 096409BD1CC610CD00ADD4D9 /* CoreBluetooth.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 096409A41CC6103D00ADD4D9 = { isa = PBXGroup; children = ( DBDCCA5A25192D1900A1912A /* Sources */, 09D515301D2DA90A00049B59 /* LICENSE */, 09D515311D2DA90A00049B59 /* README.md */, 09D5152E1D2DA8F900049B59 /* SwiftyBluetooth.podspec */, 096409BF1CC610F100ADD4D9 /* Frameworks */, 096409AF1CC6103D00ADD4D9 /* Products */, ); sourceTree = ""; }; 096409AF1CC6103D00ADD4D9 /* Products */ = { isa = PBXGroup; children = ( 096409AE1CC6103D00ADD4D9 /* SwiftyBluetooth.framework */, ); name = Products; sourceTree = ""; }; 096409BF1CC610F100ADD4D9 /* Frameworks */ = { isa = PBXGroup; children = ( 096409BC1CC610CD00ADD4D9 /* CoreBluetooth.framework */, ); name = Frameworks; sourceTree = ""; }; DBDCCA5A25192D1900A1912A /* Sources */ = { isa = PBXGroup; children = ( DBDCCA5B25192D1900A1912A /* SwiftyBluetooth.h */, DBDCCA5C25192D1900A1912A /* CentralProxy.swift */, DBDCCA5D25192D1900A1912A /* Peripheral.swift */, DBDCCA5E25192D1900A1912A /* CBUUIDConvertible.swift */, DBDCCA5F25192D1900A1912A /* Central.swift */, DBDCCA6025192D1900A1912A /* PeripheralProxy.swift */, DBDCCA6125192D1900A1912A /* SBError.swift */, DBDCCA6225192D1900A1912A /* Util.swift */, DBDCCA6325192D1900A1912A /* SwiftyBluetooth.swift */, DBDCCA6425192D1900A1912A /* CBUUIDPath.swift */, DBDCCA6525192D1900A1912A /* DescriptorValue.swift */, DBDCCA6625192D1900A1912A /* CBExtensions.swift */, DBDCCA6725192D1900A1912A /* Info.plist */, ); path = Sources; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 096409AB1CC6103D00ADD4D9 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( DBDCCA6825192D1900A1912A /* SwiftyBluetooth.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 096409AD1CC6103D00ADD4D9 /* SwiftyBluetooth */ = { isa = PBXNativeTarget; buildConfigurationList = 096409B61CC6103D00ADD4D9 /* Build configuration list for PBXNativeTarget "SwiftyBluetooth" */; buildPhases = ( 096409A91CC6103D00ADD4D9 /* Sources */, 096409AA1CC6103D00ADD4D9 /* Frameworks */, 096409AB1CC6103D00ADD4D9 /* Headers */, 096409AC1CC6103D00ADD4D9 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = SwiftyBluetooth; productName = SwiftyBluetooth; productReference = 096409AE1CC6103D00ADD4D9 /* SwiftyBluetooth.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 096409A51CC6103D00ADD4D9 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1310; TargetAttributes = { 096409AD1CC6103D00ADD4D9 = { CreatedOnToolsVersion = 7.3; LastSwiftMigration = 1140; }; }; }; buildConfigurationList = 096409A81CC6103D00ADD4D9 /* Build configuration list for PBXProject "SwiftyBluetooth" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 096409A41CC6103D00ADD4D9; productRefGroup = 096409AF1CC6103D00ADD4D9 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 096409AD1CC6103D00ADD4D9 /* SwiftyBluetooth */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 096409AC1CC6103D00ADD4D9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 096409A91CC6103D00ADD4D9 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( DBDCCA6925192D1900A1912A /* CentralProxy.swift in Sources */, DBDCCA6E25192D1900A1912A /* SBError.swift in Sources */, DBDCCA7225192D1900A1912A /* DescriptorValue.swift in Sources */, DBDCCA6C25192D1900A1912A /* Central.swift in Sources */, DBDCCA6D25192D1900A1912A /* PeripheralProxy.swift in Sources */, DBDCCA7125192D1900A1912A /* CBUUIDPath.swift in Sources */, DBDCCA7025192D1900A1912A /* SwiftyBluetooth.swift in Sources */, DBDCCA6F25192D1900A1912A /* Util.swift in Sources */, DBDCCA6A25192D1900A1912A /* Peripheral.swift in Sources */, DBDCCA6B25192D1900A1912A /* CBUUIDConvertible.swift in Sources */, DBDCCA7325192D1900A1912A /* CBExtensions.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 096409B41CC6103D00ADD4D9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 096409B51CC6103D00ADD4D9 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 096409B71CC6103D00ADD4D9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.jordanebelanger.SwiftyBluetooth; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 5.0; }; name = Debug; }; 096409B81CC6103D00ADD4D9 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.jordanebelanger.SwiftyBluetooth; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 5.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 096409A81CC6103D00ADD4D9 /* Build configuration list for PBXProject "SwiftyBluetooth" */ = { isa = XCConfigurationList; buildConfigurations = ( 096409B41CC6103D00ADD4D9 /* Debug */, 096409B51CC6103D00ADD4D9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 096409B61CC6103D00ADD4D9 /* Build configuration list for PBXNativeTarget "SwiftyBluetooth" */ = { isa = XCConfigurationList; buildConfigurations = ( 096409B71CC6103D00ADD4D9 /* Debug */, 096409B81CC6103D00ADD4D9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 096409A51CC6103D00ADD4D9 /* Project object */; } ================================================ FILE: SwiftyBluetooth.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: SwiftyBluetooth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: SwiftyBluetooth.xcodeproj/xcshareddata/xcschemes/SwiftyBluetooth.xcscheme ================================================