├── .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 | [](https://travis-ci.org/artman/Signals)
3 | [](https://cocoapods.org/pods/Signals)
4 | [](https://github.com/Carthage/Carthage)
5 | 
6 | 
7 | [](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 | 
3 | 
4 | 
5 | 
6 | [](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 |
--------------------------------------------------------------------------------