├── .gitignore ├── .jazzy.yaml ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── LICENSE ├── Package.resolved ├── Package.swift ├── Podfile ├── Podfile.lock ├── Pods ├── Manifest.lock ├── Pods.xcodeproj │ └── project.pbxproj ├── Signals │ ├── LICENSE │ ├── README.md │ └── Signals │ │ ├── Signal.swift │ │ └── ios │ │ ├── AssociatedObject.swift │ │ ├── UIBarButtonItem+Signals.swift │ │ └── UIControl+Signals.swift └── Target Support Files │ ├── Pods-SwiftySensors iOS │ ├── Pods-SwiftySensors iOS-Info.plist │ ├── Pods-SwiftySensors iOS-acknowledgements.markdown │ ├── Pods-SwiftySensors iOS-acknowledgements.plist │ ├── Pods-SwiftySensors iOS-dummy.m │ ├── Pods-SwiftySensors iOS-umbrella.h │ ├── Pods-SwiftySensors iOS.debug.xcconfig │ ├── Pods-SwiftySensors iOS.modulemap │ └── Pods-SwiftySensors iOS.release.xcconfig │ ├── Pods-SwiftySensors macOS │ ├── Pods-SwiftySensors macOS-Info.plist │ ├── Pods-SwiftySensors macOS-acknowledgements.markdown │ ├── Pods-SwiftySensors macOS-acknowledgements.plist │ ├── Pods-SwiftySensors macOS-dummy.m │ ├── Pods-SwiftySensors macOS-umbrella.h │ ├── Pods-SwiftySensors macOS.debug.xcconfig │ ├── Pods-SwiftySensors macOS.modulemap │ └── Pods-SwiftySensors macOS.release.xcconfig │ ├── Pods-SwiftySensors tvOS │ ├── Pods-SwiftySensors tvOS-Info.plist │ ├── Pods-SwiftySensors tvOS-acknowledgements.markdown │ ├── Pods-SwiftySensors tvOS-acknowledgements.plist │ ├── Pods-SwiftySensors tvOS-dummy.m │ ├── Pods-SwiftySensors tvOS-umbrella.h │ ├── Pods-SwiftySensors tvOS.debug.xcconfig │ ├── Pods-SwiftySensors tvOS.modulemap │ └── Pods-SwiftySensors tvOS.release.xcconfig │ ├── Pods-SwiftySensorsExample │ ├── Pods-SwiftySensorsExample-Info.plist │ ├── Pods-SwiftySensorsExample-acknowledgements.markdown │ ├── Pods-SwiftySensorsExample-acknowledgements.plist │ ├── Pods-SwiftySensorsExample-dummy.m │ ├── Pods-SwiftySensorsExample-frameworks.sh │ ├── Pods-SwiftySensorsExample-umbrella.h │ ├── Pods-SwiftySensorsExample.debug.xcconfig │ ├── Pods-SwiftySensorsExample.modulemap │ └── Pods-SwiftySensorsExample.release.xcconfig │ ├── Signals-iOS │ ├── Signals-iOS-Info.plist │ ├── Signals-iOS-dummy.m │ ├── Signals-iOS-prefix.pch │ ├── Signals-iOS-umbrella.h │ ├── Signals-iOS.modulemap │ └── Signals-iOS.xcconfig │ ├── Signals-macOS │ ├── Signals-macOS-Info.plist │ ├── Signals-macOS-dummy.m │ ├── Signals-macOS-prefix.pch │ ├── Signals-macOS-umbrella.h │ ├── Signals-macOS.modulemap │ └── Signals-macOS.xcconfig │ └── Signals-tvOS │ ├── Signals-tvOS-Info.plist │ ├── Signals-tvOS-dummy.m │ ├── Signals-tvOS-prefix.pch │ ├── Signals-tvOS-umbrella.h │ ├── Signals-tvOS.modulemap │ └── Signals-tvOS.xcconfig ├── README.md ├── Sources └── SwiftySensors │ ├── Characteristic.swift │ ├── CoreBluetooth.swift │ ├── CyclingPowerSerializer.swift │ ├── CyclingPowerService.swift │ ├── CyclingSerializer.swift │ ├── CyclingSpeedCadenceSerializer.swift │ ├── CyclingSpeedCadenceService.swift │ ├── DeviceInformationService.swift │ ├── FitnessMachineSerializer.swift │ ├── FitnessMachineService.swift │ ├── HeartRateSerializer.swift │ ├── HeartRateService.swift │ ├── Info.plist │ ├── Operators.swift │ ├── RSSINormalizer.swift │ ├── Sensor.swift │ ├── SensorManager.swift │ └── Service.swift ├── SwiftySensors.podspec ├── SwiftySensors.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── SwiftySensors iOS.xcscheme │ ├── SwiftySensors macOS.xcscheme │ ├── SwiftySensors tvOS.xcscheme │ └── SwiftySensorsExample.xcscheme ├── SwiftySensors.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist └── SwiftySensorsExample ├── AppDelegate.swift ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── CharacteristicViewController.swift ├── Info.plist ├── SensorDetailsViewController.swift ├── SensorListViewController.swift └── ServiceDetailsViewController.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | .DS_Store 65 | /docs 66 | /Packages 67 | -------------------------------------------------------------------------------- /.jazzy.yaml: -------------------------------------------------------------------------------- 1 | xcodebuild_arguments: 2 | ["-workspace", "SwiftySensors.xcworkspace", "-scheme", "SwiftySensors iOS"] 3 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Kurt Kinetic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Signals", 6 | "repositoryURL": "https://github.com/artman/Signals", 7 | "state": { 8 | "branch": null, 9 | "revision": "e792eb51a64f85d043d17f09f6323b803b9f73b8", 10 | "version": "6.1.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "SwiftySensors", 6 | platforms: [.macOS(.v10_13), .iOS(.v9), .tvOS(.v9), .watchOS(.v4)], 7 | products: [ 8 | .library(name: "SwiftySensors", targets: ["SwiftySensors"]), 9 | ], 10 | dependencies: [ 11 | .package(url: "https://github.com/artman/Signals", from: Version(6, 1, 0)) 12 | ], 13 | targets: [ 14 | .target(name: "SwiftySensors", dependencies: ["Signals"]) 15 | ] 16 | ) 17 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'SwiftySensors iOS' do 4 | platform :ios, '8.4' 5 | 6 | pod 'Signals' 7 | end 8 | 9 | target 'SwiftySensors macOS' do 10 | platform :osx, '10.13' 11 | 12 | pod 'Signals' 13 | end 14 | 15 | target 'SwiftySensors tvOS' do 16 | platform :tvos, '11.2' 17 | 18 | pod 'Signals' 19 | end 20 | 21 | target 'SwiftySensorsExample' do 22 | platform :ios, '8.4' 23 | 24 | pod 'Signals' 25 | end 26 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Signals (6.1.0) 3 | 4 | DEPENDENCIES: 5 | - Signals 6 | 7 | SPEC REPOS: 8 | https://github.com/cocoapods/specs.git: 9 | - Signals 10 | 11 | SPEC CHECKSUMS: 12 | Signals: f8e5c979e93c35273f8dfe55ee7f47213d3e8fe8 13 | 14 | PODFILE CHECKSUM: 2cc4bf0a164b8af6ebe60a11c52a7345b4352456 15 | 16 | COCOAPODS: 1.7.0.beta.2 17 | -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Signals (6.1.0) 3 | 4 | DEPENDENCIES: 5 | - Signals 6 | 7 | SPEC REPOS: 8 | https://github.com/cocoapods/specs.git: 9 | - Signals 10 | 11 | SPEC CHECKSUMS: 12 | Signals: f8e5c979e93c35273f8dfe55ee7f47213d3e8fe8 13 | 14 | PODFILE CHECKSUM: 2cc4bf0a164b8af6ebe60a11c52a7345b4352456 15 | 16 | COCOAPODS: 1.7.0.beta.2 17 | -------------------------------------------------------------------------------- /Pods/Signals/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Tuomas Artman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pods/Signals/README.md: -------------------------------------------------------------------------------- 1 | # Signals 2 | [![Build Status](https://travis-ci.org/artman/Signals.svg?branch=master)](https://travis-ci.org/artman/Signals) 3 | [![Cocoapods Compatible](https://img.shields.io/cocoapods/v/Signals.svg)](https://cocoapods.org/pods/Signals) 4 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 5 | ![License](https://img.shields.io/cocoapods/l/Signals.svg?style=flat&color=gray) 6 | ![Platform](https://img.shields.io/cocoapods/p/Signals.svg?style=flat) 7 | [![Twitter](https://img.shields.io/badge/twitter-@artman-blue.svg?style=flat)](http://twitter.com/artman) 8 | 9 | Signals is a library for creating and observing events. It replaces delegates, actions and NSNotificationCenter with something much more powerful and elegant. 10 | 11 | ## Features 12 | 13 | - [x] Attach-and-forget observation 14 | - [x] Type-safety 15 | - [x] Filtered observation 16 | - [x] Delayed and queued observation 17 | - [x] Comprehensive Unit Test Coverage 18 | 19 | ## Requirements 20 | 21 | - iOS 7.0 / watchOS 2.0 / Mac OS X 10.9 22 | - Swift 4.2 23 | 24 | ## Installation 25 | 26 | To use Signals with a project targeting iOS 7, simply copy `Signals.swift` into your project. 27 | 28 | #### CocoaPods 29 | 30 | To integrate Signals into your project add the following to your `Podfile`: 31 | 32 | ```ruby 33 | platform :ios, '8.0' 34 | use_frameworks! 35 | 36 | pod 'Signals', '~> 6.0' 37 | ``` 38 | 39 | #### Carthage 40 | 41 | To integrate Signals into your project using Carthage add the following to your `Cartfile`: 42 | 43 | ```ruby 44 | github "artman/Signals" ~> 6.0 45 | ``` 46 | 47 | #### Swift Package Manager 48 | 49 | To integrate Signals into your project using SwiftPM add the following to your `Package.swift`: 50 | 51 | ```swift 52 | dependencies: [ 53 | .package(url: "https://github.com/artman/Signals", from: "6.0.0"), 54 | ], 55 | ``` 56 | 57 | ## Quick start 58 | 59 | Make events on a class observable by creating one or more signals: 60 | 61 | ```swift 62 | class NetworkLoader { 63 | 64 | // Creates a number of signals that can be subscribed to 65 | let onData = Signal<(data:NSData, error:NSError)>() 66 | let onProgress = Signal() 67 | 68 | ... 69 | 70 | func receivedData(receivedData:NSData, receivedError:NSError) { 71 | // Whenever appropriate, fire off any of the signals 72 | self.onProgress.fire(1.0) 73 | self.onData.fire((data:receivedData, error:receivedError)) 74 | } 75 | } 76 | ``` 77 | 78 | Subscribe to these signals from elsewhere in your application 79 | 80 | ```swift 81 | let networkLoader = NetworkLoader("http://artman.fi") 82 | 83 | networkLoader.onProgress.subscribe(with: self) { (progress) in 84 | print("Loading progress: \(progress*100)%") 85 | } 86 | 87 | networkLoader.onData.subscribe(with: self) { (data, error) in 88 | // Do something with the data 89 | } 90 | ``` 91 | 92 | Adding subscriptions to Signals is an attach-and-forget operation. If the subscribing object is deallocated, the `Signal` cancels the subscription, so you don't need to explicitly manage the cancellation of your subsciptions. 93 | 94 | Singals aren't restricted to one subscriber. Multiple objects can subscribe to the same Signal. 95 | 96 | You can also subscribe to events after they have occurred: 97 | 98 | ```swift 99 | networkLoader.onProgress.subscribePast(with: self) { (progress) in 100 | // This will immediately fire with last progress that was reported 101 | // by the onProgress signal 102 | println("Loading progress: \(progress*100)%") 103 | } 104 | ``` 105 | 106 | ### Advanced topics 107 | 108 | Signal subscriptions can apply filters: 109 | 110 | ```swift 111 | networkLoader.onProgress.subscribe(with: self) { (progress) in 112 | // This fires when progress is done 113 | }.filter { $0 == 1.0 } 114 | ``` 115 | 116 | You can sample up subscriptions to throttle how often you're subscription is executed, regardless how often the `Signal` fires: 117 | 118 | ```swift 119 | networkLoader.onProgress.subscribe(with: self) { (progress) in 120 | // Executed once per second while progress changes 121 | }.sample(every: 1.0) 122 | ``` 123 | 124 | By default, a subscription executes synchronously on the thread that fires the `Signal`. To change the default behaviour, you can use the `dispatchOnQueue` method to define the dispatch queue: 125 | 126 | ```swift 127 | networkLoader.onProgress.subscribe(with: self) { (progress) in 128 | // This fires on the main queue 129 | }.dispatchOnQueue(DispatchQueue.main) 130 | ``` 131 | 132 | If you don't like the double quotes when you fire signals that take tuples, you can use the custom `=>` operator to fire the data: 133 | 134 | ```swift 135 | // If you don't like the double quotes when firing signals that have tuples 136 | self.onData.fire((data:receivedData, error:receivedError)) 137 | 138 | // You can use the => operator to fire the signal 139 | self.onData => (data:receivedData, error:receivedError) 140 | 141 | // Also works for signals without tuples 142 | self.onProgress => 1.0 143 | ``` 144 | 145 | ## Replacing actions 146 | 147 | Signals extends all classes that extend from UIControl (not available on OS X) and lets you use Signals to listen to control events for increased code locality. 148 | 149 | ```swift 150 | let button = UIButton() 151 | button.onTouchUpInside.observe(with: self) { 152 | // Handle the touch 153 | } 154 | 155 | let slider = UISlider() 156 | slider.onValueChanged.observe(with: self) { 157 | // Handle value change 158 | } 159 | ``` 160 | 161 | ## Replacing delegates 162 | 163 | Signals is simple and modern and greatly reduce the amount of boilerplate that is required to set up delegation. 164 | 165 | Would you rather implement a callback using a delegate: 166 | 167 | - Create a protocol that defines what is delegated 168 | - Create a delegate property on the class that wants to provide delegation 169 | - Mark each class that wants to become a delegate as comforming to the delegate protocol 170 | - Implement the delegate methods on the class that want to become a delegate 171 | - Set the delegate property to become a delegate of the instance 172 | - Check that your delegate implements each delegate method before invoking it 173 | 174 | Or do the same thing with Signals: 175 | 176 | - Create a Signal for the class that wants to provide an event 177 | - Subscribe to the Signal 178 | 179 | ## Replace NotificationCenter 180 | 181 | When your team of engineers grows, NotificationCenter quickly becomes an anti-pattern. Global notifications with implicit data and no compiler safety easily make your code error-prone and hard to maintain and refactor. 182 | 183 | Replacing NotificationCenter with Signals will give you strong type safety enforced by the compiler that will help you maintain your code no matter how fast you move. 184 | 185 | ## Communication 186 | 187 | - If you **found a bug**, open an issue or submit a fix via a pull request. 188 | - If you **have a feature request**, open an issue or submit a implementation via a pull request or hit me up on Twitter [@artman](http://twitter.com/artman) 189 | - If you **want to contribute**, submit a pull request onto the master branch. 190 | 191 | ## License 192 | 193 | Signals is released under an MIT license. See the LICENSE file for more information 194 | -------------------------------------------------------------------------------- /Pods/Signals/Signals/Signal.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2014 - 2017 Tuomas Artman. All rights reserved. 3 | // 4 | 5 | import Foundation 6 | #if os(Linux) 7 | import Dispatch 8 | #endif 9 | 10 | /// Create instances of `Signal` and assign them to public constants on your class for each event type that your 11 | /// class fires. 12 | final public class Signal { 13 | 14 | public typealias SignalCallback = (T) -> Void 15 | 16 | /// The number of times the `Signal` has fired. 17 | public private(set) var fireCount: Int = 0 18 | 19 | /// Whether or not the `Signal` should retain a reference to the last data it was fired with. Defaults to false. 20 | public var retainLastData: Bool = false { 21 | didSet { 22 | if !retainLastData { 23 | lastDataFired = nil 24 | } 25 | } 26 | } 27 | 28 | /// The last data that the `Signal` was fired with. In order for the `Signal` to retain the last fired data, its 29 | /// `retainLastFired`-property needs to be set to true 30 | public private(set) var lastDataFired: T? = nil 31 | 32 | /// All the observers of to the `Signal`. 33 | public var observers:[AnyObject] { 34 | get { 35 | return signalListeners.filter { 36 | return $0.observer != nil 37 | }.map { 38 | (signal) -> AnyObject in 39 | return signal.observer! 40 | } 41 | } 42 | } 43 | 44 | private var signalListeners = [SignalSubscription]() 45 | 46 | /// Initializer. 47 | /// 48 | /// - parameter retainLastData: Whether or not the Signal should retain a reference to the last data it was fired 49 | /// with. Defaults to false. 50 | public init(retainLastData: Bool = false) { 51 | self.retainLastData = retainLastData 52 | } 53 | 54 | /// Subscribes an observer to the `Signal`. 55 | /// 56 | /// - parameter observer: The observer that subscribes to the `Signal`. Should the observer be deallocated, the 57 | /// subscription is automatically cancelled. 58 | /// - parameter callback: The closure to invoke whenever the `Signal` fires. 59 | /// - returns: A `SignalSubscription` that can be used to cancel or filter the subscription. 60 | @discardableResult 61 | public func subscribe(with observer: AnyObject, callback: @escaping SignalCallback) -> SignalSubscription { 62 | flushCancelledListeners() 63 | let signalListener = SignalSubscription(observer: observer, callback: callback) 64 | signalListeners.append(signalListener) 65 | return signalListener 66 | } 67 | 68 | 69 | /// Subscribes an observer to the `Signal`. The subscription is automatically canceled after the `Signal` has 70 | /// fired once. 71 | /// 72 | /// - parameter observer: The observer that subscribes to the `Signal`. Should the observer be deallocated, the 73 | /// subscription is automatically cancelled. 74 | /// - parameter callback: The closure to invoke when the signal fires for the first time. 75 | @discardableResult 76 | public func subscribeOnce(with observer: AnyObject, callback: @escaping SignalCallback) -> SignalSubscription { 77 | let signalListener = self.subscribe(with: observer, callback: callback) 78 | signalListener.once = true 79 | return signalListener 80 | } 81 | 82 | /// Subscribes an observer to the `Signal` and invokes its callback immediately with the last data fired by the 83 | /// `Signal` if it has fired at least once and if the `retainLastData` property has been set to true. 84 | /// 85 | /// - parameter observer: The observer that subscribes to the `Signal`. Should the observer be deallocated, the 86 | /// subscription is automatically cancelled. 87 | /// - parameter callback: The closure to invoke whenever the `Signal` fires. 88 | @discardableResult 89 | public func subscribePast(with observer: AnyObject, callback: @escaping SignalCallback) -> SignalSubscription { 90 | #if DEBUG 91 | signalsAssert(retainLastData, "can't subscribe to past events on Signal with retainLastData set to false") 92 | #endif 93 | let signalListener = self.subscribe(with: observer, callback: callback) 94 | if let lastDataFired = lastDataFired { 95 | signalListener.callback(lastDataFired) 96 | } 97 | return signalListener 98 | } 99 | 100 | /// Subscribes an observer to the `Signal` and invokes its callback immediately with the last data fired by the 101 | /// `Signal` if it has fired at least once and if the `retainLastData` property has been set to true. If it has 102 | /// not been fired yet, it will continue listening until it fires for the first time. 103 | /// 104 | /// - parameter observer: The observer that subscribes to the `Signal`. Should the observer be deallocated, the 105 | /// subscription is automatically cancelled. 106 | /// - parameter callback: The closure to invoke whenever the signal fires. 107 | @discardableResult 108 | public func subscribePastOnce(with observer: AnyObject, callback: @escaping SignalCallback) -> SignalSubscription { 109 | #if DEBUG 110 | signalsAssert(retainLastData, "can't subscribe to past events on Signal with retainLastData set to false") 111 | #endif 112 | let signalListener = self.subscribe(with: observer, callback: callback) 113 | if let lastDataFired = lastDataFired { 114 | signalListener.callback(lastDataFired) 115 | signalListener.cancel() 116 | } else { 117 | signalListener.once = true 118 | } 119 | return signalListener 120 | } 121 | 122 | /// Fires the `Singal`. 123 | /// 124 | /// - parameter data: The data to fire the `Signal` with. 125 | public func fire(_ data: T) { 126 | fireCount += 1 127 | lastDataFired = retainLastData ? data : nil 128 | flushCancelledListeners() 129 | 130 | for signalListener in signalListeners { 131 | if signalListener.filter == nil || signalListener.filter!(data) == true { 132 | _ = signalListener.dispatch(data: data) 133 | } 134 | } 135 | } 136 | 137 | /// Cancels all subscriptions for an observer. 138 | /// 139 | /// - parameter observer: The observer whose subscriptions to cancel 140 | public func cancelSubscription(for observer: AnyObject) { 141 | signalListeners = signalListeners.filter { 142 | if let definiteListener:AnyObject = $0.observer { 143 | return definiteListener !== observer 144 | } 145 | return false 146 | } 147 | } 148 | 149 | /// Cancels all subscriptions for the `Signal`. 150 | public func cancelAllSubscriptions() { 151 | signalListeners.removeAll(keepingCapacity: false) 152 | } 153 | 154 | /// Clears the last fired data from the `Signal` and resets the fire count. 155 | public func clearLastData() { 156 | lastDataFired = nil 157 | } 158 | 159 | // MARK: - Private Interface 160 | 161 | private func flushCancelledListeners() { 162 | var removeListeners = false 163 | for signalListener in signalListeners { 164 | if signalListener.observer == nil { 165 | removeListeners = true 166 | } 167 | } 168 | if removeListeners { 169 | signalListeners = signalListeners.filter { 170 | return $0.observer != nil 171 | } 172 | } 173 | } 174 | } 175 | 176 | public extension Signal where T == Void { 177 | func fire() { 178 | fire(()) 179 | } 180 | } 181 | 182 | /// A SignalLister represenents an instance and its association with a `Signal`. 183 | final public class SignalSubscription { 184 | public typealias SignalCallback = (T) -> Void 185 | public typealias SignalFilter = (T) -> Bool 186 | 187 | // The observer. 188 | weak public var observer: AnyObject? 189 | 190 | /// Whether the observer should be removed once it observes the `Signal` firing once. Defaults to false. 191 | public var once = false 192 | 193 | fileprivate var queuedData: T? 194 | fileprivate var filter: (SignalFilter)? 195 | fileprivate var callback: SignalCallback 196 | fileprivate var dispatchQueue: DispatchQueue? 197 | private var sampleInterval: TimeInterval? 198 | 199 | fileprivate init(observer: AnyObject, callback: @escaping SignalCallback) { 200 | self.observer = observer 201 | self.callback = callback 202 | } 203 | 204 | /// Assigns a filter to the `SignalSubscription`. This lets you define conditions under which a observer should actually 205 | /// receive the firing of a `Singal`. The closure that is passed an argument can decide whether the firing of a 206 | /// `Signal` should actually be dispatched to its observer depending on the data fired. 207 | /// 208 | /// If the closeure returns true, the observer is informed of the fire. The default implementation always 209 | /// returns `true`. 210 | /// 211 | /// - parameter predicate: A closure that can decide whether the `Signal` fire should be dispatched to its observer. 212 | /// - returns: Returns self so you can chain calls. 213 | @discardableResult 214 | public func filter(_ predicate: @escaping SignalFilter) -> SignalSubscription { 215 | self.filter = predicate 216 | return self 217 | } 218 | 219 | 220 | /// Tells the observer to sample received `Signal` data and only dispatch the latest data once the time interval 221 | /// has elapsed. This is useful if the subscriber wants to throttle the amount of data it receives from the 222 | /// `Signal`. 223 | /// 224 | /// - parameter sampleInterval: The number of seconds to delay dispatch. 225 | /// - returns: Returns self so you can chain calls. 226 | @discardableResult 227 | public func sample(every sampleInterval: TimeInterval) -> SignalSubscription { 228 | self.sampleInterval = sampleInterval 229 | return self 230 | } 231 | 232 | /// Assigns a dispatch queue to the `SignalSubscription`. The queue is used for scheduling the observer calls. If not 233 | /// nil, the callback is fired asynchronously on the specified queue. Otherwise, the block is run synchronously 234 | /// on the posting thread, which is its default behaviour. 235 | /// 236 | /// - parameter queue: A queue for performing the observer's calls. 237 | /// - returns: Returns self so you can chain calls. 238 | @discardableResult 239 | public func onQueue(_ queue: DispatchQueue) -> SignalSubscription { 240 | self.dispatchQueue = queue 241 | return self 242 | } 243 | 244 | /// Cancels the observer. This will cancelSubscription the listening object from the `Signal`. 245 | public func cancel() { 246 | self.observer = nil 247 | } 248 | 249 | // MARK: - Internal Interface 250 | 251 | func dispatch(data: T) -> Bool { 252 | guard observer != nil else { 253 | return false 254 | } 255 | 256 | if once { 257 | observer = nil 258 | } 259 | 260 | if let sampleInterval = sampleInterval { 261 | if queuedData != nil { 262 | queuedData = data 263 | } else { 264 | queuedData = data 265 | let block = { [weak self] () -> Void in 266 | if let definiteSelf = self { 267 | let data = definiteSelf.queuedData! 268 | definiteSelf.queuedData = nil 269 | if definiteSelf.observer != nil { 270 | definiteSelf.callback(data) 271 | } 272 | } 273 | } 274 | let dispatchQueue = self.dispatchQueue ?? DispatchQueue.main 275 | let deadline = DispatchTime.now() + DispatchTimeInterval.milliseconds(Int(sampleInterval * 1000)) 276 | dispatchQueue.asyncAfter(deadline: deadline, execute: block) 277 | } 278 | } else { 279 | if let queue = self.dispatchQueue { 280 | queue.async { 281 | self.callback(data) 282 | } 283 | } else { 284 | callback(data) 285 | } 286 | } 287 | 288 | return observer != nil 289 | } 290 | } 291 | 292 | infix operator => : AssignmentPrecedence 293 | 294 | /// Helper operator to fire signal data. 295 | public func => (signal: Signal, data: T) -> Void { 296 | signal.fire(data) 297 | } 298 | 299 | fileprivate func signalsAssert(_ condition: Bool, _ message: String) { 300 | #if DEBUG 301 | if let assertionHandlerOverride = assertionHandlerOverride { 302 | assertionHandlerOverride(condition, message) 303 | return 304 | } 305 | #endif 306 | assert(condition, message) 307 | } 308 | 309 | #if DEBUG 310 | var assertionHandlerOverride:((_ condition: Bool, _ message: String) -> ())? 311 | #endif 312 | -------------------------------------------------------------------------------- /Pods/Signals/Signals/ios/AssociatedObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2014 - 2017 Tuomas Artman. All rights reserved. 3 | // 4 | 5 | import ObjectiveC 6 | 7 | func setAssociatedObject(_ object: AnyObject, value: T, associativeKey: UnsafeRawPointer, policy: objc_AssociationPolicy) { 8 | objc_setAssociatedObject(object, associativeKey, value, policy) 9 | } 10 | 11 | func getAssociatedObject(_ object: AnyObject, associativeKey: UnsafeRawPointer) -> T? { 12 | if let valueAsType = objc_getAssociatedObject(object, associativeKey) as? T { 13 | return valueAsType 14 | } else { 15 | return nil 16 | } 17 | } 18 | 19 | func getOrCreateAssociatedObject(_ object: AnyObject, associativeKey: UnsafeRawPointer, defaultValue:T, policy: objc_AssociationPolicy) -> T { 20 | if let valueAsType: T = getAssociatedObject(object, associativeKey: associativeKey) { 21 | return valueAsType 22 | } 23 | setAssociatedObject(object, value: defaultValue, associativeKey: associativeKey, policy: policy) 24 | return defaultValue 25 | } 26 | -------------------------------------------------------------------------------- /Pods/Signals/Signals/ios/UIBarButtonItem+Signals.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIBarButtonItem+Signals.swift 3 | // Signals iOS 4 | // 5 | // Created by Linus Unnebäck on 2018-03-09. 6 | // Copyright © 2018 Tuomas Artman. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(tvOS) 10 | 11 | import UIKit 12 | 13 | /// Extends UIBarButtonItem with signal for the action. 14 | public extension UIBarButtonItem { 15 | /// A signal that fires for each action event. 16 | var onAction: Signal { 17 | return getOrCreateSignal() 18 | } 19 | 20 | // MARK: - Private interface 21 | 22 | private struct AssociatedKeys { 23 | static var SignalDictionaryKey = "signals_signalKey" 24 | } 25 | 26 | private func getOrCreateSignal() -> Signal { 27 | let key = "Action" 28 | let dictionary = getOrCreateAssociatedObject(self, associativeKey: &AssociatedKeys.SignalDictionaryKey, defaultValue: NSMutableDictionary(), policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 29 | 30 | if let signal = dictionary[key] as? Signal { 31 | return signal 32 | } else { 33 | let signal = Signal() 34 | dictionary[key] = signal 35 | self.target = self 36 | self.action = #selector(eventHandlerAction) 37 | return signal 38 | } 39 | } 40 | 41 | @objc private dynamic func eventHandlerAction() { 42 | getOrCreateSignal().fire() 43 | } 44 | } 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /Pods/Signals/Signals/ios/UIControl+Signals.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2014 - 2017 Tuomas Artman. All rights reserved. 3 | // 4 | 5 | #if os(iOS) || os(tvOS) 6 | 7 | import UIKit 8 | 9 | /// Extends UIControl with signals for all ui control events. 10 | public extension UIControl { 11 | /// A signal that fires for each touch down control event. 12 | var onTouchDown: Signal { 13 | return getOrCreateSignalForUIControlEvent(.touchDown) 14 | } 15 | 16 | /// A signal that fires for each touch down repeat control event. 17 | var onTouchDownRepeat: Signal { 18 | return getOrCreateSignalForUIControlEvent(.touchDownRepeat) 19 | } 20 | 21 | /// A signal that fires for each touch drag inside control event. 22 | var onTouchDragInside: Signal { 23 | return getOrCreateSignalForUIControlEvent(.touchDragInside) 24 | } 25 | 26 | /// A signal that fires for each touch drag outside control event. 27 | var onTouchDragOutside: Signal { 28 | return getOrCreateSignalForUIControlEvent(.touchDragOutside) 29 | } 30 | 31 | /// A signal that fires for each touch drag enter control event. 32 | var onTouchDragEnter: Signal { 33 | return getOrCreateSignalForUIControlEvent(.touchDragEnter) 34 | } 35 | 36 | /// A signal that fires for each touch drag exit control event. 37 | var onTouchDragExit: Signal { 38 | return getOrCreateSignalForUIControlEvent(.touchDragExit) 39 | } 40 | 41 | /// A signal that fires for each touch up inside control event. 42 | var onTouchUpInside: Signal { 43 | return getOrCreateSignalForUIControlEvent(.touchUpInside) 44 | } 45 | 46 | /// A signal that fires for each touch up outside control event. 47 | var onTouchUpOutside: Signal { 48 | return getOrCreateSignalForUIControlEvent(.touchUpOutside) 49 | } 50 | 51 | /// A signal that fires for each touch cancel control event. 52 | var onTouchCancel: Signal { 53 | return getOrCreateSignalForUIControlEvent(.touchCancel) 54 | } 55 | 56 | /// A signal that fires for each value changed control event. 57 | var onValueChanged: Signal { 58 | return getOrCreateSignalForUIControlEvent(.valueChanged) 59 | } 60 | 61 | /// A signal that fires for each editing did begin control event. 62 | var onEditingDidBegin: Signal { 63 | return getOrCreateSignalForUIControlEvent(.editingDidBegin) 64 | } 65 | 66 | /// A signal that fires for each editing changed control event. 67 | var onEditingChanged: Signal { 68 | return getOrCreateSignalForUIControlEvent(.editingChanged) 69 | } 70 | 71 | /// A signal that fires for each editing did end control event. 72 | var onEditingDidEnd: Signal { 73 | return getOrCreateSignalForUIControlEvent(.editingDidEnd) 74 | } 75 | 76 | /// A signal that fires for each editing did end on exit control event. 77 | var onEditingDidEndOnExit: Signal { 78 | return getOrCreateSignalForUIControlEvent(.editingDidEndOnExit) 79 | } 80 | 81 | // MARK: - Private interface 82 | 83 | private struct AssociatedKeys { 84 | static var SignalDictionaryKey = "signals_signalKey" 85 | } 86 | 87 | private static let eventToKey: [UIControl.Event: NSString] = [ 88 | .touchDown: "TouchDown", 89 | .touchDownRepeat: "TouchDownRepeat", 90 | .touchDragInside: "TouchDragInside", 91 | .touchDragOutside: "TouchDragOutside", 92 | .touchDragEnter: "TouchDragEnter", 93 | .touchDragExit: "TouchDragExit", 94 | .touchUpInside: "TouchUpInside", 95 | .touchUpOutside: "TouchUpOutside", 96 | .touchCancel: "TouchCancel", 97 | .valueChanged: "ValueChanged", 98 | .editingDidBegin: "EditingDidBegin", 99 | .editingChanged: "EditingChanged", 100 | .editingDidEnd: "EditingDidEnd", 101 | .editingDidEndOnExit: "EditingDidEndOnExit"] 102 | 103 | private func getOrCreateSignalForUIControlEvent(_ event: UIControl.Event) -> Signal { 104 | guard let key = UIControl.eventToKey[event] else { 105 | assertionFailure("Event type is not handled") 106 | return Signal() 107 | } 108 | let dictionary = getOrCreateAssociatedObject(self, associativeKey: &AssociatedKeys.SignalDictionaryKey, defaultValue: NSMutableDictionary(), policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 109 | 110 | if let signal = dictionary[key] as? Signal { 111 | return signal 112 | } else { 113 | let signal = Signal() 114 | dictionary[key] = signal 115 | self.addTarget(self, action: Selector("eventHandler\(key)"), for: event) 116 | return signal 117 | } 118 | } 119 | 120 | private func handleUIControlEvent(_ uiControlEvent: UIControl.Event) { 121 | getOrCreateSignalForUIControlEvent(uiControlEvent).fire() 122 | } 123 | 124 | @objc private dynamic func eventHandlerTouchDown() { 125 | handleUIControlEvent(.touchDown) 126 | } 127 | 128 | @objc private dynamic func eventHandlerTouchDownRepeat() { 129 | handleUIControlEvent(.touchDownRepeat) 130 | } 131 | 132 | @objc private dynamic func eventHandlerTouchDragInside() { 133 | handleUIControlEvent(.touchDragInside) 134 | } 135 | 136 | @objc private dynamic func eventHandlerTouchDragOutside() { 137 | handleUIControlEvent(.touchDragOutside) 138 | } 139 | 140 | @objc private dynamic func eventHandlerTouchDragEnter() { 141 | handleUIControlEvent(.touchDragEnter) 142 | } 143 | 144 | @objc private dynamic func eventHandlerTouchDragExit() { 145 | handleUIControlEvent(.touchDragExit) 146 | } 147 | 148 | @objc private dynamic func eventHandlerTouchUpInside() { 149 | handleUIControlEvent(.touchUpInside) 150 | } 151 | 152 | @objc private dynamic func eventHandlerTouchUpOutside() { 153 | handleUIControlEvent(.touchUpOutside) 154 | } 155 | 156 | @objc private dynamic func eventHandlerTouchCancel() { 157 | handleUIControlEvent(.touchCancel) 158 | } 159 | 160 | @objc private dynamic func eventHandlerValueChanged() { 161 | handleUIControlEvent(.valueChanged) 162 | } 163 | 164 | @objc private dynamic func eventHandlerEditingDidBegin() { 165 | handleUIControlEvent(.editingDidBegin) 166 | } 167 | 168 | @objc private dynamic func eventHandlerEditingChanged() { 169 | handleUIControlEvent(.editingChanged) 170 | } 171 | 172 | @objc private dynamic func eventHandlerEditingDidEnd() { 173 | handleUIControlEvent(.editingDidEnd) 174 | } 175 | 176 | @objc private dynamic func eventHandlerEditingDidEndOnExit() { 177 | handleUIControlEvent(.editingDidEndOnExit) 178 | } 179 | } 180 | 181 | extension UIControl.Event: Hashable { 182 | public var hashValue: Int { 183 | return Int(self.rawValue) 184 | } 185 | } 186 | 187 | #endif 188 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors iOS/Pods-SwiftySensors iOS-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors iOS/Pods-SwiftySensors iOS-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Signals 5 | 6 | The MIT License (MIT) 7 | 8 | Copyright (c) 2014 Tuomas Artman 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | Generated by CocoaPods - https://cocoapods.org 29 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors iOS/Pods-SwiftySensors iOS-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | The MIT License (MIT) 18 | 19 | Copyright (c) 2014 Tuomas Artman 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | 39 | License 40 | MIT 41 | Title 42 | Signals 43 | Type 44 | PSGroupSpecifier 45 | 46 | 47 | FooterText 48 | Generated by CocoaPods - https://cocoapods.org 49 | Title 50 | 51 | Type 52 | PSGroupSpecifier 53 | 54 | 55 | StringsTable 56 | Acknowledgements 57 | Title 58 | Acknowledgements 59 | 60 | 61 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors iOS/Pods-SwiftySensors iOS-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_SwiftySensors_iOS : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_SwiftySensors_iOS 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors iOS/Pods-SwiftySensors iOS-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_SwiftySensors_iOSVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_SwiftySensors_iOSVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors iOS/Pods-SwiftySensors iOS.debug.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-iOS" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-iOS/Signals.framework/Headers" 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks' 5 | OTHER_LDFLAGS = $(inherited) -framework "Signals" 6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors iOS/Pods-SwiftySensors iOS.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_SwiftySensors_iOS { 2 | umbrella header "Pods-SwiftySensors iOS-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors iOS/Pods-SwiftySensors iOS.release.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-iOS" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-iOS/Signals.framework/Headers" 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks' 5 | OTHER_LDFLAGS = $(inherited) -framework "Signals" 6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors macOS/Pods-SwiftySensors macOS-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors macOS/Pods-SwiftySensors macOS-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Signals 5 | 6 | The MIT License (MIT) 7 | 8 | Copyright (c) 2014 Tuomas Artman 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | Generated by CocoaPods - https://cocoapods.org 29 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors macOS/Pods-SwiftySensors macOS-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | The MIT License (MIT) 18 | 19 | Copyright (c) 2014 Tuomas Artman 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | 39 | License 40 | MIT 41 | Title 42 | Signals 43 | Type 44 | PSGroupSpecifier 45 | 46 | 47 | FooterText 48 | Generated by CocoaPods - https://cocoapods.org 49 | Title 50 | 51 | Type 52 | PSGroupSpecifier 53 | 54 | 55 | StringsTable 56 | Acknowledgements 57 | Title 58 | Acknowledgements 59 | 60 | 61 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors macOS/Pods-SwiftySensors macOS-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_SwiftySensors_macOS : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_SwiftySensors_macOS 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors macOS/Pods-SwiftySensors macOS-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_SwiftySensors_macOSVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_SwiftySensors_macOSVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors macOS/Pods-SwiftySensors macOS.debug.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-macOS" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-macOS/Signals.framework/Headers" 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/../Frameworks' '@loader_path/Frameworks' 5 | OTHER_LDFLAGS = $(inherited) -framework "Signals" 6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors macOS/Pods-SwiftySensors macOS.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_SwiftySensors_macOS { 2 | umbrella header "Pods-SwiftySensors macOS-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors macOS/Pods-SwiftySensors macOS.release.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-macOS" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-macOS/Signals.framework/Headers" 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/../Frameworks' '@loader_path/Frameworks' 5 | OTHER_LDFLAGS = $(inherited) -framework "Signals" 6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors tvOS/Pods-SwiftySensors tvOS-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors tvOS/Pods-SwiftySensors tvOS-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Signals 5 | 6 | The MIT License (MIT) 7 | 8 | Copyright (c) 2014 Tuomas Artman 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | Generated by CocoaPods - https://cocoapods.org 29 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors tvOS/Pods-SwiftySensors tvOS-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | The MIT License (MIT) 18 | 19 | Copyright (c) 2014 Tuomas Artman 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | 39 | License 40 | MIT 41 | Title 42 | Signals 43 | Type 44 | PSGroupSpecifier 45 | 46 | 47 | FooterText 48 | Generated by CocoaPods - https://cocoapods.org 49 | Title 50 | 51 | Type 52 | PSGroupSpecifier 53 | 54 | 55 | StringsTable 56 | Acknowledgements 57 | Title 58 | Acknowledgements 59 | 60 | 61 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors tvOS/Pods-SwiftySensors tvOS-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_SwiftySensors_tvOS : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_SwiftySensors_tvOS 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors tvOS/Pods-SwiftySensors tvOS-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_SwiftySensors_tvOSVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_SwiftySensors_tvOSVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors tvOS/Pods-SwiftySensors tvOS.debug.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-tvOS" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-tvOS/Signals.framework/Headers" 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks' 5 | OTHER_LDFLAGS = $(inherited) -framework "Signals" 6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors tvOS/Pods-SwiftySensors tvOS.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_SwiftySensors_tvOS { 2 | umbrella header "Pods-SwiftySensors tvOS-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensors tvOS/Pods-SwiftySensors tvOS.release.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-tvOS" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-tvOS/Signals.framework/Headers" 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks' 5 | OTHER_LDFLAGS = $(inherited) -framework "Signals" 6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensorsExample/Pods-SwiftySensorsExample-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensorsExample/Pods-SwiftySensorsExample-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Signals 5 | 6 | The MIT License (MIT) 7 | 8 | Copyright (c) 2014 Tuomas Artman 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | Generated by CocoaPods - https://cocoapods.org 29 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensorsExample/Pods-SwiftySensorsExample-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | The MIT License (MIT) 18 | 19 | Copyright (c) 2014 Tuomas Artman 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | 39 | License 40 | MIT 41 | Title 42 | Signals 43 | Type 44 | PSGroupSpecifier 45 | 46 | 47 | FooterText 48 | Generated by CocoaPods - https://cocoapods.org 49 | Title 50 | 51 | Type 52 | PSGroupSpecifier 53 | 54 | 55 | StringsTable 56 | Acknowledgements 57 | Title 58 | Acknowledgements 59 | 60 | 61 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensorsExample/Pods-SwiftySensorsExample-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_SwiftySensorsExample : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_SwiftySensorsExample 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensorsExample/Pods-SwiftySensorsExample-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | 23 | # Used as a return value for each invocation of `strip_invalid_archs` function. 24 | STRIP_BINARY_RETVAL=0 25 | 26 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 27 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 28 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 29 | 30 | # Copies and strips a vendored framework 31 | install_framework() 32 | { 33 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 34 | local source="${BUILT_PRODUCTS_DIR}/$1" 35 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 36 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 37 | elif [ -r "$1" ]; then 38 | local source="$1" 39 | fi 40 | 41 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 42 | 43 | if [ -L "${source}" ]; then 44 | echo "Symlinked..." 45 | source="$(readlink "${source}")" 46 | fi 47 | 48 | # Use filter instead of exclude so missing patterns don't throw errors. 49 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 50 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 51 | 52 | local basename 53 | basename="$(basename -s .framework "$1")" 54 | binary="${destination}/${basename}.framework/${basename}" 55 | 56 | if ! [ -r "$binary" ]; then 57 | binary="${destination}/${basename}" 58 | elif [ -L "${binary}" ]; then 59 | echo "Destination binary is symlinked..." 60 | dirname="$(dirname "${binary}")" 61 | binary="${dirname}/$(readlink "${binary}")" 62 | fi 63 | 64 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 65 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 66 | strip_invalid_archs "$binary" 67 | fi 68 | 69 | # Resign the code if required by the build settings to avoid unstable apps 70 | code_sign_if_enabled "${destination}/$(basename "$1")" 71 | 72 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 73 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 74 | local swift_runtime_libs 75 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 76 | for lib in $swift_runtime_libs; do 77 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 78 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 79 | code_sign_if_enabled "${destination}/${lib}" 80 | done 81 | fi 82 | } 83 | 84 | # Copies and strips a vendored dSYM 85 | install_dsym() { 86 | local source="$1" 87 | if [ -r "$source" ]; then 88 | # Copy the dSYM into a the targets temp dir. 89 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 90 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 91 | 92 | local basename 93 | basename="$(basename -s .framework.dSYM "$source")" 94 | binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}" 95 | 96 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 97 | if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then 98 | strip_invalid_archs "$binary" 99 | fi 100 | 101 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then 102 | # Move the stripped file into its final destination. 103 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 104 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 105 | else 106 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 107 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM" 108 | fi 109 | fi 110 | } 111 | 112 | # Copies the bcsymbolmap files of a vendored framework 113 | install_bcsymbolmap() { 114 | local bcsymbolmap_path="$1" 115 | local destination="${BUILT_PRODUCTS_DIR}" 116 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 117 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 118 | } 119 | 120 | # Signs a framework with the provided identity 121 | code_sign_if_enabled() { 122 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 123 | # Use the current code_sign_identity 124 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 125 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 126 | 127 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 128 | code_sign_cmd="$code_sign_cmd &" 129 | fi 130 | echo "$code_sign_cmd" 131 | eval "$code_sign_cmd" 132 | fi 133 | } 134 | 135 | # Strip invalid architectures 136 | strip_invalid_archs() { 137 | binary="$1" 138 | # Get architectures for current target binary 139 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 140 | # Intersect them with the architectures we are building for 141 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 142 | # If there are no archs supported by this binary then warn the user 143 | if [[ -z "$intersected_archs" ]]; then 144 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 145 | STRIP_BINARY_RETVAL=0 146 | return 147 | fi 148 | stripped="" 149 | for arch in $binary_archs; do 150 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 151 | # Strip non-valid architectures in-place 152 | lipo -remove "$arch" -output "$binary" "$binary" 153 | stripped="$stripped $arch" 154 | fi 155 | done 156 | if [[ "$stripped" ]]; then 157 | echo "Stripped $binary of architectures:$stripped" 158 | fi 159 | STRIP_BINARY_RETVAL=1 160 | } 161 | 162 | 163 | if [[ "$CONFIGURATION" == "Debug" ]]; then 164 | install_framework "${BUILT_PRODUCTS_DIR}/Signals-iOS/Signals.framework" 165 | fi 166 | if [[ "$CONFIGURATION" == "Release" ]]; then 167 | install_framework "${BUILT_PRODUCTS_DIR}/Signals-iOS/Signals.framework" 168 | fi 169 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 170 | wait 171 | fi 172 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensorsExample/Pods-SwiftySensorsExample-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_SwiftySensorsExampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_SwiftySensorsExampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensorsExample/Pods-SwiftySensorsExample.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-iOS" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-iOS/Signals.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "Signals" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensorsExample/Pods-SwiftySensorsExample.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_SwiftySensorsExample { 2 | umbrella header "Pods-SwiftySensorsExample-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SwiftySensorsExample/Pods-SwiftySensorsExample.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-iOS" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Signals-iOS/Signals.framework/Headers" 5 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 6 | OTHER_LDFLAGS = $(inherited) -framework "Signals" 7 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-iOS/Signals-iOS-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 6.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-iOS/Signals-iOS-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Signals_iOS : NSObject 3 | @end 4 | @implementation PodsDummy_Signals_iOS 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-iOS/Signals-iOS-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-iOS/Signals-iOS-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double SignalsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char SignalsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-iOS/Signals-iOS.modulemap: -------------------------------------------------------------------------------- 1 | framework module Signals { 2 | umbrella header "Signals-iOS-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-iOS/Signals-iOS.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Signals-iOS 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 4 | PODS_BUILD_DIR = ${BUILD_DIR} 5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 6 | PODS_ROOT = ${SRCROOT} 7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Signals 8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 9 | SKIP_INSTALL = YES 10 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-macOS/Signals-macOS-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 6.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-macOS/Signals-macOS-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Signals_macOS : NSObject 3 | @end 4 | @implementation PodsDummy_Signals_macOS 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-macOS/Signals-macOS-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-macOS/Signals-macOS-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double SignalsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char SignalsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-macOS/Signals-macOS.modulemap: -------------------------------------------------------------------------------- 1 | framework module Signals { 2 | umbrella header "Signals-macOS-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-macOS/Signals-macOS.xcconfig: -------------------------------------------------------------------------------- 1 | CODE_SIGN_IDENTITY = 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Signals-macOS 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Signals 9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 10 | SKIP_INSTALL = YES 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-tvOS/Signals-tvOS-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 6.1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-tvOS/Signals-tvOS-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Signals_tvOS : NSObject 3 | @end 4 | @implementation PodsDummy_Signals_tvOS 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-tvOS/Signals-tvOS-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-tvOS/Signals-tvOS-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double SignalsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char SignalsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-tvOS/Signals-tvOS.modulemap: -------------------------------------------------------------------------------- 1 | framework module Signals { 2 | umbrella header "Signals-tvOS-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Signals-tvOS/Signals-tvOS.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Signals-tvOS 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS 4 | PODS_BUILD_DIR = ${BUILD_DIR} 5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 6 | PODS_ROOT = ${SRCROOT} 7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Signals 8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 9 | SKIP_INSTALL = YES 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swifty Sensors 2 | ![iOS](https://img.shields.io/badge/iOS-8.2%2B-blue.svg) 3 | ![macOS](https://img.shields.io/badge/macOS-10.11%2B-blue.svg) 4 | ![Swift 3.0](https://img.shields.io/badge/swift-3.0-orange.svg) 5 | ![License](https://img.shields.io/badge/license-MIT-lightgrey.svg) 6 | [![CocoaPods](https://cocoapod-badges.herokuapp.com/v/SwiftySensors/badge.svg)](https://cocoapods.org/pods/SwiftySensors) 7 | 8 | Bluetooth LE Sensor Manager for iOS and macOS. 9 | 10 | [Full API Documentation](http://cocoadocs.org/docsets/SwiftySensors/) 11 | 12 | ## Installation 13 | ### CocoaPods 14 | ``` 15 | use_frameworks! 16 | pod 'SwiftySensors' 17 | ``` 18 | ### Manual 19 | Copy all of the swift files in the `Sources` directory into you project. 20 | 21 | ### Swift Package Manager 22 | Add this repo url to your dependencies list: 23 | ``` 24 | dependencies: [ 25 | .Package(url: "https://github.com/kinetic-fit/sensors-swift", Version(X, X, X)) 26 | ] 27 | ``` 28 | *Note: If you are using the [Swifty Sensors Kinetic Plugin](https://github.com/kinetic-fit/sensors-swift-kinetic), you cannot use the Swift Package Manager at this time due to no support for objective-c libraries.* 29 | 30 | ## Usage 31 | 32 | See the Example iOS App for a basic example of: 33 | - scanning for **sensors** 34 | - connecting to **sensors** 35 | - discovering **services** 36 | - discovering **characteristics** 37 | - reading values 38 | - **characteristic** notifications 39 | 40 | Initialization of a SensorManager is straightforward. 41 | 42 | 1. Set the **services** you want to scan for 43 | 2. Add additional **services** you want to discover on *sensor* (but not scan for in advertisement data) 44 | 3. Set the **scan mode** of the manager 45 | 46 | ``` 47 | // Customize what services you want to scan for 48 | SensorManager.instance.setServicesToScanFor([ 49 | CyclingPowerService.self, 50 | CyclingSpeedCadenceService.self, 51 | HeartRateService.self 52 | ]) 53 | 54 | // Add additional services we want to have access to (but don't want to specifically scan for) 55 | SensorManager.instance.addServiceTypes([DeviceInformationService.self]) 56 | 57 | // Set the scan mode (see documentation) 58 | SensorManager.instance.state = .aggressiveScan 59 | 60 | // Capture SwiftySensors log messages and print them to the console. You can inject your own logging system here if desired. 61 | SensorManager.logSensorMessage = { message in 62 | print(message) 63 | } 64 | ``` 65 | 66 | SwiftySensors uses [Signals](https://github.com/artman/Signals) to make observation of the various events easy. 67 | ``` 68 | // Subscribe to Sensor Discovery Events 69 | SensorManager.instance.onSensorDiscovered.subscribe(on: self) { sensor in 70 | // sensor has been discovered (but not connected to yet) 71 | } 72 | 73 | // Subscribe to value changes on a Characteristic 74 | characteristic.onValueUpdated.subscribe(on: self) { characteristic in 75 | // characteristic.value was just updated 76 | } 77 | ``` 78 | 79 | All Services and Characteristics are concrete classes to make working with Bluetooth LE sensors much easier. 80 | 81 | Example Heart Rate Sensor Hierarchy: 82 | ``` 83 | Sensor 84 | - HeartRateService 85 | - Measurement 86 | - BodySensorLocation 87 | - DeviceInformationService 88 | - SoftwareRevision 89 | - ModelNumber 90 | - SerialNumber 91 | - ... 92 | ``` 93 | 94 | To connect to a Sensor: 95 | ``` 96 | SensorManager.instance.connectToSensor(sensor) 97 | ``` 98 | 99 | Subscribing to value updates and getting the deserialized value of a Heart Rate Sensor: 100 | ``` 101 | // The sensor could be selected by a user, selected by a matching algorithm on the sensor's advertised services, etc. 102 | let sensor = < Heart Rate Sensor > 103 | 104 | // The function service() on a sensor will try to find the appropriate return type requested 105 | guard let hrService: HeartRateService = sensor.service() else { return } 106 | 107 | // The function characteristic() on a service will try to find the appropriate return type requested 108 | guard let hrMeasurement: HeartRateService.Measurement = hrService.characteristic() else { return } 109 | // ... the HeartRateService class also defines the `measurement` property, which is equivalent to the above 110 | 111 | hrMeasurement.onValueUpdated.subscribe(on: self) { characteristic in 112 | // The Measurement characteristic has a deserialized value of the sensor data 113 | let heartRate = hrMeasurement.currentMeasurement.heartRate 114 | } 115 | ``` 116 | 117 | ## Current Concrete Services and Characteristics 118 | - [Cycling Power](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.cycling_power.xml) 119 | - [Measurement](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.cycling_power_measurement.xml) 120 | - [Feature](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.cycling_power_feature.xml) 121 | - [Sensor Location](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.sensor_location.xml) 122 | - [Control Point](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.cycling_power_control_point.xml) 123 | - [Cycling Speed and Cadence](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.cycling_speed_and_cadence.xml) 124 | - [Measurement](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.csc_measurement.xml) 125 | - [Feature](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.csc_feature.xml) 126 | - [Sensor Location](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.sensor_location.xml) 127 | - [Heart Rate](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.heart_rate.xml) 128 | - [Measurement](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.heart_rate_measurement.xml) 129 | - [Body Sensor Location](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.body_sensor_location.xml) 130 | - [Control Point](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=heart_rate_control_point.xml) 131 | - [Device Information](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.device_information.xml) 132 | - [ManufacturerName](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.manufacturer_name_string.xml) 133 | - [ModelNumber](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.model_number_string.xml) 134 | - [SerialNumber](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.serial_number_string.xml) 135 | - [HardwareRevision](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.hardware_revision_string.xml) 136 | - [FirmwareRevision](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.firmware_revision_string.xml) 137 | - [SoftwareRevision](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.software_revision_string.xml) 138 | - [SystemID](https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.software_revision_string.xml) 139 | 140 | ### Extensions and 3rd Party Services 141 | - [Wahoo Trainer Characteristic Extension](https://github.com/kinetic-fit/sensors-swift-wahoo) for the Cycling Power Service 142 | - [Kinetic Sensors](https://github.com/kinetic-fit/sensors-swift-kinetic) 143 | 144 | ## Injecting Types; Writing Services, Characteristics, Extensions 145 | Adding custom functionality specific to your needs is fairly straightforward. 146 | 147 | ``` 148 | // Customize the Sensor class that the manager instantiates for each sensor 149 | SensorManager.instance.SensorType = < Custom Sensor Class : Extends Sensor > 150 | ``` 151 | 152 | Look at HeartRateService for a simple example of writing your own Service class. 153 | 154 | To add new Characteristic types to an existing Service that is not a part of the official spec, take a look at the [Wahoo Trainer Characteristic Extension](https://github.com/kinetic-fit/sensors-swift-wahoo). 155 | **This is NOT a normal solution adopted by BLE sensor manufaturers, but occassionally they break the rules.** 156 | 157 | ## Serializers 158 | The serialization / deserialization of characteristic data is isolated outside of the Characteristic classes and can be used alone. This can be useful if you already have a Sensor management stack and just need the logic to correctly deserialize various BLE messages. 159 | ``` 160 | use_frameworks! 161 | pod 'SwiftySensors/Serializers' 162 | ``` 163 | 164 | ## Known bugs 165 | None. 166 | 167 | ## ToDos 168 | There are many official BLE specs that need to be implemented. 169 | 170 | ## Projects Using SwiftySensors 171 | - [Kinetic Fit](https://itunes.apple.com/us/app/kinetic-fit/id1023388296?mt=8) 172 | 173 | Let us know if you want your App listed here! 174 | 175 | 176 | [Full API Documentation](http://cocoadocs.org/docsets/SwiftySensors/) 177 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/Characteristic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Characteristic.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import CoreBluetooth 11 | import Signals 12 | 13 | /** 14 | Base Characteristic Implementation. Extend this class with a concrete definition of a BLE characteristic. 15 | */ 16 | open class Characteristic { 17 | 18 | /// Parent Service 19 | public private(set) weak var service: Service? 20 | 21 | /// Value Updated Signal 22 | public let onValueUpdated = Signal() 23 | 24 | /// Value Written Signal 25 | public let onValueWritten = Signal() 26 | 27 | /// Backing CoreBluetooth Characteristic 28 | public internal(set) var cbCharacteristic: CBCharacteristic! 29 | 30 | /// Timestamp of when the Value was last updated 31 | public private(set) var valueUpdatedTimestamp: Double? 32 | 33 | /// Timestamp of when the Value was last written to 34 | public private(set) var valueWrittenTimestamp: Double? 35 | 36 | 37 | // Internal Constructor. SensorManager manages the instantiation and destruction of Characteristic objects 38 | /// :nodoc: 39 | required public init(service: Service, cbc: CBCharacteristic) { 40 | self.service = service 41 | self.cbCharacteristic = cbc 42 | } 43 | 44 | /** 45 | Called when the Value of the Characteristic was read. 46 | */ 47 | open func valueUpdated() { 48 | valueUpdatedTimestamp = Date.timeIntervalSinceReferenceDate 49 | onValueUpdated => self 50 | } 51 | 52 | /** 53 | Called when the Value of the Characteristic was successfully written. 54 | */ 55 | open func valueWritten() { 56 | valueWrittenTimestamp = Date.timeIntervalSinceReferenceDate 57 | onValueWritten => self 58 | } 59 | 60 | /** 61 | Initiate a Read of the Characteritic's Value. `valueUpdated` will be called and `onValueUpdated` will trigger when read. 62 | */ 63 | public func readValue() { 64 | cbCharacteristic.read() 65 | } 66 | 67 | /// The Value of the Characteristic 68 | public var value: Data? { 69 | return cbCharacteristic.value 70 | } 71 | 72 | } 73 | 74 | 75 | /** 76 | Base Implementation of a Characteristic with a UTF8 String value. Initiates a `readValue` on instantiation. 77 | */ 78 | open class UTF8Characteristic: Characteristic { 79 | 80 | /// The UTF8 Value of the Characteristic 81 | public var stringValue: String? { 82 | if let value = value { 83 | return String(data: value, encoding: String.Encoding.utf8) 84 | } 85 | return nil 86 | } 87 | 88 | /// :nodoc: 89 | required public init(service: Service, cbc: CBCharacteristic) { 90 | super.init(service: service, cbc: cbc) 91 | 92 | readValue() 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/CoreBluetooth.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreBluetooth.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import CoreBluetooth 11 | 12 | extension CBCharacteristic { 13 | 14 | /** 15 | Enable / Disable Notifications for Characteristic 16 | 17 | - parameter enabled: Notification Flag 18 | */ 19 | public func notify(_ enabled: Bool) { 20 | service.peripheral.setNotifyValue(enabled, for: self) 21 | } 22 | 23 | /// Read the value of the Characteristic 24 | public func read() { 25 | service.peripheral.readValue(for: self) 26 | } 27 | 28 | /** 29 | Write Data to the Characteristic 30 | 31 | - parameter data: Data to write 32 | - parameter writeType: BLE Write Type 33 | 34 | - returns: true if write performed 35 | */ 36 | @discardableResult public func write(_ data: Data, writeType: CBCharacteristicWriteType) -> Bool { 37 | if service.peripheral.state == .connected { 38 | service.peripheral.writeValue(data, for: self, type: writeType) 39 | return true 40 | } 41 | return false 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/CyclingPowerSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CyclingPowerSerializer.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | /// :nodoc: 13 | open class CyclingPowerSerializer { 14 | 15 | public struct Features: OptionSet { 16 | public let rawValue: UInt32 17 | 18 | public static let PedalPowerBalanceSupported = Features(rawValue: 1 << 0) 19 | public static let AccumulatedTorqueSupported = Features(rawValue: 1 << 1) 20 | public static let WheelRevolutionDataSupported = Features(rawValue: 1 << 2) 21 | public static let CrankRevolutionDataSupported = Features(rawValue: 1 << 3) 22 | public static let ExtremeMagnitudesSupported = Features(rawValue: 1 << 4) 23 | public static let ExtremeAnglesSupported = Features(rawValue: 1 << 5) 24 | public static let TopAndBottomDeadSpotAnglesSupported = Features(rawValue: 1 << 6) 25 | public static let AccumulatedEnergySupported = Features(rawValue: 1 << 7) 26 | public static let OffsetCompensationIndicatorSupported = Features(rawValue: 1 << 8) 27 | public static let OffsetCompensationSupported = Features(rawValue: 1 << 9) 28 | public static let ContentMaskingSupported = Features(rawValue: 1 << 10) 29 | public static let MultipleSensorLocationsSupported = Features(rawValue: 1 << 11) 30 | public static let CrankLengthAdjustmentSupported = Features(rawValue: 1 << 12) 31 | public static let ChainLengthAdjustmentSupported = Features(rawValue: 1 << 13) 32 | public static let ChainWeightAdjustmentSupported = Features(rawValue: 1 << 14) 33 | public static let SpanLengthAdjustmentSupported = Features(rawValue: 1 << 15) 34 | public static let SensorMeasurementContext = Features(rawValue: 1 << 16) 35 | public static let InstantaneousMeasurementDirectionSupported = Features(rawValue: 1 << 17) 36 | public static let FactoryCalibrationDateSupported = Features(rawValue: 1 << 18) 37 | 38 | public init(rawValue: UInt32) { 39 | self.rawValue = rawValue 40 | } 41 | } 42 | 43 | public static func readFeatures(_ data: Data) -> Features { 44 | let bytes = data.map { $0 } 45 | var rawFeatures: UInt32 = 0 46 | if bytes.count > 0 { rawFeatures |= UInt32(bytes[0]) } 47 | if bytes.count > 1 { rawFeatures |= UInt32(bytes[1]) << 8 } 48 | if bytes.count > 2 { rawFeatures |= UInt32(bytes[2]) << 16 } 49 | if bytes.count > 3 { rawFeatures |= UInt32(bytes[3]) << 24 } 50 | return Features(rawValue: rawFeatures) 51 | } 52 | 53 | struct MeasurementFlags: OptionSet { 54 | let rawValue: UInt16 55 | 56 | static let PedalPowerBalancePresent = MeasurementFlags(rawValue: 1 << 0) 57 | static let AccumulatedTorquePresent = MeasurementFlags(rawValue: 1 << 2) 58 | static let WheelRevolutionDataPresent = MeasurementFlags(rawValue: 1 << 4) 59 | static let CrankRevolutionDataPresent = MeasurementFlags(rawValue: 1 << 5) 60 | static let ExtremeForceMagnitudesPresent = MeasurementFlags(rawValue: 1 << 6) 61 | static let ExtremeTorqueMagnitudesPresent = MeasurementFlags(rawValue: 1 << 7) 62 | static let ExtremeAnglesPresent = MeasurementFlags(rawValue: 1 << 8) 63 | static let TopDeadSpotAnglePresent = MeasurementFlags(rawValue: 1 << 9) 64 | static let BottomDeadSpotAnglePresent = MeasurementFlags(rawValue: 1 << 10) 65 | static let AccumulatedEnergyPresent = MeasurementFlags(rawValue: 1 << 11) 66 | static let OffsetCompensationIndicator = MeasurementFlags(rawValue: 1 << 12) 67 | } 68 | 69 | public struct MeasurementData: CyclingMeasurementData { 70 | public var timestamp: Double = 0 71 | public var instantaneousPower: Int16 = 0 72 | public var pedalPowerBalance: UInt8? 73 | public var pedalPowerBalanceReference: Bool? 74 | public var accumulatedTorque: UInt16? 75 | 76 | public var cumulativeWheelRevolutions: UInt32? 77 | public var lastWheelEventTime: UInt16? 78 | 79 | public var cumulativeCrankRevolutions: UInt16? 80 | public var lastCrankEventTime: UInt16? 81 | 82 | public var maximumForceMagnitude: Int16? 83 | public var minimumForceMagnitude: Int16? 84 | public var maximumTorqueMagnitude: Int16? 85 | public var minimumTorqueMagnitude: Int16? 86 | public var maximumAngle: UInt16? 87 | public var minimumAngle: UInt16? 88 | public var topDeadSpotAngle: UInt16? 89 | public var bottomDeadSpotAngle: UInt16? 90 | public var accumulatedEnergy: UInt16? 91 | } 92 | 93 | 94 | public static func readMeasurement(_ data: Data) -> MeasurementData { 95 | var measurement = MeasurementData() 96 | 97 | let bytes = data.map { $0 } 98 | var index: Int = 0 99 | 100 | if bytes.count >= 2 { 101 | let rawFlags: UInt16 = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 102 | let flags = MeasurementFlags(rawValue: rawFlags) 103 | 104 | if bytes.count >= 4 { 105 | measurement.instantaneousPower = Int16(bytes[index++=]) | Int16(bytes[index++=]) << 8 106 | 107 | if flags.contains(.PedalPowerBalancePresent) && bytes.count >= index { 108 | measurement.pedalPowerBalance = bytes[index++=] 109 | measurement.pedalPowerBalanceReference = rawFlags & 0x2 == 0x2 110 | } 111 | 112 | if flags.contains(.AccumulatedTorquePresent) && bytes.count >= index + 1 { 113 | measurement.accumulatedTorque = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 114 | } 115 | 116 | if flags.contains(.WheelRevolutionDataPresent) && bytes.count >= index + 6 { 117 | var cumulativeWheelRevolutions = UInt32(bytes[index++=]) 118 | cumulativeWheelRevolutions |= UInt32(bytes[index++=]) << 8 119 | cumulativeWheelRevolutions |= UInt32(bytes[index++=]) << 16 120 | cumulativeWheelRevolutions |= UInt32(bytes[index++=]) << 24 121 | measurement.cumulativeWheelRevolutions = cumulativeWheelRevolutions 122 | measurement.lastWheelEventTime = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 123 | } 124 | 125 | if flags.contains(.CrankRevolutionDataPresent) && bytes.count >= index + 4 { 126 | measurement.cumulativeCrankRevolutions = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 127 | measurement.lastCrankEventTime = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 128 | } 129 | 130 | if flags.contains(.ExtremeForceMagnitudesPresent) && bytes.count >= index + 4 { 131 | measurement.maximumForceMagnitude = Int16(bytes[index++=]) | Int16(bytes[index++=]) << 8 132 | measurement.minimumForceMagnitude = Int16(bytes[index++=]) | Int16(bytes[index++=]) << 8 133 | } 134 | 135 | if flags.contains(.ExtremeTorqueMagnitudesPresent) && bytes.count >= index + 4 { 136 | measurement.maximumTorqueMagnitude = Int16(bytes[index++=]) | Int16(bytes[index++=]) << 8 137 | measurement.minimumTorqueMagnitude = Int16(bytes[index++=]) | Int16(bytes[index++=]) << 8 138 | } 139 | 140 | if flags.contains(.ExtremeAnglesPresent) && bytes.count >= index + 3 { 141 | // TODO: this bit shifting is not correct. 142 | measurement.minimumAngle = UInt16(bytes[index++=]) | UInt16(bytes[index] & 0xF0) << 4 143 | measurement.maximumAngle = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 4 144 | } 145 | 146 | if flags.contains(.TopDeadSpotAnglePresent) && bytes.count >= index + 2 { 147 | measurement.topDeadSpotAngle = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 148 | } 149 | 150 | if flags.contains(.BottomDeadSpotAnglePresent) && bytes.count >= index + 2 { 151 | measurement.bottomDeadSpotAngle = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 152 | } 153 | 154 | if flags.contains(.AccumulatedEnergyPresent) && bytes.count >= index + 2 { 155 | measurement.accumulatedEnergy = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 156 | } 157 | } 158 | } 159 | measurement.timestamp = Date.timeIntervalSinceReferenceDate 160 | return measurement 161 | } 162 | 163 | struct VectorFlags: OptionSet { 164 | let rawValue: UInt8 165 | 166 | static let CrankRevolutionDataPresent = VectorFlags(rawValue: 1 << 0) 167 | static let FirstCrankAnglePresent = VectorFlags(rawValue: 1 << 1) 168 | static let InstantaneousForcesPresent = VectorFlags(rawValue: 1 << 2) 169 | static let InstantaneousTorquesPresent = VectorFlags(rawValue: 1 << 3) 170 | } 171 | 172 | public struct VectorData { 173 | public enum MeasurementDirection { 174 | case unknown 175 | case tangentialComponent 176 | case radialComponent 177 | case lateralComponent 178 | } 179 | public var instantaneousMeasurementDirection: MeasurementDirection = .unknown 180 | public var cumulativeCrankRevolutions: UInt16? 181 | public var lastCrankEventTime: UInt16? 182 | public var firstCrankAngle: UInt16? 183 | public var instantaneousForce: [Int16]? 184 | public var instantaneousTorque: [Double]? 185 | } 186 | 187 | 188 | 189 | 190 | public static func readVector(_ data: Data) -> VectorData { 191 | var vector = VectorData() 192 | 193 | let bytes = data.map { $0 } 194 | 195 | 196 | let flags = VectorFlags(rawValue: bytes[0]) 197 | 198 | let measurementDirection = (bytes[0] & 0x30) >> 4 199 | switch measurementDirection { 200 | case 0: 201 | vector.instantaneousMeasurementDirection = .unknown 202 | case 1: 203 | vector.instantaneousMeasurementDirection = .tangentialComponent 204 | case 2: 205 | vector.instantaneousMeasurementDirection = .radialComponent 206 | case 3: 207 | vector.instantaneousMeasurementDirection = .lateralComponent 208 | default: 209 | vector.instantaneousMeasurementDirection = .unknown 210 | } 211 | var index: Int = 1 212 | 213 | if flags.contains(.CrankRevolutionDataPresent) { 214 | vector.cumulativeCrankRevolutions = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 215 | vector.lastCrankEventTime = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 216 | } 217 | if flags.contains(.FirstCrankAnglePresent) { 218 | vector.firstCrankAngle = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 219 | } 220 | 221 | // These two arrays are mutually exclusive 222 | if flags.contains(.InstantaneousForcesPresent) { 223 | 224 | } else if flags.contains(.InstantaneousTorquesPresent) { 225 | let torqueRaw = Int16(bytes[index++=]) | Int16(bytes[index++=]) << 8 226 | let torque = Double(torqueRaw) / 32.0 227 | print(torque) 228 | } 229 | 230 | return vector 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/CyclingPowerService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CyclingPowerSensor.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import CoreBluetooth 11 | import Signals 12 | 13 | // 14 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.cycling_power.xml 15 | // 16 | /// :nodoc: 17 | open class CyclingPowerService: Service, ServiceProtocol { 18 | 19 | public static var uuid: String { return "1818" } 20 | 21 | public static var characteristicTypes: Dictionary = [ 22 | Measurement.uuid: Measurement.self, 23 | Feature.uuid: Feature.self, 24 | Vector.uuid: Vector.self, 25 | SensorLocation.uuid: SensorLocation.self, 26 | ControlPoint.uuid: ControlPoint.self 27 | ] 28 | 29 | public var measurement: Measurement? { return characteristic() } 30 | 31 | public var feature: Feature? { return characteristic() } 32 | 33 | public var sensorLocation: SensorLocation? { return characteristic() } 34 | 35 | public var controlPoint: ControlPoint? { return characteristic() } 36 | 37 | 38 | // 39 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.cycling_power_measurement.xml 40 | // 41 | open class Measurement: Characteristic { 42 | 43 | public static let uuid: String = "2A63" 44 | 45 | open private(set) var instantaneousPower: UInt? 46 | 47 | open private(set) var speedKPH: Double? 48 | 49 | open private(set) var crankRPM: Double? 50 | 51 | open var wheelCircumferenceCM: Double = 213.3 52 | 53 | open private(set) var measurementData: CyclingPowerSerializer.MeasurementData? { 54 | didSet { 55 | guard let current = measurementData else { return } 56 | instantaneousPower = UInt(current.instantaneousPower) 57 | 58 | guard let previous = oldValue else { return } 59 | speedKPH = CyclingSerializer.calculateWheelKPH(current, previous: previous, wheelCircumferenceCM: wheelCircumferenceCM, wheelTimeResolution: 2048) 60 | crankRPM = CyclingSerializer.calculateCrankRPM(current, previous: previous) 61 | } 62 | } 63 | 64 | required public init(service: Service, cbc: CBCharacteristic) { 65 | super.init(service: service, cbc: cbc) 66 | 67 | cbCharacteristic.notify(true) 68 | } 69 | 70 | override open func valueUpdated() { 71 | // cbCharacteristic is nil? 72 | if let value = cbCharacteristic.value { 73 | measurementData = CyclingPowerSerializer.readMeasurement(value) 74 | } 75 | super.valueUpdated() 76 | } 77 | 78 | } 79 | 80 | // 81 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.cycling_power_vector.xml 82 | // 83 | open class Vector: Characteristic { 84 | 85 | public static let uuid: String = "2A64" 86 | 87 | open private(set) var vectorData: CyclingPowerSerializer.VectorData? { 88 | didSet { 89 | // guard let current = measurementData else { return } 90 | // instantaneousPower = UInt(current.instantaneousPower) 91 | // 92 | // guard let previous = oldValue else { return } 93 | // speedKPH = CyclingSerializer.calculateWheelKPH(current, previous: previous, wheelCircumferenceCM: wheelCircumferenceCM, wheelTimeResolution: 2048) 94 | // crankRPM = CyclingSerializer.calculateCrankRPM(current, previous: previous) 95 | } 96 | } 97 | 98 | required public init(service: Service, cbc: CBCharacteristic) { 99 | super.init(service: service, cbc: cbc) 100 | 101 | cbCharacteristic.notify(true) 102 | } 103 | 104 | override open func valueUpdated() { 105 | if let value = cbCharacteristic.value { 106 | vectorData = CyclingPowerSerializer.readVector(value) 107 | } 108 | super.valueUpdated() 109 | } 110 | 111 | } 112 | 113 | // 114 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.cycling_power_feature.xml 115 | // 116 | open class Feature: Characteristic { 117 | 118 | public static let uuid: String = "2A65" 119 | 120 | open private(set) var features: CyclingPowerSerializer.Features? 121 | 122 | required public init(service: Service, cbc: CBCharacteristic) { 123 | super.init(service: service, cbc: cbc) 124 | 125 | cbCharacteristic.read() 126 | } 127 | 128 | override open func valueUpdated() { 129 | if let value = cbCharacteristic.value { 130 | features = CyclingPowerSerializer.readFeatures(value) 131 | } 132 | 133 | super.valueUpdated() 134 | 135 | if let service = service { 136 | service.sensor.onServiceFeaturesIdentified => (service.sensor, service) 137 | } 138 | } 139 | } 140 | 141 | 142 | 143 | // 144 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.sensor_location.xml 145 | // 146 | open class SensorLocation: Characteristic { 147 | 148 | public static let uuid: String = "2A5D" 149 | 150 | open private(set) var location: CyclingSerializer.SensorLocation? 151 | 152 | required public init(service: Service, cbc: CBCharacteristic) { 153 | super.init(service: service, cbc: cbc) 154 | 155 | cbCharacteristic.read() 156 | } 157 | 158 | override open func valueUpdated() { 159 | if let value = cbCharacteristic.value { 160 | location = CyclingSerializer.readSensorLocation(value) 161 | } 162 | super.valueUpdated() 163 | } 164 | } 165 | 166 | 167 | 168 | // 169 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.cycling_power_control_point.xml 170 | // 171 | open class ControlPoint: Characteristic { 172 | 173 | public static let uuid: String = "2A66" 174 | 175 | static let writeType = CBCharacteristicWriteType.withResponse 176 | 177 | required public init(service: Service, cbc: CBCharacteristic) { 178 | super.init(service: service, cbc: cbc) 179 | 180 | cbCharacteristic.notify(true) 181 | } 182 | 183 | override open func valueUpdated() { 184 | // TODO: Process this response 185 | super.valueUpdated() 186 | } 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/CyclingSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CyclingSerializer.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | 13 | /// :nodoc: 14 | public protocol CyclingMeasurementData { 15 | var timestamp: Double { get } 16 | var cumulativeWheelRevolutions: UInt32? { get } 17 | var lastWheelEventTime: UInt16? { get } 18 | var cumulativeCrankRevolutions: UInt16? { get } 19 | var lastCrankEventTime: UInt16? { get } 20 | } 21 | 22 | /// :nodoc: 23 | open class CyclingSerializer { 24 | 25 | public enum SensorLocation: UInt8 { 26 | case other = 0 27 | case topOfShoe = 1 28 | case inShoe = 2 29 | case hip = 3 30 | case frontWheel = 4 31 | case leftCrank = 5 32 | case rightCrank = 6 33 | case leftPedal = 7 34 | case rightPedal = 8 35 | case frontHub = 9 36 | case rearDropout = 10 37 | case chainstay = 11 38 | case rearWheel = 12 39 | case rearHub = 13 40 | case chest = 14 41 | case spider = 15 42 | case chainRing = 16 43 | } 44 | 45 | public static func readSensorLocation(_ data: Data) -> SensorLocation? { 46 | let bytes = data.map { $0 } 47 | return SensorLocation(rawValue: bytes[0]) 48 | } 49 | 50 | public static func calculateWheelKPH(_ current: CyclingMeasurementData, previous: CyclingMeasurementData, wheelCircumferenceCM: Double, wheelTimeResolution: Int) -> Double? { 51 | guard let cwr1 = current.cumulativeWheelRevolutions else { return nil } 52 | guard let cwr2 = previous.cumulativeWheelRevolutions else { return nil } 53 | guard let lwet1 = current.lastWheelEventTime else { return nil } 54 | guard let lwet2 = previous.lastWheelEventTime else { return nil } 55 | 56 | let wheelRevsDelta: UInt32 = deltaWithRollover(cwr1, old: cwr2, max: UInt32.max) 57 | let wheelTimeDelta: UInt16 = deltaWithRollover(lwet1, old: lwet2, max: UInt16.max) 58 | 59 | let wheelTimeSeconds = Double(wheelTimeDelta) / Double(wheelTimeResolution) 60 | if wheelTimeSeconds > 0 { 61 | let wheelRPM = Double(wheelRevsDelta) / (wheelTimeSeconds / 60) 62 | let cmPerKm = 0.00001 63 | let minsPerHour = 60.0 64 | return wheelRPM * wheelCircumferenceCM * cmPerKm * minsPerHour 65 | } 66 | return 0 67 | } 68 | 69 | public static func calculateCrankRPM(_ current: CyclingMeasurementData, previous: CyclingMeasurementData) -> Double? { 70 | guard let ccr1 = current.cumulativeCrankRevolutions else { return nil } 71 | guard let ccr2 = previous.cumulativeCrankRevolutions else { return nil } 72 | guard let lcet1 = current.lastCrankEventTime else { return nil } 73 | guard let lcet2 = previous.lastCrankEventTime else { return nil } 74 | 75 | let crankRevsDelta: UInt16 = deltaWithRollover(ccr1, old: ccr2, max: UInt16.max) 76 | let crankTimeDelta: UInt16 = deltaWithRollover(lcet1, old: lcet2, max: UInt16.max) 77 | 78 | let crankTimeSeconds = Double(crankTimeDelta) / 1024 79 | if crankTimeSeconds > 0 { 80 | return Double(crankRevsDelta) / (crankTimeSeconds / 60) 81 | } 82 | return 0 83 | } 84 | 85 | private static func deltaWithRollover(_ new: T, old: T, max: T) -> T { 86 | return old > new ? max - old + new : new - old 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/CyclingSpeedCadenceSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CyclingSpeedCadenceSerializer.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | /// :nodoc: 13 | open class CyclingSpeedCadenceSerializer { 14 | 15 | struct MeasurementFlags: OptionSet { 16 | let rawValue: UInt8 17 | 18 | static let WheelRevolutionDataPresent = MeasurementFlags(rawValue: 1 << 0) 19 | static let CrankRevolutionDataPresent = MeasurementFlags(rawValue: 1 << 1) 20 | } 21 | 22 | public struct Features: OptionSet { 23 | public let rawValue: UInt16 24 | 25 | public static let WheelRevolutionDataSupported = Features(rawValue: 1 << 0) 26 | public static let CrankRevolutionDataSupported = Features(rawValue: 1 << 1) 27 | public static let MultipleSensorLocationsSupported = Features(rawValue: 1 << 2) 28 | 29 | public init(rawValue: UInt16) { 30 | self.rawValue = rawValue 31 | } 32 | } 33 | 34 | public struct MeasurementData: CyclingMeasurementData, CustomDebugStringConvertible { 35 | public var timestamp: Double = 0 36 | public var cumulativeWheelRevolutions: UInt32? 37 | public var lastWheelEventTime: UInt16? 38 | public var cumulativeCrankRevolutions: UInt16? 39 | public var lastCrankEventTime: UInt16? 40 | 41 | public var debugDescription: String { 42 | return "\(cumulativeWheelRevolutions ?? 0) \(lastWheelEventTime ?? 0) \(cumulativeCrankRevolutions ?? 0) \(lastCrankEventTime ?? 0)" 43 | } 44 | } 45 | 46 | 47 | public static func readFeatures(_ data: Data) -> Features { 48 | let bytes = data.map { $0 } 49 | let rawFeatures: UInt16 = UInt16(bytes[0]) | UInt16(bytes[1]) << 8 50 | return Features(rawValue: rawFeatures) 51 | } 52 | 53 | public static func readMeasurement(_ data: Data) -> MeasurementData { 54 | var measurement = MeasurementData() 55 | 56 | let bytes = data.map { $0 } 57 | var index: Int = 0 58 | 59 | let rawFlags: UInt8 = bytes[index++=] 60 | let flags = MeasurementFlags(rawValue: rawFlags) 61 | 62 | if flags.contains(.WheelRevolutionDataPresent) { 63 | var cumulativeWheelRevolutions = UInt32(bytes[index++=]) 64 | cumulativeWheelRevolutions |= UInt32(bytes[index++=]) << 8 65 | cumulativeWheelRevolutions |= UInt32(bytes[index++=]) << 16 66 | cumulativeWheelRevolutions |= UInt32(bytes[index++=]) << 24 67 | measurement.cumulativeWheelRevolutions = cumulativeWheelRevolutions 68 | measurement.lastWheelEventTime = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 69 | } 70 | 71 | if flags.contains(.CrankRevolutionDataPresent) { 72 | measurement.cumulativeCrankRevolutions = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 73 | measurement.lastCrankEventTime = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 74 | } 75 | 76 | measurement.timestamp = Date.timeIntervalSinceReferenceDate 77 | 78 | return measurement 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/CyclingSpeedCadenceService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CyclingSpeedCadenceService.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import CoreBluetooth 11 | import Signals 12 | 13 | // 14 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.cycling_speed_and_cadence.xml 15 | // 16 | /// :nodoc: 17 | open class CyclingSpeedCadenceService: Service, ServiceProtocol { 18 | 19 | public static var uuid: String { return "1816" } 20 | 21 | public static var characteristicTypes: Dictionary = [ 22 | Measurement.uuid: Measurement.self, 23 | Feature.uuid: Feature.self, 24 | SensorLocation.uuid: SensorLocation.self 25 | ] 26 | 27 | open var measurement: Measurement? { return characteristic() } 28 | 29 | open var feature: Feature? { return characteristic() } 30 | 31 | open var sensorLocation: SensorLocation? { return characteristic() } 32 | 33 | // 34 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.csc_measurement.xml 35 | // 36 | open class Measurement: Characteristic { 37 | 38 | public static let uuid: String = "2A5B" 39 | 40 | open private(set) var speedKPH: Double? 41 | 42 | open private(set) var crankRPM: Double? 43 | 44 | open var wheelCircumferenceCM: Double = 213.3 45 | 46 | open private(set) var measurementData: CyclingSpeedCadenceSerializer.MeasurementData? { 47 | didSet { 48 | guard let previous = oldValue else { return } 49 | guard let current = measurementData else { return } 50 | 51 | if let kph = CyclingSerializer.calculateWheelKPH(current, previous: previous, wheelCircumferenceCM: wheelCircumferenceCM, wheelTimeResolution: 1024) { 52 | speedKPH = kph 53 | } 54 | if let rpm = CyclingSerializer.calculateCrankRPM(current, previous: previous) { 55 | crankRPM = rpm 56 | } 57 | } 58 | } 59 | 60 | required public init(service: Service, cbc: CBCharacteristic) { 61 | super.init(service: service, cbc: cbc) 62 | 63 | cbCharacteristic.notify(true) 64 | } 65 | 66 | override open func valueUpdated() { 67 | if let value = cbCharacteristic.value { 68 | 69 | // Certain sensors (*cough* Mio Velo *cough*) will send updates in bursts 70 | // so we're going to do a little filtering here to get a more stable reading 71 | 72 | let now = Date.timeIntervalSinceReferenceDate 73 | 74 | // calculate the expected interval of wheel events based on current speed 75 | // This results in a small "bump" of speed typically at the end. need to fix that... 76 | var reqInterval = 0.8 77 | if let speedKPH = speedKPH { 78 | let speedCMS = speedKPH * 27.77777777777778 79 | // A slower speed of around 5 kph would expect a wheel event every 1.5 seconds. 80 | // These values could probably use some tweaking ... 81 | reqInterval = max(0.5, min((wheelCircumferenceCM / speedCMS) * 0.9, 1.5)) 82 | } 83 | if measurementData == nil || now - measurementData!.timestamp > reqInterval { 84 | measurementData = CyclingSpeedCadenceSerializer.readMeasurement(value) 85 | } 86 | } 87 | super.valueUpdated() 88 | } 89 | 90 | } 91 | 92 | 93 | // 94 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.csc_feature.xml 95 | // 96 | open class Feature: Characteristic { 97 | 98 | public static let uuid: String = "2A5C" 99 | 100 | open private(set) var features: CyclingSpeedCadenceSerializer.Features? 101 | 102 | required public init(service: Service, cbc: CBCharacteristic) { 103 | super.init(service: service, cbc: cbc) 104 | 105 | cbCharacteristic.read() 106 | } 107 | 108 | override open func valueUpdated() { 109 | if let value = cbCharacteristic.value { 110 | features = CyclingSpeedCadenceSerializer.readFeatures(value) 111 | } 112 | 113 | super.valueUpdated() 114 | 115 | if let service = service { 116 | service.sensor.onServiceFeaturesIdentified => (service.sensor, service) 117 | } 118 | } 119 | } 120 | 121 | 122 | // 123 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.sensor_location.xml 124 | // 125 | open class SensorLocation: Characteristic { 126 | 127 | public static let uuid: String = "2A5D" 128 | 129 | required public init(service: Service, cbc: CBCharacteristic) { 130 | super.init(service: service, cbc: cbc) 131 | 132 | cbCharacteristic.read() 133 | } 134 | 135 | open private(set) var location: CyclingSerializer.SensorLocation? 136 | 137 | override open func valueUpdated() { 138 | if let value = cbCharacteristic.value { 139 | location = CyclingSerializer.readSensorLocation(value) 140 | } 141 | super.valueUpdated() 142 | } 143 | } 144 | 145 | 146 | } 147 | 148 | 149 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/DeviceInformationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceInformationService.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import CoreBluetooth 11 | 12 | // 13 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.device_information.xml 14 | // 15 | /// :nodoc: 16 | open class DeviceInformationService: Service, ServiceProtocol { 17 | 18 | public static var uuid: String { return "180A" } 19 | 20 | public static var characteristicTypes: Dictionary = [ 21 | ManufacturerName.uuid: ManufacturerName.self, 22 | ModelNumber.uuid: ModelNumber.self, 23 | SerialNumber.uuid: SerialNumber.self, 24 | HardwareRevision.uuid: HardwareRevision.self, 25 | FirmwareRevision.uuid: FirmwareRevision.self, 26 | SoftwareRevision.uuid: SoftwareRevision.self, 27 | SystemID.uuid: SystemID.self 28 | ] 29 | 30 | open var manufacturerName: ManufacturerName? { return characteristic() } 31 | open var modelNumber: ModelNumber? { return characteristic() } 32 | open var serialNumber: SerialNumber? { return characteristic() } 33 | open var hardwareRevision: HardwareRevision? { return characteristic() } 34 | open var firmwareRevision: FirmwareRevision? { return characteristic() } 35 | open var softwareRevision: SoftwareRevision? { return characteristic() } 36 | open var systemID: SystemID? { return characteristic() } 37 | 38 | // 39 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.manufacturer_name_string.xml 40 | // 41 | open class ManufacturerName: UTF8Characteristic { 42 | 43 | public static let uuid: String = "2A29" 44 | 45 | } 46 | 47 | // 48 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.model_number_string.xml 49 | // 50 | open class ModelNumber: UTF8Characteristic { 51 | 52 | public static let uuid: String = "2A24" 53 | 54 | } 55 | 56 | // 57 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.serial_number_string.xml 58 | // 59 | open class SerialNumber: UTF8Characteristic { 60 | 61 | public static let uuid: String = "2A25" 62 | 63 | } 64 | 65 | // 66 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.hardware_revision_string.xml 67 | // 68 | open class HardwareRevision: UTF8Characteristic { 69 | 70 | public static let uuid: String = "2A27" 71 | 72 | } 73 | 74 | // 75 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.firmware_revision_string.xml 76 | // 77 | open class FirmwareRevision: UTF8Characteristic { 78 | 79 | public static let uuid: String = "2A26" 80 | 81 | } 82 | 83 | // 84 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.software_revision_string.xml 85 | // 86 | open class SoftwareRevision: UTF8Characteristic { 87 | public static let uuid: String = "2A28" 88 | } 89 | 90 | // 91 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.software_revision_string.xml 92 | // 93 | open class SystemID: Characteristic { 94 | 95 | public static let uuid: String = "2A23" 96 | 97 | required public init(service: Service, cbc: CBCharacteristic) { 98 | super.init(service: service, cbc: cbc) 99 | 100 | readValue() 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/HeartRateSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeartRateSerializer.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | /// :nodoc: 13 | open class HeartRateSerializer { 14 | 15 | public struct MeasurementData { 16 | public enum ContactStatus { 17 | case notSupported 18 | case notDetected 19 | case detected 20 | } 21 | public var heartRate: UInt16 = 0 22 | public var contactStatus: ContactStatus = .notSupported 23 | public var energyExpended: UInt16? 24 | public var rrIntervals: [UInt16] = [] 25 | } 26 | 27 | public enum BodySensorLocation: UInt8 { 28 | case other = 0 29 | case chest = 1 30 | case wrist = 2 31 | case finger = 3 32 | case hand = 4 33 | case earLobe = 5 34 | case foot = 6 35 | } 36 | 37 | public static func readMeasurement(_ data: Data) -> MeasurementData { 38 | var measurement = MeasurementData() 39 | 40 | let bytes = data.map { $0 } 41 | if bytes.count < 2 { 42 | return measurement 43 | } 44 | 45 | var index: Int = 0 46 | let flags = bytes[index++=]; 47 | 48 | if flags & 0x01 == 0 { 49 | measurement.heartRate = UInt16(bytes[index++=]) 50 | } else if bytes.count > index + 1 { 51 | measurement.heartRate = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 52 | } 53 | 54 | let contactStatusBits = (flags | 0x06) >> 1 55 | if contactStatusBits == 2 { 56 | measurement.contactStatus = .notDetected 57 | } else if contactStatusBits == 3 { 58 | measurement.contactStatus = .detected 59 | } 60 | if flags & 0x08 == 0x08 && bytes.count > index + 1 { 61 | measurement.energyExpended = UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8 62 | } 63 | if flags & 0x10 == 0x10 { 64 | while (bytes.count > index + 1) { 65 | // Resolution of 1/1024 second 66 | let interval = (UInt16(bytes[index++=]) | UInt16(bytes[index++=]) << 8) 67 | measurement.rrIntervals.append(interval) 68 | } 69 | } 70 | return measurement 71 | } 72 | 73 | 74 | public static func readSensorLocation(_ data: Data) -> BodySensorLocation? { 75 | let bytes = data.map { $0 } 76 | if bytes.count == 0 { return nil } 77 | return BodySensorLocation(rawValue: bytes[0]) 78 | } 79 | 80 | public static func writeResetEnergyExpended() -> [UInt8] { 81 | return [0x01] 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/HeartRateService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeartRateService.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import CoreBluetooth 11 | import Signals 12 | 13 | // 14 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.heart_rate.xml 15 | // 16 | /// :nodoc: 17 | open class HeartRateService: Service, ServiceProtocol { 18 | 19 | public static var uuid: String { return "180D" } 20 | 21 | public static var characteristicTypes: Dictionary = [ 22 | Measurement.uuid: Measurement.self, 23 | BodySensorLocation.uuid: BodySensorLocation.self, 24 | ControlPoint.uuid: ControlPoint.self 25 | ] 26 | 27 | open var measurement: Measurement? { return characteristic() } 28 | 29 | open var bodySensorLocation: BodySensorLocation? { return characteristic() } 30 | 31 | open var controlPoint: ControlPoint? { return characteristic() } 32 | 33 | // 34 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.heart_rate_measurement.xml 35 | // 36 | open class Measurement: Characteristic { 37 | 38 | public static let uuid: String = "2A37" 39 | 40 | required public init(service: Service, cbc: CBCharacteristic) { 41 | super.init(service: service, cbc: cbc) 42 | 43 | cbCharacteristic.notify(true) 44 | 45 | service.sensor.onServiceFeaturesIdentified => (service.sensor, service) 46 | } 47 | 48 | open private(set) var currentMeasurement: HeartRateSerializer.MeasurementData? 49 | 50 | override open func valueUpdated() { 51 | if let value = cbCharacteristic.value { 52 | currentMeasurement = HeartRateSerializer.readMeasurement(value) 53 | } 54 | super.valueUpdated() 55 | } 56 | 57 | } 58 | 59 | 60 | // 61 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.body_sensor_location.xml 62 | // 63 | open class BodySensorLocation: Characteristic { 64 | 65 | public static let uuid: String = "2A38" 66 | 67 | required public init(service: Service, cbc: CBCharacteristic) { 68 | super.init(service: service, cbc: cbc) 69 | 70 | readValue() 71 | } 72 | 73 | open private(set) var location: HeartRateSerializer.BodySensorLocation? 74 | 75 | override open func valueUpdated() { 76 | if let value = cbCharacteristic.value { 77 | location = HeartRateSerializer.readSensorLocation(value) 78 | } 79 | super.valueUpdated() 80 | } 81 | } 82 | 83 | 84 | // 85 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.heart_rate_control_point.xml 86 | // 87 | open class ControlPoint: Characteristic { 88 | 89 | public static let uuid: String = "2A39" 90 | 91 | required public init(service: Service, cbc: CBCharacteristic) { 92 | super.init(service: service, cbc: cbc) 93 | 94 | cbCharacteristic.notify(true) 95 | } 96 | 97 | open func resetEnergyExpended() { 98 | cbCharacteristic.write(Data(HeartRateSerializer.writeResetEnergyExpended()), writeType: .withResponse) 99 | } 100 | 101 | override open func valueUpdated() { 102 | // TODO: Unsure what value is read from the CP after we reset the energy expended (not documented?) 103 | super.valueUpdated() 104 | } 105 | 106 | } 107 | 108 | } 109 | 110 | 111 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/Operators.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Operators.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | public extension SignedInteger { 13 | 14 | /// Increment this SignedInteger by 1 15 | mutating func increment() { 16 | self = self.advanced(by: 1) 17 | } 18 | 19 | /// Decrement this SignedInteger by 1 20 | mutating func decrement() { 21 | self = self.advanced(by: -1) 22 | } 23 | 24 | } 25 | 26 | prefix operator ++= 27 | postfix operator ++= 28 | prefix operator --= 29 | postfix operator --= 30 | 31 | /// Increment this SignedInteger and return the new value 32 | public prefix func ++= (v: inout T) -> T { 33 | v.increment() 34 | return v 35 | } 36 | 37 | /// Increment this SignedInteger and return the old value 38 | public postfix func ++= (v: inout T) -> T { 39 | let result = v 40 | v.increment() 41 | return result 42 | } 43 | 44 | /// Decrement this SignedInteger and return the new value 45 | public prefix func --= (v: inout T) -> T { 46 | v.decrement() 47 | return v 48 | } 49 | 50 | /// Decrement this SignedInteger and return the old value 51 | public postfix func --= (v: inout T) -> T { 52 | let result = v 53 | v.decrement() 54 | return result 55 | } 56 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/RSSINormalizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RSSINormalizer.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | 12 | /** 13 | Normalize a raw RSSI value to a linear scale. 14 | 15 | Derived from Android's RSSI signal level calculator 16 | https://github.com/android/platform_frameworks_base/blob/master/wifi/java/android/net/wifi/WifiManager.java#L1654 17 | */ 18 | public class RSSINormalizer { 19 | 20 | /** 21 | Calculates the level of the signal. This should be used any time a signal is being shown to the user. 22 | 23 | - parameter rssi: The power of the signal measured in RSSI. 24 | - parameter numLevels: The number of levels to consider in the calculated level. 25 | - parameter rssiMin: Lower bound of expected RSSI values (results in 0 Signal Level). 26 | - parameter rssiMax: Upper bound of expected RSSI values (results in `numLevels-1` Signal Level). 27 | - returns: A level of the signal, given in the range of 0 to numLevels-1 (both inclusive). 28 | */ 29 | public static func calculateSignalLevel(_ rssi: Int, numLevels: Int, rssiMin: Int = -100, rssiMax: Int = -65) -> Int { 30 | if rssi <= rssiMin { 31 | return 0 32 | } else if rssi >= rssiMax { 33 | return numLevels - 1 34 | } 35 | let inputRange = Float(rssiMax - rssiMin) 36 | let outputRange = Float(numLevels - 1) 37 | return Int(Float(rssi - rssiMin) * outputRange / inputRange) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/Sensor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sensor.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import CoreBluetooth 11 | import Signals 12 | 13 | /** 14 | Sensor wraps a CoreBluetooth Peripheral and manages the hierarchy of Services and Characteristics. 15 | */ 16 | open class Sensor: NSObject { 17 | 18 | // Internal Constructor. SensorManager manages the instantiation and destruction of Sensor objects 19 | /// :nodoc: 20 | required public init(peripheral: CBPeripheral, advertisements: [CBUUID] = []) { 21 | self.peripheral = peripheral 22 | self.advertisements = advertisements 23 | 24 | super.init() 25 | 26 | peripheral.delegate = self 27 | peripheral.addObserver(self, forKeyPath: "state", options: [.new, .old], context: &myContext) 28 | } 29 | 30 | deinit { 31 | peripheral.removeObserver(self, forKeyPath: "state") 32 | peripheral.delegate = nil 33 | rssiPingTimer?.invalidate() 34 | } 35 | 36 | /// Backing CoreBluetooth Peripheral 37 | public let peripheral: CBPeripheral 38 | 39 | /// Discovered Services 40 | public fileprivate(set) var services = Dictionary() 41 | 42 | /// Advertised UUIDs 43 | public let advertisements: [CBUUID] 44 | 45 | /// Raw Advertisement Data 46 | public internal(set) var advertisementData: [String: Any]? { 47 | didSet { 48 | onAdvertisementDataUpdated => advertisementData 49 | } 50 | } 51 | 52 | /// Advertisement Data Changed Signal 53 | public let onAdvertisementDataUpdated = Signal<([String: Any]?)>() 54 | 55 | /// Name Changed Signal 56 | public let onNameChanged = Signal() 57 | 58 | /// State Changed Signal 59 | public let onStateChanged = Signal() 60 | 61 | /// Service Discovered Signal 62 | public let onServiceDiscovered = Signal<(Sensor, Service)>() 63 | 64 | /// Service Features Identified Signal 65 | public let onServiceFeaturesIdentified = Signal<(Sensor, Service)>() 66 | 67 | /// Characteristic Discovered Signal 68 | public let onCharacteristicDiscovered = Signal<(Sensor, Characteristic)>() 69 | 70 | /// Characteristic Value Updated Signal 71 | public let onCharacteristicValueUpdated = Signal<(Sensor, Characteristic)>() 72 | 73 | /// Characteristic Value Written Signal 74 | public let onCharacteristicValueWritten = Signal<(Sensor, Characteristic)>() 75 | 76 | /// RSSI Changed Signal 77 | public let onRSSIChanged = Signal<(Sensor, Int)>() 78 | 79 | /// Most recent RSSI value 80 | public internal(set) var rssi: Int = Int.min { 81 | didSet { 82 | onRSSIChanged => (self, rssi) 83 | } 84 | } 85 | 86 | /// Last time of Sensor Communication with the Sensor Manager (Time Interval since Reference Date) 87 | public fileprivate(set) var lastSensorActivity = Date.timeIntervalSinceReferenceDate 88 | 89 | /** 90 | Get a service by its UUID or by Type 91 | 92 | - parameter uuid: UUID string 93 | - returns: Service 94 | */ 95 | public func service(_ uuid: String? = nil) -> T? { 96 | if let uuid = uuid { 97 | return services[uuid] as? T 98 | } 99 | for service in services.values { 100 | if let s = service as? T { 101 | return s 102 | } 103 | } 104 | return nil 105 | } 106 | 107 | /** 108 | Check if a Sensor advertised a specific UUID Service 109 | 110 | - parameter uuid: UUID string 111 | - returns: `true` if the sensor advertised the `uuid` service 112 | */ 113 | open func advertisedService(_ uuid: String) -> Bool { 114 | let service = CBUUID(string: uuid) 115 | for advertisement in advertisements { 116 | if advertisement.isEqual(service) { 117 | return true 118 | } 119 | } 120 | return false 121 | } 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | ////////////////////////////////////////////////////////////////// 134 | // Private / Internal Classes, Properties and Constants 135 | ////////////////////////////////////////////////////////////////// 136 | 137 | internal weak var serviceFactory: SensorManager.ServiceFactory? 138 | private var rssiPingTimer: Timer? 139 | private var myContext = 0 140 | 141 | /// :nodoc: 142 | override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 143 | if context == &myContext { 144 | if keyPath == "state" { 145 | peripheralStateChanged() 146 | } 147 | } else { 148 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 149 | } 150 | } 151 | 152 | fileprivate var rssiPingEnabled: Bool = false { 153 | didSet { 154 | if rssiPingEnabled { 155 | if rssiPingTimer == nil { 156 | rssiPingTimer = Timer.scheduledTimer(timeInterval: SensorManager.RSSIPingInterval, target: self, selector: #selector(Sensor.rssiPingTimerHandler), userInfo: nil, repeats: true) 157 | } 158 | } else { 159 | rssi = Int.min 160 | rssiPingTimer?.invalidate() 161 | rssiPingTimer = nil 162 | } 163 | } 164 | } 165 | } 166 | 167 | 168 | // Private Funtions 169 | extension Sensor { 170 | 171 | fileprivate func peripheralStateChanged() { 172 | switch peripheral.state { 173 | case .connected: 174 | rssiPingEnabled = true 175 | case .connecting: 176 | break 177 | case .disconnected: 178 | fallthrough 179 | default: 180 | rssiPingEnabled = false 181 | services.removeAll() 182 | } 183 | SensorManager.logSensorMessage?("Sensor: peripheralStateChanged: \(peripheral.state.rawValue)") 184 | onStateChanged => self 185 | } 186 | 187 | fileprivate func serviceDiscovered(_ cbs: CBService) { 188 | if let service = services[cbs.uuid.uuidString], service.cbService == cbs { 189 | return 190 | } 191 | if let ServiceType = serviceFactory?.serviceTypes[cbs.uuid.uuidString] { 192 | let service = ServiceType.init(sensor: self, cbs: cbs) 193 | services[cbs.uuid.uuidString] = service 194 | onServiceDiscovered => (self, service) 195 | 196 | SensorManager.logSensorMessage?("Sensor: Service Created: \(service)") 197 | if let sp = service as? ServiceProtocol { 198 | let charUUIDs: [CBUUID] = type(of: sp).characteristicTypes.keys.map { uuid in 199 | return CBUUID(string: uuid) 200 | } 201 | peripheral.discoverCharacteristics(charUUIDs.count > 0 ? charUUIDs : nil, for: cbs) 202 | } 203 | } else { 204 | SensorManager.logSensorMessage?("Sensor: Service Ignored: \(cbs)") 205 | } 206 | } 207 | 208 | fileprivate func characteristicDiscovered(_ cbc: CBCharacteristic, cbs: CBService) { 209 | guard let service = services[cbs.uuid.uuidString] else { return } 210 | if let characteristic = service.characteristic(cbc.uuid.uuidString), characteristic.cbCharacteristic == cbc { 211 | return 212 | } 213 | guard let sp = service as? ServiceProtocol else { return } 214 | 215 | if let CharType = type(of: sp).characteristicTypes[cbc.uuid.uuidString] { 216 | let characteristic = CharType.init(service: service, cbc: cbc) 217 | service.characteristics[cbc.uuid.uuidString] = characteristic 218 | 219 | characteristic.onValueUpdated.subscribe(with: self) { [weak self] c in 220 | if let s = self { 221 | s.onCharacteristicValueUpdated => (s, c) 222 | } 223 | } 224 | characteristic.onValueWritten.subscribe(with: self) { [weak self] c in 225 | if let s = self { 226 | s.onCharacteristicValueWritten => (s, c) 227 | } 228 | } 229 | 230 | SensorManager.logSensorMessage?("Sensor: Characteristic Created: \(characteristic)") 231 | onCharacteristicDiscovered => (self, characteristic) 232 | } else { 233 | SensorManager.logSensorMessage?("Sensor: Characteristic Ignored: \(cbc)") 234 | } 235 | } 236 | 237 | @objc func rssiPingTimerHandler() { 238 | if peripheral.state == .connected { 239 | peripheral.readRSSI() 240 | } 241 | } 242 | 243 | internal func markSensorActivity() { 244 | lastSensorActivity = Date.timeIntervalSinceReferenceDate 245 | } 246 | 247 | } 248 | 249 | 250 | extension Sensor: CBPeripheralDelegate { 251 | 252 | /// :nodoc: 253 | public func peripheralDidUpdateName(_ peripheral: CBPeripheral) { 254 | onNameChanged => self 255 | markSensorActivity() 256 | } 257 | 258 | /// :nodoc: 259 | public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { 260 | guard let cbss = peripheral.services else { return } 261 | for cbs in cbss { 262 | serviceDiscovered(cbs) 263 | } 264 | markSensorActivity() 265 | } 266 | 267 | /// :nodoc: 268 | public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { 269 | guard let cbcs = service.characteristics else { return } 270 | for cbc in cbcs { 271 | characteristicDiscovered(cbc, cbs: service) 272 | } 273 | markSensorActivity() 274 | } 275 | 276 | /// :nodoc: 277 | public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { 278 | guard let service = services[characteristic.service.uuid.uuidString] else { return } 279 | guard let char = service.characteristics[characteristic.uuid.uuidString] else { return } 280 | if char.cbCharacteristic !== characteristic { 281 | char.cbCharacteristic = characteristic 282 | } 283 | char.valueUpdated() 284 | markSensorActivity() 285 | } 286 | 287 | /// :nodoc: 288 | public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { 289 | guard let service = services[characteristic.service.uuid.uuidString] else { return } 290 | guard let char = service.characteristics[characteristic.uuid.uuidString] else { return } 291 | if char.cbCharacteristic !== characteristic { 292 | char.cbCharacteristic = characteristic 293 | } 294 | char.valueWritten() 295 | markSensorActivity() 296 | } 297 | 298 | /// :nodoc: 299 | public func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) { 300 | if RSSI.intValue < 0 { 301 | rssi = RSSI.intValue 302 | markSensorActivity() 303 | } 304 | } 305 | 306 | } 307 | 308 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/SensorManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SensorManager.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import CoreBluetooth 11 | import Signals 12 | 13 | /** 14 | An extensible Bluetooth LE Sensor Manager with concrete Service and Characteristic types, hierarchy, forwarding and observation to simplify BLE. 15 | */ 16 | public class SensorManager: NSObject { 17 | 18 | /** 19 | This is a lazy instance. You can opt to NOT call it and control the lifecycle of the SensorManager yourself if desired. 20 | 21 | No internal reference is made to this instance. 22 | */ 23 | public static let instance = SensorManager() 24 | 25 | /** 26 | All SensorManager logging is directed through this closure. Set it to nil to turn logging off 27 | or set your own closure at the project level to direct all logging to your logger of choice. 28 | */ 29 | public static var logSensorMessage: ((_: String) -> ())? = { message in 30 | print(message) 31 | } 32 | 33 | /** 34 | Initializes the Sensor Manager. 35 | 36 | - parameter powerAlert: Whether the system should display a warning dialog 37 | if Bluetooth is powered off when the central manager is instantiated. 38 | 39 | - returns: Sensor Manager instance 40 | */ 41 | public init(powerAlert: Bool = false) { 42 | super.init() 43 | 44 | let options: [String: AnyObject] = [ 45 | CBCentralManagerOptionShowPowerAlertKey: NSNumber(booleanLiteral: powerAlert) 46 | ] 47 | centralManager = CBCentralManager(delegate: self, queue: nil, options: options) 48 | } 49 | 50 | /// Sensor Manager State 51 | public enum ManagerState { 52 | /// Off 53 | case off 54 | /// On, Scanning Disabled 55 | case idle 56 | /// Passive Scan for BLE sensors 57 | case passiveScan 58 | /// Aggressive Scan for BLE sensors 59 | case aggressiveScan 60 | } 61 | 62 | /// Current Sensor Manager State 63 | public var state: ManagerState = .off { 64 | didSet { 65 | if oldValue != state { 66 | stateUpdated() 67 | } 68 | } 69 | } 70 | 71 | /// Customizable Sensor Class Type 72 | public var SensorType: Sensor.Type = Sensor.self 73 | 74 | /** 75 | All Discovered Sensors. 76 | 77 | Note: sensors may no longer be connectable. Call `removeInactiveSensors` to trim. 78 | */ 79 | public var sensors: [Sensor] { 80 | return Array(sensorsById.values) 81 | } 82 | 83 | /** 84 | Set the Service Types to scan for. Will also create the Services when discovered. 85 | 86 | - parameter serviceTypes: Service Types Array 87 | */ 88 | public func setServicesToScanFor(_ serviceTypes: [ServiceProtocol.Type]) { 89 | addServiceTypes(serviceTypes) 90 | serviceFactory.servicesToDiscover = serviceTypes.map { type in 91 | return CBUUID(string: type.uuid) 92 | } 93 | } 94 | 95 | /** 96 | Add Service Types to Create when discovered after connecting to a Sensor. 97 | 98 | - parameter serviceTypes: Service Types Array 99 | */ 100 | public func addServiceTypes(_ serviceTypes: [ServiceProtocol.Type]) { 101 | for type in serviceTypes { 102 | serviceFactory.serviceTypes[type.uuid] = type.serviceType 103 | } 104 | } 105 | 106 | /** 107 | Attempt to connect to a sensor. 108 | 109 | - parameter sensor: The sensor to connect to. 110 | */ 111 | public func connectToSensor(_ sensor: Sensor) { 112 | SensorManager.logSensorMessage?("SensorManager: Connecting to sensor ...") 113 | centralManager.connect(sensor.peripheral, options: nil) 114 | } 115 | 116 | /** 117 | Disconnect from a sensor. 118 | 119 | - parameter sensor: The sensor to disconnect from. 120 | */ 121 | public func disconnectFromSensor(_ sensor: Sensor) { 122 | SensorManager.logSensorMessage?("SensorManager: Disconnecting from sensor ...") 123 | centralManager.cancelPeripheralConnection(sensor.peripheral) 124 | } 125 | 126 | /** 127 | Removes inactive sensors from the Sensor Manager. 128 | 129 | - parameter inactiveTime: Trim sensors that have not communicated 130 | with the Sensor Manager with the last `inactiveTime` TimeInterval 131 | */ 132 | public func removeInactiveSensors(_ inactiveTime: TimeInterval) { 133 | let now = Date.timeIntervalSinceReferenceDate 134 | for sensor in sensors { 135 | if now - sensor.lastSensorActivity > inactiveTime { 136 | if let sensor = sensorsById.removeValue(forKey: sensor.peripheral.identifier.uuidString) { 137 | onSensorRemoved => sensor 138 | } 139 | } 140 | } 141 | } 142 | 143 | /// Bluetooth State Change Signal 144 | public let onBluetoothStateChange = Signal() 145 | 146 | /// Sensor Discovered Signal 147 | public let onSensorDiscovered = Signal() 148 | 149 | /// Sensor Connected Signal 150 | public let onSensorConnected = Signal() 151 | 152 | /// Sensor Connection Failed Signal 153 | public let onSensorConnectionFailed = Signal() 154 | 155 | /// Sensor Disconnected Signal 156 | public let onSensorDisconnected = Signal<(Sensor, NSError?)>() 157 | 158 | /// Sensor Removed Signal 159 | public let onSensorRemoved = Signal() 160 | 161 | ////////////////////////////////////////////////////////////////// 162 | // Private / Internal Classes, Properties and Constants 163 | ////////////////////////////////////////////////////////////////// 164 | 165 | public fileprivate(set) var centralManager: CBCentralManager! 166 | 167 | internal class ServiceFactory { 168 | fileprivate(set) var serviceTypes = Dictionary() 169 | 170 | var serviceUUIDs: [CBUUID]? { 171 | return serviceTypes.count > 0 ? serviceTypes.keys.map { uuid in 172 | return CBUUID(string: uuid) 173 | } : nil 174 | } 175 | 176 | var servicesToDiscover: [CBUUID] = [] 177 | } 178 | 179 | fileprivate let serviceFactory = ServiceFactory() 180 | fileprivate var sensorsById = Dictionary() 181 | fileprivate var activityUpdateTimer: Timer? 182 | static internal let RSSIPingInterval: TimeInterval = 2 183 | static internal let ActivityInterval: TimeInterval = 5 184 | static internal let InactiveInterval: TimeInterval = 4 185 | } 186 | 187 | 188 | // Private Funtions 189 | extension SensorManager { 190 | 191 | fileprivate func stateUpdated() { 192 | if centralManager.state != .poweredOn { return } 193 | 194 | activityUpdateTimer?.invalidate() 195 | activityUpdateTimer = nil 196 | 197 | switch state { 198 | case .off: 199 | stopScan() 200 | 201 | for sensor in sensors { 202 | disconnectFromSensor(sensor) 203 | } 204 | SensorManager.logSensorMessage?("Shutting Down SensorManager") 205 | 206 | case .idle: 207 | stopScan() 208 | startActivityTimer() 209 | 210 | case .passiveScan: 211 | scan(false) 212 | startActivityTimer() 213 | 214 | case .aggressiveScan: 215 | scan(true) 216 | startActivityTimer() 217 | } 218 | } 219 | 220 | fileprivate func stopScan() { 221 | centralManager.stopScan() 222 | } 223 | 224 | fileprivate func startActivityTimer() { 225 | activityUpdateTimer?.invalidate() 226 | activityUpdateTimer = Timer.scheduledTimer(timeInterval: SensorManager.ActivityInterval, target: self, selector: #selector(SensorManager.rssiUpateTimerHandler(_:)), userInfo: nil, repeats: true) 227 | } 228 | 229 | fileprivate func scan(_ aggressive: Bool) { 230 | let options: [String: AnyObject] = [ 231 | CBCentralManagerScanOptionAllowDuplicatesKey: aggressive as AnyObject 232 | ] 233 | let serviceUUIDs = serviceFactory.servicesToDiscover.count > 0 ? serviceFactory.servicesToDiscover : nil 234 | centralManager.scanForPeripherals(withServices: serviceUUIDs, options: options) 235 | SensorManager.logSensorMessage?("SensorManager: Scanning for Services") 236 | if let serviceUUIDs = serviceUUIDs { 237 | for peripheral in centralManager.retrieveConnectedPeripherals(withServices: serviceUUIDs) { 238 | let _ = sensorForPeripheral(peripheral, create: true) 239 | } 240 | } 241 | } 242 | 243 | 244 | @objc func rssiUpateTimerHandler(_ timer: Timer) { 245 | let now = Date.timeIntervalSinceReferenceDate 246 | for sensor in sensors { 247 | if now - sensor.lastSensorActivity > SensorManager.InactiveInterval { 248 | sensor.rssi = Int.min 249 | } 250 | } 251 | } 252 | 253 | fileprivate func sensorForPeripheral(_ peripheral: CBPeripheral, create: Bool, advertisements: [CBUUID] = [], data: [String: Any]? = nil) -> Sensor? { 254 | if let sensor = sensorsById[peripheral.identifier.uuidString] { 255 | sensor.advertisementData = data 256 | return sensor 257 | } 258 | if !create { 259 | return nil 260 | } 261 | let sensor = SensorType.init(peripheral: peripheral, advertisements: advertisements) 262 | sensor.serviceFactory = serviceFactory 263 | sensor.advertisementData = data 264 | sensorsById[peripheral.identifier.uuidString] = sensor 265 | onSensorDiscovered => sensor 266 | SensorManager.logSensorMessage?("SensorManager: Created Sensor for Peripheral: \(peripheral)") 267 | return sensor 268 | } 269 | 270 | } 271 | 272 | 273 | 274 | extension SensorManager: CBCentralManagerDelegate { 275 | 276 | /// :nodoc: 277 | public func centralManager(_ manager: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { 278 | SensorManager.logSensorMessage?("CBCentralManager: didFailToConnectPeripheral: \(peripheral) :: \(error?.localizedDescription ?? "No Error Given")") 279 | 280 | if let sensor = sensorForPeripheral(peripheral, create: false) { 281 | onSensorConnectionFailed => sensor 282 | } 283 | } 284 | 285 | /// :nodoc: 286 | public func centralManager(_ manager: CBCentralManager, didConnect peripheral: CBPeripheral) { 287 | SensorManager.logSensorMessage?("CBCentralManager: didConnectPeripheral: \(peripheral)") 288 | 289 | if let sensor = sensorForPeripheral(peripheral, create: true) { 290 | peripheral.discoverServices(serviceFactory.serviceUUIDs) 291 | onSensorConnected => sensor 292 | } 293 | } 294 | 295 | /// :nodoc: 296 | public func centralManager(_ manager: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { 297 | SensorManager.logSensorMessage?("CBCentralManager: didDisconnectPeripheral: \(peripheral)") 298 | 299 | // Error Codes: 300 | // 0 = Unknown error. possibly a major crash? 301 | // 6 = Connection timed out unexpectedly (pulled the battery out, lost connection due to distance) 302 | // 10 = The connection has failed unexpectedly. 303 | 304 | if let sensor = sensorForPeripheral(peripheral, create: false) { 305 | onSensorDisconnected => (sensor, error as NSError?) 306 | if error != nil { 307 | sensorsById.removeValue(forKey: sensor.peripheral.identifier.uuidString) 308 | onSensorRemoved => sensor 309 | } 310 | } 311 | } 312 | 313 | /// :nodoc: 314 | public func centralManager(_ manager: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { 315 | if let uuids = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] { 316 | if let sensor = sensorForPeripheral(peripheral, create: true, advertisements: uuids, data: advertisementData) { 317 | if RSSI.intValue < 0 { 318 | sensor.rssi = RSSI.intValue 319 | sensor.markSensorActivity() 320 | } 321 | } 322 | } 323 | } 324 | 325 | /// :nodoc: 326 | public func centralManagerDidUpdateState(_ central: CBCentralManager) { 327 | SensorManager.logSensorMessage?("centralManagerDidUpdateState: \(central.state.rawValue)") 328 | 329 | switch central.state { 330 | case .unknown: 331 | break 332 | case .resetting: 333 | break 334 | case .unsupported: 335 | break 336 | case .unauthorized: 337 | break 338 | case .poweredOff: 339 | break 340 | case .poweredOn: 341 | stateUpdated() 342 | @unknown default: 343 | break 344 | } 345 | 346 | onBluetoothStateChange => CBCentralManagerState(rawValue: central.state.rawValue)! 347 | } 348 | 349 | } 350 | 351 | -------------------------------------------------------------------------------- /Sources/SwiftySensors/Service.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Service.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import CoreBluetooth 11 | 12 | /** 13 | The Service Protocol is used by the Sensor Manager to identify, organize and instantiate Services given a UUID string. 14 | */ 15 | public protocol ServiceProtocol: class { 16 | 17 | /// UUID string of the Service. 18 | static var uuid: String { get } 19 | 20 | /// Service Class Type to instantiate. 21 | static var serviceType: Service.Type { get } 22 | 23 | /// Characteristic Types (UUID key) to discover and instantiate. 24 | static var characteristicTypes: Dictionary { get } 25 | 26 | } 27 | 28 | extension ServiceProtocol where Self: Service { 29 | /// :nodoc: 30 | public static var serviceType: Service.Type { return self } 31 | } 32 | 33 | /** 34 | Compares the UUIDs of 2 Service Objects. 35 | 36 | - parameter lhs: Service Object 37 | - parameter rhs: Service Object 38 | - returns: `true` if the UUIDs of the two Service Objects match 39 | */ 40 | public func == (lhs: Service, rhs: Service) -> Bool { 41 | return lhs.cbService.uuid == rhs.cbService.uuid 42 | } 43 | 44 | /** 45 | Base Service Implementation. Extend this class with a concrete definition of a BLE service. 46 | */ 47 | open class Service: Equatable { 48 | 49 | /// Parent Sensor 50 | public private(set) weak var sensor: Sensor! 51 | 52 | /// Backing CoreBluetooth Service 53 | public let cbService: CBService 54 | 55 | /// All Characteristics owned by this Service 56 | public internal(set) var characteristics = Dictionary() 57 | 58 | /** 59 | Get a characteristic by its UUID or by Type 60 | 61 | - parameter uuid: UUID string 62 | - returns: Characteristic 63 | */ 64 | public func characteristic(_ uuid: String? = nil) -> T? { 65 | if let uuid = uuid { 66 | return characteristics[uuid] as? T 67 | } 68 | for characteristic in characteristics.values { 69 | if let c = characteristic as? T { 70 | return c 71 | } 72 | } 73 | return nil 74 | } 75 | 76 | // Internal Constructor. SensorManager manages the instantiation and destruction of Service objects 77 | /// :nodoc: 78 | required public init(sensor: Sensor, cbs: CBService) { 79 | self.sensor = sensor 80 | self.cbService = cbs 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /SwiftySensors.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | 3 | spec.name = 'SwiftySensors' 4 | spec.version = '1.1.0' 5 | spec.summary = 'BLE Fitness Sensors Communication Utilities for iOS, macOS and tvOS' 6 | 7 | spec.homepage = 'https://github.com/kinetic-fit/sensors-swift' 8 | spec.license = { :type => 'MIT', :file => 'LICENSE' } 9 | spec.author = { 'Kinetic' => 'admin@kinetic.fit' } 10 | 11 | spec.ios.deployment_target = '8.4' 12 | spec.osx.deployment_target = '10.13' 13 | spec.tvos.deployment_target = '11.2' 14 | 15 | spec.source = { :git => 'https://github.com/kinetic-fit/sensors-swift.git', 16 | :tag => spec.version.to_s, 17 | :submodules => true } 18 | spec.source_files = 'Sources/**/*.swift' 19 | spec.swift_version = '5.0' 20 | 21 | spec.dependency 'Signals' 22 | 23 | 24 | spec.subspec 'Serializers' do |serial| 25 | serial.source_files = 'Sources/*Serializer.swift', 'Sources/Operators.swift' 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /SwiftySensors.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftySensors.xcodeproj/xcshareddata/xcschemes/SwiftySensors iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /SwiftySensors.xcodeproj/xcshareddata/xcschemes/SwiftySensors macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /SwiftySensors.xcodeproj/xcshareddata/xcschemes/SwiftySensors tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /SwiftySensors.xcodeproj/xcshareddata/xcschemes/SwiftySensorsExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /SwiftySensors.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SwiftySensors.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftySensorsExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftySensorsExample 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | import SwiftySensors 12 | 13 | @UIApplicationMain 14 | class AppDelegate: UIResponder, UIApplicationDelegate { 15 | 16 | var window: UIWindow? 17 | 18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 19 | 20 | // Customize what services you want to scan for 21 | SensorManager.instance.setServicesToScanFor([ 22 | CyclingPowerService.self, 23 | CyclingSpeedCadenceService.self, 24 | FitnessMachineService.self, 25 | HeartRateService.self]) 26 | 27 | // Add additional services we want to have access to (but don't want to specifically scan for) 28 | SensorManager.instance.addServiceTypes([DeviceInformationService.self]) 29 | 30 | // Set the scan mode (see documentation) 31 | SensorManager.instance.state = .aggressiveScan 32 | 33 | // Capture SwiftySensors log messages and print them to the console. You can inject your own logging system here if desired. 34 | SensorManager.logSensorMessage = { message in 35 | print(message) 36 | } 37 | 38 | return true 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /SwiftySensorsExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /SwiftySensorsExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /SwiftySensorsExample/CharacteristicViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CharacteristicViewController.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | import SwiftySensors 12 | 13 | class CharacteristicViewController: UIViewController { 14 | 15 | var characteristic: Characteristic! 16 | 17 | @IBOutlet var nameLabel: UILabel! 18 | 19 | @IBOutlet var valueTextView: UITextView! 20 | 21 | override func viewWillAppear(_ animated: Bool) { 22 | super.viewWillAppear(animated) 23 | 24 | nameLabel.text = "\(characteristic!)".components(separatedBy: ".").last 25 | 26 | characteristic.onValueUpdated.subscribe(with: self) { [weak self] characteristic in 27 | self?.refreshValue() 28 | } 29 | 30 | if let cp = characteristic as? FitnessMachineService.ControlPoint { 31 | cp.requestControl() 32 | } 33 | 34 | refreshValue() 35 | 36 | } 37 | 38 | private func refreshValue() { 39 | print("Value Updated") 40 | if let value = characteristic.value { 41 | valueTextView.text = "0x\(value.hexEncodedString())" 42 | } else { 43 | valueTextView.text = "" 44 | } 45 | // if let cp = characteristic as? FitnessMachineService.ControlPoint { 46 | // print(cp.response) 47 | writeButtonHandler(self) 48 | // } 49 | } 50 | 51 | @IBAction func readButtonHandler(_ sender: AnyObject) { 52 | characteristic.readValue() 53 | } 54 | 55 | private var power: Int16 = 50 56 | @IBAction func writeButtonHandler(_ sender: AnyObject) { 57 | if let cp = characteristic as? FitnessMachineService.ControlPoint { 58 | cp.setTargetPower(watts: power) 59 | power += 1 60 | } 61 | } 62 | } 63 | 64 | extension Data { 65 | 66 | func hexEncodedString() -> String { 67 | return map { String(format: "%02hhx", $0) }.joined() 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /SwiftySensorsExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIBackgroundModes 26 | 27 | bluetooth-central 28 | 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /SwiftySensorsExample/SensorDetailsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SensorDetailsViewController.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | import SwiftySensors 12 | 13 | class SensorDetailsViewController: UIViewController { 14 | 15 | var sensor: Sensor! 16 | 17 | @IBOutlet var nameLabel: UILabel! 18 | @IBOutlet var connectButton: UIButton! 19 | @IBOutlet var tableView: UITableView! 20 | 21 | fileprivate var services: [Service] = [] 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | sensor.onServiceDiscovered.subscribe(with: self) { [weak self] sensor, service in 27 | self?.rebuildData() 28 | } 29 | sensor.onStateChanged.subscribe(with: self) { [weak self] sensor in 30 | self?.updateConnectButton() 31 | } 32 | } 33 | 34 | override func viewWillAppear(_ animated: Bool) { 35 | super.viewWillAppear(animated) 36 | 37 | nameLabel.text = sensor.peripheral.name 38 | updateConnectButton() 39 | 40 | rebuildData() 41 | } 42 | 43 | fileprivate func rebuildData() { 44 | services = Array(sensor.services.values) 45 | tableView.reloadData() 46 | } 47 | 48 | fileprivate func updateConnectButton() { 49 | switch sensor.peripheral.state { 50 | case .connected: 51 | connectButton.setTitle("Connected", for: UIControl.State()) 52 | connectButton.isEnabled = true 53 | case .connecting: 54 | connectButton.setTitle("Connecting", for: UIControl.State()) 55 | connectButton.isEnabled = false 56 | case .disconnected: 57 | connectButton.setTitle("Disconnected", for: UIControl.State()) 58 | connectButton.isEnabled = true 59 | rebuildData() 60 | case .disconnecting: 61 | connectButton.setTitle("Disconnecting", for: UIControl.State()) 62 | connectButton.isEnabled = false 63 | @unknown default: 64 | break 65 | } 66 | } 67 | 68 | @IBAction func connectButtonHandler(_ sender: AnyObject) { 69 | if sensor.peripheral.state == .connected { 70 | SensorManager.instance.disconnectFromSensor(sensor) 71 | } else if sensor.peripheral.state == .disconnected { 72 | SensorManager.instance.connectToSensor(sensor) 73 | } 74 | } 75 | 76 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 77 | if let serviceDetails = segue.destination as? ServiceDetailsViewController { 78 | guard let indexPath = tableView.indexPathForSelectedRow else { return } 79 | if indexPath.row >= services.count { return } 80 | serviceDetails.service = services[indexPath.row] 81 | } 82 | } 83 | } 84 | 85 | 86 | 87 | extension SensorDetailsViewController: UITableViewDataSource { 88 | 89 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 90 | let serviceCell = tableView.dequeueReusableCell(withIdentifier: "ServiceCell")! 91 | let service = services[indexPath.row] 92 | 93 | serviceCell.textLabel?.text = "\(service)".components(separatedBy: ".").last 94 | return serviceCell 95 | } 96 | 97 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 98 | return services.count 99 | } 100 | 101 | } 102 | 103 | -------------------------------------------------------------------------------- /SwiftySensorsExample/SensorListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SensorListViewController.swift 3 | // SwiftySensorsExample 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | import SwiftySensors 12 | 13 | class SensorListViewController: UITableViewController { 14 | 15 | fileprivate var sensors: [Sensor] = [] 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | SensorManager.instance.onSensorDiscovered.subscribe(with: self) { [weak self] sensor in 21 | guard let s = self else { return } 22 | if !s.sensors.contains(sensor) { 23 | s.sensors.append(sensor) 24 | s.tableView.reloadData() 25 | } 26 | } 27 | } 28 | 29 | override func viewWillAppear(_ animated: Bool) { 30 | super.viewWillAppear(animated) 31 | 32 | sensors = SensorManager.instance.sensors 33 | tableView.reloadData() 34 | } 35 | 36 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 37 | return sensors.count 38 | } 39 | 40 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 41 | let sensorCell = tableView.dequeueReusableCell(withIdentifier: "SensorCell")! 42 | let sensor = sensors[indexPath.row] 43 | sensorCell.textLabel?.text = sensor.peripheral.name 44 | return sensorCell 45 | } 46 | 47 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 48 | if let sensorDetails = segue.destination as? SensorDetailsViewController { 49 | guard let indexPath = tableView.indexPathForSelectedRow else { return } 50 | if indexPath.row >= sensors.count { return } 51 | sensorDetails.sensor = sensors[indexPath.row] 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /SwiftySensorsExample/ServiceDetailsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceDetailsViewController.swift 3 | // SwiftySensors 4 | // 5 | // https://github.com/kinetic-fit/sensors-swift 6 | // 7 | // Copyright © 2017 Kinetic. All rights reserved. 8 | // 9 | 10 | import UIKit 11 | import SwiftySensors 12 | 13 | class ServiceDetailsViewController: UIViewController { 14 | 15 | var service: Service! 16 | 17 | @IBOutlet var nameLabel: UILabel! 18 | @IBOutlet var tableView: UITableView! 19 | 20 | fileprivate var characteristics: [Characteristic] = [] 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | service.sensor.onCharacteristicDiscovered.subscribe(with: self) { [weak self] sensor, characteristic in 26 | self?.rebuildData() 27 | } 28 | } 29 | 30 | override func viewWillAppear(_ animated: Bool) { 31 | super.viewWillAppear(animated) 32 | 33 | nameLabel.text = "\(service!)".components(separatedBy: ".").last 34 | 35 | rebuildData() 36 | } 37 | 38 | fileprivate func rebuildData() { 39 | characteristics = Array(service.characteristics.values) 40 | tableView.reloadData() 41 | } 42 | 43 | 44 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 45 | if let charViewController = segue.destination as? CharacteristicViewController { 46 | guard let indexPath = tableView.indexPathForSelectedRow else { return } 47 | if indexPath.row >= characteristics.count { return } 48 | charViewController.characteristic = characteristics[indexPath.row] 49 | } 50 | } 51 | 52 | } 53 | 54 | 55 | 56 | extension ServiceDetailsViewController: UITableViewDataSource { 57 | 58 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 59 | let charCell = tableView.dequeueReusableCell(withIdentifier: "CharCell")! 60 | let characteristic = characteristics[indexPath.row] 61 | 62 | charCell.textLabel?.text = "\(characteristic)".components(separatedBy: ".").last 63 | return charCell 64 | } 65 | 66 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 67 | return characteristics.count 68 | } 69 | 70 | } 71 | --------------------------------------------------------------------------------