├── LIFXHTTPKit.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcshareddata
│ └── xcschemes
│ │ ├── LIFXHTTPKit-watchOS.xcscheme
│ │ ├── LIFXHTTPKit-iOS.xcscheme
│ │ └── LIFXHTTPKit-Mac.xcscheme
└── project.pbxproj
├── Tests
├── Secrets.example.plist
├── SecretsHelper.swift
├── ClientHelper.swift
├── Info.plist
├── ClientTests.swift
├── LightTests.swift
├── LightTargetSelectorTests.swift
└── LightTargetTests.swift
├── Source
├── LIFXHTTPKit.h
├── ClientObserver.swift
├── LightTargetObserver.swift
├── State.swift
├── Scene.swift
├── Location.swift
├── Group.swift
├── Result.swift
├── Errors.swift
├── Info.plist
├── ProductInformation.swift
├── Color.swift
├── LightTargetSelector.swift
├── HTTPOperation.swift
├── Light.swift
├── Client.swift
├── HTTPSession.swift
└── LightTarget.swift
├── .gitignore
├── CONTRIBUTING.markdown
├── LICENSE.txt
├── CHANGELOG.markdown
└── README.markdown
/LIFXHTTPKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Tests/Secrets.example.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AccessToken
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Source/LIFXHTTPKit.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 29/05/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | #import
7 |
8 | FOUNDATION_EXPORT double LIFXHTTPKitVersionNumber;
9 | FOUNDATION_EXPORT const unsigned char LIFXHTTPKitVersionString[];
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | build/
3 | *.pbxuser
4 | !default.pbxuser
5 | *.mode1v3
6 | !default.mode1v3
7 | *.mode2v3
8 | !default.mode2v3
9 | *.perspectivev3
10 | !default.perspectivev3
11 | xcuserdata
12 | *.xccheckout
13 | *.moved-aside
14 | DerivedData
15 | *.hmap
16 | *.ipa
17 | *.xcuserstate
18 | .idea/
19 |
20 | # Configuration
21 | Tests/Secrets.plist
22 |
--------------------------------------------------------------------------------
/CONTRIBUTING.markdown:
--------------------------------------------------------------------------------
1 | All patches and feedback welcome. See the [issues](https://github.com/tatey/LIFXHTTPKit/issues) if you don't know where to start.
2 |
3 | 1. Fork it (https://github.com/tatey/LIFXHTTPKit/fork)
4 | 2. Create your feature branch (git checkout -b my-new-feature)
5 | 3. Commit your changes (git commit -am 'Add some feature')
6 | 4. Push to the branch (git push origin my-new-feature)
7 | 5. Create a new Pull Request
8 |
--------------------------------------------------------------------------------
/Source/ClientObserver.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 13/06/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | class ClientObserver {
9 | typealias LightsDidUpdate = (_ lights: [Light]) -> Void
10 |
11 | let lightsDidUpdateHandler: LightsDidUpdate
12 |
13 | init(lightsDidUpdateHandler: @escaping LightsDidUpdate) {
14 | self.lightsDidUpdateHandler = lightsDidUpdateHandler
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Source/LightTargetObserver.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 13/06/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public class LightTargetObserver {
9 | public typealias StateDidUpdate = () -> Void
10 |
11 | let stateDidUpdateHandler: StateDidUpdate
12 |
13 | init(stateDidUpdateHandler: @escaping StateDidUpdate) {
14 | self.stateDidUpdateHandler = stateDidUpdateHandler
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Source/State.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 6/10/2015.
3 | // Copyright © 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public struct State: Equatable {
9 | public let selector: LightTargetSelector
10 | public let brightness: Double?
11 | public let color: Color?
12 | public let power: Bool?
13 | }
14 |
15 | public func ==(lhs: State, rhs: State) -> Bool {
16 | return lhs.brightness == rhs.brightness &&
17 | lhs.color == rhs.color &&
18 | lhs.selector == rhs.selector
19 | }
20 |
--------------------------------------------------------------------------------
/Source/Scene.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 6/10/2015.
3 | // Copyright © 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public struct Scene: Equatable {
9 | public let uuid: String
10 | public let name: String
11 | public let states: [State]
12 |
13 | public func toSelector() -> LightTargetSelector {
14 | return LightTargetSelector(type: .SceneID, value: uuid)
15 | }
16 | }
17 |
18 | public func ==(lhs: Scene, rhs: Scene) -> Bool {
19 | return lhs.uuid == rhs.uuid &&
20 | lhs.name == rhs.name &&
21 | lhs.states == rhs.states
22 | }
23 |
--------------------------------------------------------------------------------
/Tests/SecretsHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 21/06/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | class SecretsHelper {
9 | private static let dictionary: NSDictionary = {
10 | if let path = Bundle(for: SecretsHelper.self).path(forResource: "Secrets", ofType: "plist"), let dictionary = NSDictionary(contentsOfFile: path) {
11 | return dictionary
12 | } else {
13 | fatalError("Missing secrets.plist. See README.")
14 | }
15 | }()
16 |
17 | static var accessToken: String {
18 | return dictionary["AccessToken"] as? String ?? ""
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Source/Location.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 15/07/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public struct Location: Equatable {
9 | public let id: String
10 | public let name: String
11 |
12 | public func toSelector() -> LightTargetSelector {
13 | return LightTargetSelector(type: .LocationID, value: id)
14 | }
15 |
16 | // MARK: Printable
17 |
18 | public var description: String {
19 | return ""
20 | }
21 | }
22 |
23 | public func ==(lhs: Location, rhs: Location) -> Bool {
24 | return lhs.id == rhs.id && lhs.name == rhs.name
25 | }
26 |
--------------------------------------------------------------------------------
/Source/Group.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 15/07/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public struct Group: Equatable, CustomStringConvertible {
9 | public let id: String
10 | public let name: String
11 |
12 | public func toSelector() -> LightTargetSelector {
13 | return LightTargetSelector(type: .GroupID, value: id)
14 | }
15 |
16 | // MARK: Printable
17 |
18 | public var description: String {
19 | return ""
20 | }
21 | }
22 |
23 | public func ==(lhs: Group, rhs: Group) -> Bool {
24 | return lhs.id == rhs.id && lhs.name == rhs.name
25 | }
26 |
--------------------------------------------------------------------------------
/Tests/ClientHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 12/07/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 | import LIFXHTTPKit
8 |
9 | class ClientHelper {
10 | static let sharedClient: Client = {
11 | let client = Client(accessToken: SecretsHelper.accessToken)
12 | let semaphore = DispatchSemaphore(value: 0)
13 | client.fetch { (errors) in
14 | if errors.count > 0 {
15 | fatalError("\(#function): Shared client failed to initialize. Are you using a genuine access token? See README.")
16 | }
17 | semaphore.signal()
18 | }
19 | _ = semaphore.wait(timeout: DispatchTime.distantFuture)
20 | return client
21 | }()
22 | }
23 |
--------------------------------------------------------------------------------
/Source/Result.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 13/06/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public struct Result: Equatable, CustomStringConvertible {
9 | public enum Status: String {
10 | case OK = "ok"
11 | case TimedOut = "timed_out"
12 | case Offline = "offline"
13 | }
14 |
15 | public let id: String
16 | public let status: Status
17 |
18 | // MARK: Printable
19 |
20 | public var description: String {
21 | return ""
22 | }
23 | }
24 |
25 | public func ==(lhs: Result, rhs: Result) -> Bool {
26 | return lhs.id == rhs.id && lhs.status == rhs.status
27 | }
28 |
--------------------------------------------------------------------------------
/Source/Errors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 13/06/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public let ErrorDomain: String = "LIFXHTTPKitErrorDomain"
9 |
10 | public enum ErrorCode: Int {
11 | // LIFXHTTPKit Errors
12 | case jsonInvalid
13 | case unacceptableSelector
14 |
15 | // HTTP Errors
16 | case unexpectedHTTPStatusCode
17 | case unauthorized // 401
18 | case forbidden // 403
19 | case tooManyRequests // 429
20 | case serverError // 5XX
21 | }
22 |
23 | struct HTTPKitError: Error {
24 | let code: ErrorCode
25 | let message: String
26 |
27 | init(code: ErrorCode, message: String) {
28 | self.code = code
29 | self.message = message
30 | }
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/Tests/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 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Tests/ClientTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 29/05/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import XCTest
7 | import LIFXHTTPKit
8 |
9 | class ClientTests: XCTestCase {
10 | func testAllLightsReturnsLightTargetConfiguredWithAllSelector() {
11 | let client = Client(accessToken: "")
12 | let lightTarget = client.allLightTarget()
13 | XCTAssertEqual(lightTarget.selector, LightTargetSelector(type: .All), "Expected selector to be `.All` type")
14 | }
15 |
16 | func testFetchWithInvalidAccessTokenSetsErrors() {
17 | let expectation = self.expectation(description: "fetch")
18 |
19 | let client = Client(accessToken: "")
20 | client.fetch { (errors) in
21 | XCTAssertGreaterThan(errors.count, 0)
22 | expectation.fulfill()
23 | }
24 |
25 | waitForExpectations(timeout: 3.0, handler: nil)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Source/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 | 2.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSHumanReadableCopyright
24 | Copyright © 2016 Tate Johnson. All rights reserved.
25 | NSPrincipalClass
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Tate Johnson
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/Tests/LightTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 14/06/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import XCTest
7 | @testable import LIFXHTTPKit
8 |
9 | class LightTests: XCTestCase {
10 | func testLightWithPropertiesUsingPower() {
11 | let light1 = newLight()
12 | let light2 = light1.lightWithProperties(false)
13 | XCTAssertFalse(light2.power)
14 | XCTAssertNotEqual(light1, light2)
15 | }
16 |
17 | func testLightWithPropertiesUsingBrightness() {
18 | let light1 = newLight()
19 | let light2 = light1.lightWithProperties(brightness: 1.0)
20 | XCTAssertEqual(light2.brightness, 1.0)
21 | XCTAssertNotEqual(light1, light2)
22 | }
23 |
24 | func testLightWithPropertiesUsingColor() {
25 | let light1 = newLight()
26 | let light2 = light1.lightWithProperties(color: Color.white(5000))
27 | XCTAssertEqual(light2.color, Color.white(5000))
28 | XCTAssertNotEqual(light1, light2)
29 | }
30 |
31 | func testLightWithPropertiesUsingConnected() {
32 | let light1 = newLight()
33 | let light2 = light1.lightWithProperties(connected: false)
34 | XCTAssertFalse(light2.connected)
35 | XCTAssertNotEqual(light1, light2)
36 | }
37 |
38 | private func newLight() -> Light {
39 | return Light(id: "d3b2f2d97452", power: true, brightness: 0.5, color: Color.white(Color.defaultKelvin), productInfo: nil, label: "Lamp", connected: true, group: nil, location: nil, touchedAt: nil)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Source/ProductInformation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProductInformation.swift
3 | // LIFXHTTPKit
4 | //
5 | // Created by LIFX Laptop on 5/4/17.
6 |
7 | import Foundation
8 |
9 | public struct ProductInformation {
10 | public let productName: String
11 | public let manufacturer: String
12 | public let capabilities: Capabilities?
13 |
14 | public init?(data: NSDictionary) {
15 | guard let name = data["name"] as? String, let company = data["company"] as? String, let productCapabilities = data["capabilities"] as? NSDictionary else {
16 | return nil
17 | }
18 | productName = name
19 | manufacturer = company
20 | capabilities = Capabilities(data: productCapabilities)
21 | }
22 |
23 | var description: String {
24 | return "Name: \(productName) - manufactured by \(manufacturer) Capabilities supported - \(String(describing: capabilities?.description))"
25 | }
26 | }
27 |
28 | public struct Capabilities {
29 | public let hasColor: Bool
30 | public let hasIR: Bool
31 | public let hasMulitiZone: Bool
32 |
33 | public init?(data: NSDictionary) {
34 | guard let hasColor = data["has_color"] as? Bool,
35 | let hasIR = data["has_ir"] as? Bool, let multiZone = data["has_multizone"] as? Bool else {
36 | return nil
37 | }
38 |
39 | self.hasColor = hasColor
40 | self.hasIR = hasIR
41 | self.hasMulitiZone = multiZone
42 | }
43 |
44 | var description: String {
45 | return "Color - \(hasColor), Infra-red \(hasIR), Multiple zones - \(hasMulitiZone)"
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/Tests/LightTargetSelectorTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 6/10/2015.
3 | // Copyright © 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | import XCTest
9 | import LIFXHTTPKit
10 |
11 | class LightTargetSelectorTests: XCTestCase {
12 | func testNewSelectorWithRawSelectorHavingAll() {
13 | if let selector = LightTargetSelector(stringValue: "all") {
14 | XCTAssertEqual(selector.type, LightTargetSelectorType.All)
15 | XCTAssertEqual(selector.value, "")
16 | } else {
17 | XCTFail("Expected selector to be constructed.")
18 | }
19 | }
20 |
21 | func testNewSelectorWithRawSelectorHavingIDAndValue() {
22 | if let selector = LightTargetSelector(stringValue: "id:d3b2f2d97452") {
23 | XCTAssertEqual(selector.type, LightTargetSelectorType.ID)
24 | XCTAssertEqual(selector.value, "d3b2f2d97452")
25 | } else {
26 | XCTFail("Expected selector to be constructed.")
27 | }
28 | }
29 |
30 | func testNewSelectorWithRawSelectorHavingBadCombinations() {
31 | XCTAssertNil(LightTargetSelector(stringValue: ""))
32 | XCTAssertNil(LightTargetSelector(stringValue: "id:"))
33 | XCTAssertNil(LightTargetSelector(stringValue: ":"))
34 | }
35 |
36 | func testStringValue() {
37 | let selector1 = LightTargetSelector(type: .ID, value: "d3b2f2d97452")
38 | XCTAssertEqual(selector1.stringValue, "id:d3b2f2d97452")
39 |
40 | let selector2 = LightTargetSelector(type: .All)
41 | XCTAssertEqual(selector2.stringValue, "all")
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Source/Color.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 22/06/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public struct Color: Equatable, CustomStringConvertible {
9 | static let maxHue: Double = 360.0
10 | static let defaultKelvin: Int = 3500
11 |
12 | public let hue: Double
13 | public let saturation: Double
14 | public let kelvin: Int
15 |
16 | public init(hue: Double, saturation: Double, kelvin: Int) {
17 | self.hue = hue
18 | self.saturation = saturation
19 | self.kelvin = kelvin
20 | }
21 |
22 | public static func color(_ hue: Double, saturation: Double) -> Color {
23 | return Color(hue: hue, saturation: saturation, kelvin: Color.defaultKelvin)
24 | }
25 |
26 | public static func white(_ kelvin: Int) -> Color {
27 | return Color(hue: 0.0, saturation: 0.0, kelvin: kelvin)
28 | }
29 |
30 | public var isColor: Bool {
31 | return !isWhite
32 | }
33 |
34 | public var isWhite: Bool {
35 | return saturation == 0.0
36 | }
37 |
38 | func toQueryStringValue() -> String {
39 | if isWhite {
40 | return "kelvin:\(kelvin)"
41 | } else {
42 | return "hue:\(hue) saturation:\(saturation)"
43 | }
44 | }
45 |
46 | // MARK: Printable
47 |
48 | public var description: String {
49 | return ""
50 | }
51 | }
52 |
53 | public func ==(lhs: Color, rhs: Color) -> Bool {
54 | return lhs.hue == rhs.hue &&
55 | lhs.saturation == rhs.saturation &&
56 | lhs.kelvin == rhs.kelvin
57 | }
58 |
--------------------------------------------------------------------------------
/Source/LightTargetSelector.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 8/10/2015.
3 | // Copyright © 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public enum LightTargetSelectorType: String {
9 | case All = "all"
10 | case ID = "id"
11 | case GroupID = "group_id"
12 | case LocationID = "location_id"
13 | case SceneID = "scene_id"
14 | case Label = "label"
15 | }
16 |
17 | public struct LightTargetSelector: Equatable, CustomStringConvertible {
18 | public let type: LightTargetSelectorType
19 | public let value: String
20 |
21 | public init(type: LightTargetSelectorType, value: String = "") {
22 | self.type = type
23 | self.value = value
24 |
25 | if (type == .Label) {
26 | print("Constructing selectors with `.Label` type is deprecated and will be removed in a future version.")
27 | }
28 | }
29 |
30 | public init?(stringValue: String) {
31 | let components = stringValue.components(separatedBy: ":")
32 | if let type = LightTargetSelectorType(rawValue: components.first ?? "") {
33 | if type == .All {
34 | self.type = type
35 | value = ""
36 | } else if let value = components.last, value.characters.count > 0 {
37 | self.type = type
38 | self.value = value
39 | } else {
40 | return nil
41 | }
42 | } else {
43 | return nil
44 | }
45 | }
46 |
47 | public var stringValue: String {
48 | if type == .All {
49 | return type.rawValue
50 | } else {
51 | return "\(type.rawValue):\(value)"
52 | }
53 | }
54 |
55 | func toQueryStringValue() -> String {
56 | return stringValue
57 | }
58 |
59 | // MARK: Printable
60 |
61 | public var description: String {
62 | return ""
63 | }
64 | }
65 |
66 | public func ==(lhs: LightTargetSelector, rhs: LightTargetSelector) -> Bool {
67 | if lhs.type == .All {
68 | return lhs.type == rhs.type
69 | } else {
70 | return lhs.type == rhs.type && lhs.value == rhs.value
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Source/HTTPOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 16/10/2015.
3 | // Copyright © 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | class HTTPOperationState {
9 | var cancelled: Bool
10 | var executing: Bool
11 | var finished: Bool
12 |
13 | init() {
14 | cancelled = false
15 | executing = false
16 | finished = false
17 | }
18 | }
19 |
20 | class HTTPOperation: Operation {
21 | private let state: HTTPOperationState
22 | private let delegateQueue: DispatchQueue
23 | private var task: URLSessionDataTask?
24 |
25 | init(URLSession: Foundation.URLSession, delegateQueue: DispatchQueue, request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) {
26 | state = HTTPOperationState()
27 | self.delegateQueue = delegateQueue
28 |
29 | super.init()
30 |
31 | task = URLSession.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in
32 | if let strongSelf = self {
33 | strongSelf.isExecuting = false
34 | strongSelf.isFinished = true
35 | strongSelf.delegateQueue.async {
36 | completionHandler(data, response, error as NSError?)
37 | }
38 | }
39 | })
40 | }
41 |
42 | override var isAsynchronous: Bool {
43 | return true
44 | }
45 |
46 | override private(set) var isCancelled: Bool {
47 | get { return state.cancelled }
48 | set {
49 | willChangeValue(forKey: "isCancelled")
50 | state.cancelled = newValue
51 | didChangeValue(forKey: "isCancelled")
52 | }
53 | }
54 |
55 | override private(set) var isExecuting: Bool {
56 | get { return state.executing }
57 | set {
58 | willChangeValue(forKey: "isExecuting")
59 | state.executing = newValue
60 | didChangeValue(forKey: "isExecuting")
61 | }
62 | }
63 |
64 | override private(set) var isFinished: Bool {
65 | get { return state.finished }
66 | set {
67 | willChangeValue(forKey: "isFinished")
68 | state.finished = newValue
69 | didChangeValue(forKey: "isFinished")
70 | }
71 | }
72 |
73 | override func start() {
74 | if isCancelled {
75 | return
76 | }
77 |
78 | task?.resume()
79 | isExecuting = true
80 | }
81 |
82 | override func cancel() {
83 | task?.cancel()
84 | isCancelled = true
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Source/Light.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 13/06/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public struct Light: Equatable, CustomStringConvertible {
9 | public let id: String
10 | public let power: Bool
11 | public let brightness: Double
12 | public let color: Color
13 | public let productInfo: ProductInformation?
14 | public let label: String
15 | public let connected: Bool
16 | public let group: Group?
17 | public let location: Location?
18 | public let touchedAt: Date?
19 |
20 |
21 | public func toSelector() -> LightTargetSelector {
22 | return LightTargetSelector(type: .ID, value: id)
23 | }
24 |
25 | func lightWithProperties(_ power: Bool? = nil, brightness: Double? = nil, color: Color? = nil, productInformation: ProductInformation? = nil, connected: Bool? = nil, touchedAt: Date? = nil) -> Light {
26 | return Light(id: id, power: power ?? self.power, brightness: brightness ?? self.brightness, color: color ?? self.color, productInfo: productInformation ?? self.productInfo, label: label, connected: connected ?? self.connected, group: group, location: location, touchedAt: touchedAt ?? Date())
27 | }
28 |
29 | // MARK: Capabilities
30 |
31 | public var hasColor: Bool {
32 | return self.productInfo?.capabilities?.hasColor ?? false
33 | }
34 |
35 | public var hasIR: Bool {
36 | return self.productInfo?.capabilities?.hasIR ?? false
37 | }
38 |
39 | public var hasMultiZone: Bool {
40 | return self.productInfo?.capabilities?.hasMulitiZone ?? false
41 | }
42 |
43 | // MARK: Printable
44 |
45 | public var description: String {
46 | return ""
47 | }
48 | }
49 |
50 | public func ==(lhs: Light, rhs: Light) -> Bool {
51 | return lhs.id == rhs.id &&
52 | lhs.power == rhs.power &&
53 | lhs.brightness == rhs.brightness &&
54 | lhs.color == rhs.color &&
55 | lhs.label == rhs.label &&
56 | lhs.connected == rhs.connected &&
57 | lhs.group == rhs.group &&
58 | lhs.location == rhs.location
59 | }
60 |
--------------------------------------------------------------------------------
/CHANGELOG.markdown:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 3.0.0 / 2017-06-12
4 |
5 | * **(Breaking)** Add support for Swift 3.0.
6 | * Expose `productInfo` on `Light` for determining product features.
7 |
8 | ## 2.0.0 / 2016-07-08
9 |
10 | * **(Breaking)** Add support for watchOS 2.0. Minimum iOS target raised to 8.2.
11 | * Expose `lastTouched` to `Light` and `LightTarget` for determining the freshness of remotely fetched data.
12 |
13 | ## 1.0.0 / 2015-10-28
14 |
15 | * Migrate from v1beta to v1 of the LIFX HTTP API.
16 | * Add support for iOS 8.0+.
17 | * **(Breaking)** Add support for Swift 2. You must build with Xcode 7.0+.
18 | * Add support for light target based scenes.
19 | * Add codified errors for various HTTP status codes.
20 | * Changed `Client` initializers to optionally take cached lights and scenes for faster restore.
21 | * Changed `HTTPSession` initializer to take `delegateQueue` and `timeout` arguments.
22 | * Changed `HTTPSession` to guarantee requests are performed serially, in-order.
23 | * **(Breaking)** Changed the completion handler in `Client -fetch:` to pass an array of aggregated errors instead of a single optional error.
24 | * **(Breaking)** Renamed `Selector` to `LightTargetSelector` for better interoperability in Xcode 7. Unfortunately a breaking change was unavoidable.
25 | * Publicly exposed `session` on `Client` to easily get a configured session from the client.
26 | * Publicly exposed `lights` and `scenes` as read-only on `Client` making it possible to inspect the state of the client.
27 | * Publicly exposed `lights` as read-only on `LightTarget` in favour of calling `toLights()` to be consistent with `Client`.
28 | * Publicly exposed `baseURL`, `delegateQueue`, and `URLSession` as read-only on `HTTPSession`.
29 | * Deprecated constructing selectors with `.Label` type.
30 | * Deprecated `HTTPSession -setLightsPower:power:duration:completionHandler:` and `HTTPSession -setLightsColor:color:duration:powerOn:completionHandler:`. Use `HTTPSession -setLightsState:power:color:brightness:duration:completionHandler:` instead.
31 | * Deprecated `LightTarget -setColor:brightness:power:duration:completionHandler:`. Use `LightTarget -setState:brightness:power:duration:completionHandler:` instead.
32 | * Deprecated `LightTarget -toLights`. Use the `lights` property instead.
33 |
34 | ## 0.0.2 / 2015-09-02
35 |
36 | * `LightTarget -setBrightness:duration:completionHandler:`, `LightTarget -setColor:duration:completionHandler:`, and `LightTarget -setColor:brightness:duration:power:completionHandler:` respects `power` parameter by optimistically updating in-memory cache.
37 |
38 | ## 0.0.1 / 2015-07-22
39 |
40 | * Initial release
41 |
--------------------------------------------------------------------------------
/LIFXHTTPKit.xcodeproj/xcshareddata/xcschemes/LIFXHTTPKit-watchOS.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 |
--------------------------------------------------------------------------------
/Tests/LightTargetTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 14/06/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import XCTest
7 | import LIFXHTTPKit
8 |
9 | class LightTargetTests: XCTestCase {
10 | lazy var lightTarget: LightTarget = {
11 | if let first = ClientHelper.sharedClient.allLightTarget().toLightTargets().first {
12 | return first
13 | } else {
14 | fatalError("\(#function): Expected at least one connected light target")
15 | }
16 | }()
17 |
18 | func testObserver() {
19 | let expectation = self.expectation(description: "observer")
20 | let observer = lightTarget.addObserver {
21 | expectation.fulfill()
22 | }
23 | lightTarget.setPower(true, duration: 0.0, completionHandler: nil)
24 | waitForExpectations(timeout: 3.0, handler: nil)
25 | lightTarget.removeObserver(observer)
26 | }
27 |
28 | func testSetPower() {
29 | let expectation = self.expectation(description: "setPower")
30 | let newPower = !lightTarget.power
31 | lightTarget.setPower(newPower, duration: 0.0, completionHandler: { (results, error) in
32 | DispatchQueue.main.async {
33 | XCTAssertNil(error, "expected error to be nil")
34 | XCTAssertEqual(newPower, self.lightTarget.power, "power is still new value after operation is completed")
35 | expectation.fulfill()
36 | }
37 | })
38 | XCTAssertEqual(newPower, lightTarget.power, "power is optimstically set to new value")
39 | waitForExpectations(timeout: 3.0, handler: nil)
40 | }
41 |
42 | func testSetBrightness() {
43 | let expectation = self.expectation(description: "setBrightness")
44 | let newBrightness = Double(arc4random_uniform(100)) / 100.0
45 | lightTarget.setBrightness(newBrightness, duration: 0.0) { (results, error) in
46 | DispatchQueue.main.async {
47 | XCTAssertNil(error, "expected error to be nil")
48 | XCTAssertEqual(newBrightness, self.lightTarget.brightness, "brightness is still new value after operation is completed")
49 | expectation.fulfill()
50 | }
51 | }
52 | XCTAssertEqual(newBrightness, lightTarget.brightness, "brightness is optimistically set to new value")
53 | waitForExpectations(timeout: 3.0, handler: nil)
54 | }
55 |
56 | func testSetColor() {
57 | let expectation = self.expectation(description: "setColor")
58 | let newColor = Color.color(Double(arc4random_uniform(360)), saturation: 0.5)
59 | lightTarget.setColor(newColor, duration: 0.0, completionHandler: { (results, error) in
60 | DispatchQueue.main.async {
61 | XCTAssertNil(error, "expected error to be nil")
62 | XCTAssertEqual(newColor.hue, self.lightTarget.color.hue, "hue is still new value after operation is completed")
63 | expectation.fulfill()
64 | }
65 | })
66 | XCTAssertEqual(newColor.hue, lightTarget.color.hue, "hue is optimsitically set to new value")
67 | waitForExpectations(timeout: 3.0, handler: nil)
68 | }
69 |
70 | func testRestoreState() {
71 | let client = ClientHelper.sharedClient
72 | if let scene = client.scenes.first {
73 | let expectation = self.expectation(description: "restoreState")
74 |
75 | let selector = LightTargetSelector(type: .SceneID, value: scene.uuid)
76 | let lightTarget = client.lightTargetWithSelector(selector)
77 | XCTAssertEqual(scene.name, lightTarget.label)
78 | lightTarget.restoreState(0.0) { (results, error) in
79 | XCTAssertNil(error, "expected error to be nil")
80 | expectation.fulfill()
81 | }
82 |
83 | waitForExpectations(timeout: 3.0, handler: nil)
84 | } else {
85 | print("Skipping \(#function): Authenticated account doesn't have any scenes")
86 | }
87 | }
88 |
89 | func testToGroupTargets() {
90 | let groups = lightTarget.toGroupTargets()
91 | XCTAssertGreaterThan(groups.count, 0, "expected at least one group")
92 | XCTAssertEqual(groups.first!.selector.type, LightTargetSelectorType.GroupID, "expected selector type to be GroupID")
93 | }
94 |
95 | func testToLocationTargets() {
96 | let locations = lightTarget.toLocationTargets()
97 | XCTAssertGreaterThan(locations.count, 0, "expected at least one location")
98 | XCTAssertEqual(locations.first!.selector.type, LightTargetSelectorType.LocationID, "expected selector type to be LocationID")
99 | }
100 |
101 | func testLightTargetTouchedAt() {
102 | XCTAssertEqualWithAccuracy(lightTarget.touchedAt.timeIntervalSinceNow, Date().timeIntervalSinceNow, accuracy: 10.0)
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Source/Client.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 29/05/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public class Client {
9 | public let session: HTTPSession
10 | public private(set) var lights: [Light]
11 | public private(set) var scenes: [Scene]
12 | private var observers: [ClientObserver]
13 |
14 | public convenience init(accessToken: String, lights: [Light]? = nil, scenes: [Scene]? = nil) {
15 | self.init(session: HTTPSession(accessToken: accessToken), lights: lights, scenes: scenes)
16 | }
17 |
18 | public init(session: HTTPSession, lights: [Light]? = nil, scenes: [Scene]? = nil) {
19 | self.session = session
20 | self.lights = lights ?? []
21 | self.scenes = scenes ?? []
22 | observers = []
23 | }
24 |
25 | public func fetch(completionHandler: ((_ errors: [Error]) -> Void)? = nil) {
26 | let group = DispatchGroup()
27 | var errors: [Error] = []
28 |
29 | group.enter()
30 | fetchLights { (error) in
31 | if let error = error {
32 | errors.append(error)
33 | }
34 | group.leave()
35 | }
36 |
37 | group.enter()
38 | fetchScenes { (error) in
39 | if let error = error {
40 | errors.append(error)
41 | }
42 | group.leave()
43 | }
44 |
45 | group.notify(queue: session.delegateQueue) {
46 | completionHandler?(errors)
47 | }
48 | }
49 |
50 | public func fetchLights(completionHandler: ((_ error: Error?) -> Void)? = nil) {
51 | session.lights("all") { [weak self] (request, response, lights, error) in
52 | if error != nil {
53 | completionHandler?(error)
54 | return
55 | }
56 |
57 | if let strongSelf = self {
58 | let oldLights = strongSelf.lights
59 | let newLights = lights
60 | if oldLights != newLights {
61 | strongSelf.lights = newLights
62 | for observer in strongSelf.observers {
63 | observer.lightsDidUpdateHandler(lights)
64 | }
65 | }
66 |
67 | }
68 |
69 | completionHandler?(nil)
70 | }
71 | }
72 |
73 | public func fetchScenes(completionHandler: ((_ error: Error?) -> Void)? = nil) {
74 | session.scenes { [weak self] (request, response, scenes, error) in
75 | if error != nil {
76 | completionHandler?(error)
77 | return
78 | }
79 |
80 | self?.scenes = scenes
81 |
82 | completionHandler?(nil)
83 | }
84 | }
85 |
86 | public func allLightTarget() -> LightTarget {
87 | return lightTargetWithSelector(LightTargetSelector(type: .All))
88 | }
89 |
90 | public func lightTargetWithSelector(_ selector: LightTargetSelector) -> LightTarget {
91 | return LightTarget(client: self, selector: selector, filter: selectorToFilter(selector))
92 | }
93 |
94 | func addObserver(lightsDidUpdateHandler: @escaping ClientObserver.LightsDidUpdate) -> ClientObserver {
95 | let observer = ClientObserver(lightsDidUpdateHandler: lightsDidUpdateHandler)
96 | observers.append(observer)
97 | return observer
98 | }
99 |
100 | func removeObserver(observer: ClientObserver) {
101 | for (index, other) in observers.enumerated() {
102 | if other === observer {
103 | observers.remove(at: index)
104 | break
105 | }
106 | }
107 | }
108 |
109 | func updateLights(_ lights: [Light]) {
110 | let oldLights = self.lights
111 | var newLights: [Light] = []
112 |
113 | for light in lights {
114 | if !newLights.contains(where: { $0.id == light.id }) {
115 | newLights.append(light)
116 | }
117 | }
118 | for light in oldLights {
119 | if !newLights.contains(where: { $0.id == light.id }) {
120 | newLights.append(light)
121 | }
122 | }
123 |
124 | if oldLights != newLights {
125 | for observer in observers {
126 | observer.lightsDidUpdateHandler(newLights)
127 | }
128 | self.lights = newLights
129 | }
130 | }
131 |
132 | private func selectorToFilter(_ selector: LightTargetSelector) -> LightTargetFilter {
133 | switch selector.type {
134 | case .All:
135 | return { (light) in return true }
136 | case .ID:
137 | return { (light) in return light.id == selector.value }
138 | case .GroupID:
139 | return { (light) in return light.group?.id == selector.value }
140 | case .LocationID:
141 | return { (light) in return light.location?.id == selector.value }
142 | case .SceneID:
143 | return { [weak self] (light) in
144 | if let strongSelf = self, let index = strongSelf.scenes.index(where: { $0.toSelector() == selector }) {
145 | let scene = strongSelf.scenes[index]
146 | return scene.states.contains { (state) in
147 | let filter = strongSelf.selectorToFilter(state.selector)
148 | return filter(light)
149 | }
150 | } else {
151 | return false
152 | }
153 | }
154 | case .Label:
155 | return { (light) in return light.label == selector.value }
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/LIFXHTTPKit.xcodeproj/xcshareddata/xcschemes/LIFXHTTPKit-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
66 |
67 |
68 |
78 |
79 |
85 |
86 |
87 |
88 |
89 |
90 |
96 |
97 |
103 |
104 |
105 |
106 |
108 |
109 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/LIFXHTTPKit.xcodeproj/xcshareddata/xcschemes/LIFXHTTPKit-Mac.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
57 |
58 |
59 |
60 |
61 |
62 |
68 |
69 |
70 |
71 |
72 |
73 |
83 |
84 |
90 |
91 |
92 |
93 |
94 |
95 |
101 |
102 |
108 |
109 |
110 |
111 |
113 |
114 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/Source/HTTPSession.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 13/06/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public class HTTPSession {
9 | public static let defaultBaseURL: URL = URL(string: "https://api.lifx.com/v1/")!
10 | public static let defaultUserAgent: String = "LIFXHTTPKit/\(LIFXHTTPKitVersionNumber)"
11 | public static let defaultTimeout: TimeInterval = 5.0
12 |
13 | public let baseURL: URL
14 | public let delegateQueue: DispatchQueue
15 | public let URLSession: Foundation.URLSession
16 |
17 | private let operationQueue: OperationQueue
18 |
19 | public init(accessToken: String, delegateQueue: DispatchQueue = DispatchQueue(label: "com.tatey.lifx-http-kit.http-session", attributes: []), baseURL: URL = HTTPSession.defaultBaseURL, userAgent: String = HTTPSession.defaultUserAgent, timeout: TimeInterval = HTTPSession.defaultTimeout) {
20 | self.baseURL = baseURL
21 | self.delegateQueue = delegateQueue
22 |
23 | let configuration = URLSessionConfiguration.default
24 | configuration.httpAdditionalHeaders = ["Authorization": "Bearer \(accessToken)", "Accept": "appplication/json", "User-Agent": userAgent]
25 | configuration.timeoutIntervalForRequest = timeout
26 | URLSession = Foundation.URLSession(configuration: configuration)
27 |
28 | operationQueue = OperationQueue()
29 | operationQueue.maxConcurrentOperationCount = 1
30 | }
31 |
32 | public func lights(_ selector: String = "all", completionHandler: @escaping ((_ request: URLRequest, _ response: URLResponse?, _ lights: [Light], _ error: Error?) -> Void)) {
33 | var request = URLRequest(url: baseURL.appendingPathComponent("lights/\(selector)"))
34 | request.httpMethod = "GET"
35 | addOperationWithRequest(request as URLRequest) { (data, response, error) in
36 | if let error = error ?? self.validateResponseWithExpectedStatusCodes(response, statusCodes: [200]) {
37 | completionHandler(request as URLRequest, response, [], error)
38 | } else {
39 | let (lights, error) = self.dataToLights(data)
40 | completionHandler(request, response, lights, error)
41 | }
42 | }
43 | }
44 |
45 | public func setLightsPower(_ selector: String, power: Bool, duration: Float, completionHandler: @escaping ((_ request: URLRequest, _ response: URLResponse?, _ results: [Result], _ error: Error?) -> Void)) {
46 | print("`setLightsPower:power:duration:completionHandler:` is deprecated and will be removed in a future version. Use `setLightsState:power:color:brightness:duration:completionHandler:` instead.")
47 | setLightsState(selector, power: power, duration: duration, completionHandler: completionHandler)
48 | }
49 |
50 | public func setLightsColor(_ selector: String, color: String, duration: Float, powerOn: Bool, completionHandler: @escaping ((_ request: URLRequest, _ response: URLResponse?, _ results: [Result], _ error: Error?) -> Void)) {
51 | print("`setLightsColor:color:duration:powerOn:completionHandler:` is deprecated and will be removed in a future version. Use `setLightsState:power:color:brightness:duration:completionHandler:` instead.")
52 | setLightsState(selector, power: powerOn, color: color, duration: duration, completionHandler: completionHandler)
53 | }
54 |
55 | public func setLightsState(_ selector: String, power: Bool? = nil, color: String? = nil, brightness: Double? = nil, duration: Float, completionHandler: @escaping ((_ request: URLRequest, _ response: URLResponse?, _ results: [Result], _ error: Error?) -> Void)) {
56 | var request = URLRequest(url: baseURL.appendingPathComponent("lights/\(selector)/state"))
57 | var parameters: [String : Any] = ["duration": duration as AnyObject]
58 | if let power = power {
59 | parameters["power"] = power ? "on" : "off" as AnyObject?
60 | }
61 | if let color = color {
62 | parameters["color"] = color as AnyObject?
63 | }
64 | if let brightness = brightness {
65 | parameters["brightness"] = brightness as AnyObject?
66 | }
67 | request.httpMethod = "PUT"
68 | request.httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: [])
69 | request.setValue("application/json", forHTTPHeaderField: "Content-Type")
70 | addOperationWithRequest(request as URLRequest) { (data, response, error) in
71 | if let error = error ?? self.validateResponseWithExpectedStatusCodes(response, statusCodes: [200, 207]) {
72 | completionHandler(request as URLRequest, response, [], error)
73 | } else {
74 | let (results, error) = self.dataToResults(data)
75 | completionHandler(request, response, results, error)
76 | }
77 | }
78 | }
79 |
80 | public func scenes(_ completionHandler: @escaping ((_ request: URLRequest, _ response: URLResponse?, _ scenes: [Scene], _ error: Error?) -> Void)) {
81 | var request = URLRequest(url: baseURL.appendingPathComponent("scenes"))
82 | request.httpMethod = "GET"
83 | addOperationWithRequest(request as URLRequest) { (data, response, error) in
84 | if let error = error ?? self.validateResponseWithExpectedStatusCodes(response, statusCodes: [200]) {
85 | completionHandler(request as URLRequest, response, [], error)
86 | } else {
87 | let (scenes, error) = self.dataToScenes(data)
88 | completionHandler(request, response, scenes, error)
89 | }
90 | }
91 | }
92 |
93 | public func setScenesActivate(_ selector: String, duration: Float, completionHandler: @escaping ((_ request: URLRequest, _ response: URLResponse?, _ results: [Result], _ error: Error?) -> Void)) {
94 | var request = URLRequest(url: baseURL.appendingPathComponent("scenes/\(selector)/activate"))
95 | let parameters = ["duration": duration] as [String: Any]
96 | request.httpMethod = "PUT"
97 | request.httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: [])
98 | addOperationWithRequest(request as URLRequest) { (data, response, error) in
99 | if let error = error ?? self.validateResponseWithExpectedStatusCodes(response, statusCodes: [200, 207]) {
100 | completionHandler(request as URLRequest, response, [], error)
101 | } else {
102 | let (results, error) = self.dataToResults(data)
103 | completionHandler(request, response, results, error)
104 | }
105 | }
106 | }
107 |
108 | // MARK: Helpers
109 |
110 | private func addOperationWithRequest(_ request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) {
111 | let operation = HTTPOperation(URLSession: URLSession, delegateQueue: delegateQueue, request: request, completionHandler: completionHandler)
112 | operationQueue.operations.first?.addDependency(operation)
113 | operationQueue.addOperation(operation)
114 | }
115 |
116 | private func validateResponseWithExpectedStatusCodes(_ response: URLResponse?, statusCodes: [Int]) -> Error? {
117 | guard let response = response as? HTTPURLResponse else {
118 | return nil
119 | }
120 |
121 | if statusCodes.contains(response.statusCode) {
122 | return nil
123 | }
124 |
125 | switch (response.statusCode) {
126 | case 401:
127 | return HTTPKitError(code: .unauthorized, message: "Bad access token")
128 | case 403:
129 | return HTTPKitError(code: .forbidden, message: "Permission denied")
130 | case 429:
131 | return HTTPKitError(code: .tooManyRequests, message: "Rate limit exceeded")
132 | case 500, 502, 503, 523:
133 | return HTTPKitError(code: .unauthorized, message: "Server error")
134 | default:
135 | return HTTPKitError(code: .unexpectedHTTPStatusCode, message: "Expecting \(statusCodes), got \(response.statusCode)")
136 | }
137 | }
138 |
139 | private func dataToLights(_ data: Data?) -> (lights: [Light], error: Error?) {
140 | guard let data = data else {
141 | return ([], HTTPKitError(code: .jsonInvalid, message: "No data"))
142 | }
143 |
144 | let rootJSONObject: Any?
145 | do {
146 | rootJSONObject = try JSONSerialization.jsonObject(with: data, options: [])
147 | } catch let error {
148 | return ([], error)
149 | }
150 |
151 | let lightJSONObjects: [NSDictionary]
152 | if let array = rootJSONObject as? [NSDictionary] {
153 | lightJSONObjects = array
154 | } else {
155 | lightJSONObjects = []
156 | }
157 |
158 | var lights: [Light] = []
159 | for lightJSONObject in lightJSONObjects {
160 | if let id = lightJSONObject["id"] as? String,
161 | let power = lightJSONObject["power"] as? String,
162 | let brightness = lightJSONObject["brightness"] as? Double,
163 | let colorJSONObject = lightJSONObject["color"] as? NSDictionary,
164 | let colorHue = colorJSONObject["hue"] as? Double,
165 | let colorSaturation = colorJSONObject["saturation"] as? Double,
166 | let colorKelvin = colorJSONObject["kelvin"] as? Int,
167 | let label = lightJSONObject["label"] as? String,
168 | let connected = lightJSONObject["connected"] as? Bool {
169 | let group: Group?
170 | if let groupJSONObject = lightJSONObject["group"] as? NSDictionary,
171 | let groupId = groupJSONObject["id"] as? String,
172 | let groupName = groupJSONObject["name"] as? String {
173 | group = Group(id: groupId, name: groupName)
174 | } else {
175 | group = nil
176 | }
177 |
178 | let location: Location?
179 | if let locationJSONObject = lightJSONObject["location"] as? NSDictionary,
180 | let locationId = locationJSONObject["id"] as? String,
181 | let locationName = locationJSONObject["name"] as? String {
182 | location = Location(id: locationId, name: locationName)
183 | } else {
184 | location = nil
185 | }
186 |
187 | var productInformation: ProductInformation?
188 | if let productInformationJSONObject = lightJSONObject["product"] as? NSDictionary {
189 | productInformation = ProductInformation(data: productInformationJSONObject)
190 | }
191 |
192 | let color = Color(hue: colorHue, saturation: colorSaturation, kelvin: colorKelvin)
193 | let light = Light(id: id, power: power == "on", brightness: brightness, color: color, productInfo: productInformation, label: label, connected: connected, group: group, location: location, touchedAt: Date())
194 | lights.append(light)
195 | } else {
196 | return ([], HTTPKitError(code: .jsonInvalid, message: "JSON object is missing required properties"))
197 | }
198 | }
199 | return (lights, nil)
200 | }
201 |
202 | private func dataToScenes(_ data: Data?) -> (scenes: [Scene], error: Error?) {
203 | guard let data = data else {
204 | return ([], HTTPKitError(code: .jsonInvalid, message: "No data"))
205 | }
206 |
207 | let rootJSONObject: Any?
208 | do {
209 | rootJSONObject = try JSONSerialization.jsonObject(with: data, options: [])
210 | } catch let error {
211 | return ([], error)
212 | }
213 |
214 | let sceneJSONObjects: [NSDictionary]
215 | if let array = rootJSONObject as? [NSDictionary] {
216 | sceneJSONObjects = array
217 | } else {
218 | sceneJSONObjects = []
219 | }
220 |
221 | var scenes: [Scene] = []
222 | for sceneJSONObject in sceneJSONObjects {
223 | if let uuid = sceneJSONObject["uuid"] as? String,
224 | let name = sceneJSONObject["name"] as? String,
225 | let stateJSONObjects = sceneJSONObject["states"] as? [NSDictionary] {
226 | var states: [State] = []
227 | for stateJSONObject in stateJSONObjects {
228 | if let rawSelector = stateJSONObject["selector"] as? String,
229 | let selector = LightTargetSelector(stringValue: rawSelector) {
230 | let brightness = stateJSONObject["brightness"] as? Double ?? nil
231 | let color: Color?
232 | if let colorJSONObject = stateJSONObject["color"] as? NSDictionary,
233 | let colorHue = colorJSONObject["hue"] as? Double,
234 | let colorSaturation = colorJSONObject["saturation"] as? Double,
235 | let colorKelvin = colorJSONObject["kelvin"] as? Int {
236 | color = Color(hue: colorHue, saturation: colorSaturation, kelvin: colorKelvin)
237 | } else {
238 | color = nil
239 | }
240 | let power: Bool?
241 | if let powerJSONValue = stateJSONObject["power"] as? String {
242 | power = powerJSONValue == "on"
243 | } else {
244 | power = nil
245 | }
246 | let state = State(selector: selector, brightness: brightness, color: color, power: power)
247 | states.append(state)
248 | }
249 | }
250 | let scene = Scene(uuid: uuid, name: name, states: states)
251 | scenes.append(scene)
252 | }
253 | }
254 | return (scenes, nil)
255 | }
256 |
257 | private func dataToResults(_ data: Data?) -> (results: [Result], error: Error?) {
258 | guard let data = data else {
259 | return ([], HTTPKitError(code: .jsonInvalid, message: "No data"))
260 | }
261 |
262 | let rootJSONObject: Any
263 | do {
264 | rootJSONObject = try JSONSerialization.jsonObject(with: data, options: [])
265 | } catch let error {
266 | return ([], error)
267 | }
268 |
269 | let resultJSONObjects: [NSDictionary]
270 | if let dictionary = rootJSONObject as? NSDictionary, let array = dictionary["results"] as? [NSDictionary] {
271 | resultJSONObjects = array
272 | } else {
273 | resultJSONObjects = []
274 | }
275 |
276 | var results: [Result] = []
277 | for resultJSONObject in resultJSONObjects {
278 | if let id = resultJSONObject["id"] as? String, let status = Result.Status(rawValue: resultJSONObject["status"] as? String ?? "unknown") {
279 | let result = Result(id: id, status: status)
280 | results.append(result)
281 | } else {
282 | return ([], HTTPKitError(code: .jsonInvalid, message: "JSON object is missing required properties"))
283 | }
284 | }
285 |
286 | return (results, nil)
287 | }
288 | }
289 |
--------------------------------------------------------------------------------
/Source/LightTarget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tate Johnson on 13/06/2015.
3 | // Copyright (c) 2015 Tate Johnson. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | typealias LightTargetFilter = (_ light: Light) -> Bool
9 |
10 | public class LightTarget {
11 | public static let defaultDuration: Float = 0.5
12 |
13 | public private(set) var power: Bool
14 | public private(set) var brightness: Double
15 | public private(set) var color: Color
16 | public private(set) var label: String
17 | public private(set) var connected: Bool
18 | public private(set) var count: Int
19 | public private(set) var touchedAt: Date
20 |
21 | public let selector: LightTargetSelector
22 | public private(set) var lights: [Light]
23 | private let filter: LightTargetFilter
24 | private var observers: [LightTargetObserver]
25 |
26 | private let client: Client
27 | private var clientObserver: ClientObserver!
28 |
29 | init(client: Client, selector: LightTargetSelector, filter: @escaping LightTargetFilter) {
30 | power = false
31 | brightness = 0.0
32 | color = Color(hue: 0, saturation: 0, kelvin: Color.defaultKelvin)
33 | label = ""
34 | connected = false
35 | count = 0
36 | touchedAt = Date(timeIntervalSince1970: 0.0)
37 |
38 | self.selector = selector
39 | self.filter = filter
40 | lights = []
41 | observers = []
42 |
43 | self.client = client
44 | clientObserver = client.addObserver { [unowned self] (lights) in
45 | self.updateLights(lights)
46 | }
47 |
48 | updateLights(client.lights)
49 | }
50 |
51 | deinit {
52 | client.removeObserver(observer: clientObserver)
53 | }
54 |
55 | // MARK: Observers
56 |
57 | public func addObserver(_ stateDidUpdateHandler: @escaping LightTargetObserver.StateDidUpdate) -> LightTargetObserver {
58 | let observer = LightTargetObserver(stateDidUpdateHandler: stateDidUpdateHandler)
59 | observers.append(observer)
60 | return observer
61 | }
62 |
63 | public func removeObserver(_ observer: LightTargetObserver) {
64 | for (index, other) in observers.enumerated() {
65 | if other === observer {
66 | observers.remove(at: index)
67 | }
68 | }
69 | }
70 |
71 | public func removeAllObservers() {
72 | observers = []
73 | }
74 |
75 | private func notifyObservers() {
76 | for observer in observers {
77 | observer.stateDidUpdateHandler()
78 | }
79 | }
80 |
81 | // MARK: Slicing
82 |
83 | public func toLightTargets() -> [LightTarget] {
84 | return lights.map { (light) in return self.client.lightTargetWithSelector(LightTargetSelector(type: .ID, value: light.id)) }
85 | }
86 |
87 | public func toGroupTargets() -> [LightTarget] {
88 | return lights.reduce([]) { (groups, light) -> [Group] in
89 | if let group = light.group, !groups.contains(group) {
90 | return groups + [group]
91 | } else {
92 | return groups
93 | }
94 | }.map { (group) in
95 | return self.client.lightTargetWithSelector(group.toSelector())
96 | }
97 | }
98 |
99 | public func toLocationTargets() -> [LightTarget] {
100 | return lights.reduce([]) { (locations, light) -> [Location] in
101 | if let location = light.location, !locations.contains(location) {
102 | return locations + [location]
103 | } else {
104 | return locations
105 | }
106 | }.map { (location) in
107 | return self.client.lightTargetWithSelector(location.toSelector())
108 | }
109 | }
110 |
111 | public func toLights() -> [Light] {
112 | print("`toLights` is deprecated and will be removed in a future version. Use `lights` instead.")
113 | return lights
114 | }
115 |
116 | // MARK: Lighting Operations
117 |
118 | public func setPower(_ power: Bool, duration: Float = LightTarget.defaultDuration, completionHandler: ((_ results: [Result], _ error: Error?) -> Void)? = nil) {
119 | let oldPower = self.power
120 | client.updateLights(lights.map({ $0.lightWithProperties(power) }))
121 | client.session.setLightsState(selector.toQueryStringValue(), power: power, duration: duration) { [weak self] (request, response, results, error) in
122 | if let strongSelf = self {
123 | var newLights = strongSelf.lightsByDeterminingConnectivityWithResults(strongSelf.lights, results: results)
124 | if error != nil {
125 | newLights = newLights.map({ $0.lightWithProperties(oldPower) })
126 | }
127 | strongSelf.client.updateLights(newLights)
128 | }
129 | completionHandler?(results, error)
130 | }
131 | }
132 |
133 | public func setBrightness(_ brightness: Double, duration: Float = LightTarget.defaultDuration, completionHandler: ((_ results: [Result], _ error: Error?) -> Void)? = nil) {
134 | let oldBrightness = self.brightness
135 | client.updateLights(lights.map({ $0.lightWithProperties(brightness: brightness) }))
136 | client.session.setLightsState(selector.toQueryStringValue(), brightness: brightness, duration: duration) { [weak self] (request, response, results, error) in
137 | if let strongSelf = self {
138 | var newLights = strongSelf.lightsByDeterminingConnectivityWithResults(strongSelf.lights, results: results)
139 | if error != nil {
140 | newLights = newLights.map({ $0.lightWithProperties(brightness: oldBrightness) })
141 | }
142 | strongSelf.client.updateLights(newLights)
143 | }
144 | completionHandler?(results, error)
145 | }
146 | }
147 |
148 | public func setColor(_ color: Color, duration: Float = LightTarget.defaultDuration, completionHandler: ((_ results: [Result], _ error: Error?) -> Void)? = nil) {
149 | let oldColor = self.color
150 | client.updateLights(lights.map({ $0.lightWithProperties(color: color) }))
151 | client.session.setLightsState(selector.toQueryStringValue(), color: color.toQueryStringValue(), duration: duration) { [weak self] (request, response, results, error) in
152 | if let strongSelf = self {
153 | var newLights = strongSelf.lightsByDeterminingConnectivityWithResults(strongSelf.lights, results: results)
154 | if error != nil {
155 | newLights = newLights.map({ $0.lightWithProperties(color: oldColor) })
156 | }
157 | strongSelf.client.updateLights(newLights)
158 | }
159 | completionHandler?(results, error)
160 | }
161 | }
162 |
163 | public func setColor(_ color: Color, brightness: Double, power: Bool? = nil, duration: Float = LightTarget.defaultDuration, completionHandler: ((_ results: [Result], _ error: Error?) -> Void)? = nil) {
164 | print("`setColor:brightness:power:duration:completionHandler: is deprecated and will be removed in a future version. Use `setState:brightness:power:duration:completionHandler:` instead.")
165 | return setState(color, brightness: brightness, power: power, duration: duration, completionHandler: completionHandler)
166 | }
167 |
168 | public func setState(_ color: Color? = nil, brightness: Double? = nil, power: Bool? = nil, duration: Float = LightTarget.defaultDuration, completionHandler: ((_ results: [Result], _ error: Error?) -> Void)? = nil) {
169 | let oldBrightness = self.brightness
170 | let oldColor = self.color
171 | let oldPower = self.power
172 | client.updateLights(lights.map({ $0.lightWithProperties(power, brightness: brightness, color: color) }))
173 | client.session.setLightsState(selector.toQueryStringValue(), power: power, color: color?.toQueryStringValue(), brightness: brightness, duration: duration) { [weak self] (request, response, results, error) in
174 | if let strongSelf = self {
175 | var newLights = strongSelf.lightsByDeterminingConnectivityWithResults(strongSelf.lights, results: results)
176 | if error != nil {
177 | newLights = newLights.map({ $0.lightWithProperties(oldPower, brightness: oldBrightness, color: oldColor) })
178 | }
179 | strongSelf.client.updateLights(newLights)
180 | }
181 | completionHandler?(results, error)
182 | }
183 | }
184 |
185 | public func restoreState(_ duration: Float = LightTarget.defaultDuration, completionHandler: ((_ results: [Result], _ error: Error?) -> Void)? = nil) {
186 | if selector.type != .SceneID {
187 | let error = HTTPKitError(code: .unacceptableSelector, message: "Unsupported Selector. Only `.SceneID` is supported.")
188 | completionHandler?([], error)
189 | return
190 | }
191 |
192 | let states: [State]
193 | if let index = client.scenes.index(where: { $0.toSelector() == selector }) {
194 | states = client.scenes[index].states
195 | } else {
196 | states = []
197 | }
198 | let oldLights = lights
199 | let newLights = oldLights.map { (light) -> Light in
200 | if let index = states.index(where: { $0.selector == light.toSelector() }) {
201 | let state = states[index]
202 | let brightness = state.brightness ?? light.brightness
203 | let color = state.color ?? light.color
204 | let power = state.power ?? light.power
205 | return light.lightWithProperties(power, brightness: brightness, color: color)
206 | } else {
207 | return light
208 | }
209 | }
210 |
211 | client.updateLights(newLights)
212 | client.session.setScenesActivate(selector.toQueryStringValue(), duration: duration) { [weak self] (request, response, results, error) in
213 | if let strongSelf = self {
214 | var newLights = strongSelf.lightsByDeterminingConnectivityWithResults(strongSelf.lights, results: results)
215 | if error != nil {
216 | newLights = newLights.map { (newLight) -> Light in
217 | if let index = oldLights.index(where: { $0.id == newLight.id }) {
218 | let oldLight = oldLights[index]
219 | return oldLight.lightWithProperties(connected: newLight.connected)
220 | } else {
221 | return newLight
222 | }
223 | }
224 | }
225 | strongSelf.client.updateLights(newLights)
226 | }
227 | completionHandler?(results, error)
228 | }
229 | }
230 |
231 | // MARK: Helpers
232 |
233 | private func updateLights(_ lights: [Light]) {
234 | self.lights = lights.filter(filter)
235 | dirtyCheck()
236 | }
237 |
238 | private func lightsByDeterminingConnectivityWithResults(_ lights: [Light], results: [Result]) -> [Light] {
239 | return lights.map { (light) in
240 | for result in results {
241 | if result.id == light.id {
242 | switch result.status {
243 | case .OK:
244 | return light.lightWithProperties(connected: true)
245 | case .TimedOut, .Offline:
246 | return light.lightWithProperties(connected: false)
247 | }
248 | }
249 | }
250 | return light
251 | }
252 | }
253 |
254 | // MARK: Dirty Checking
255 |
256 | private func dirtyCheck() {
257 | var dirty = false
258 |
259 | let newPower = derivePower()
260 | if power != newPower {
261 | power = newPower
262 | dirty = true
263 | }
264 |
265 | let newBrightness = deriveBrightness()
266 | if brightness != newBrightness {
267 | brightness = newBrightness
268 | dirty = true
269 | }
270 |
271 | let newColor = deriveColor()
272 | if color != newColor {
273 | color = newColor
274 | dirty = true
275 | }
276 |
277 | let newLabel = deriveLabel()
278 | if label != newLabel {
279 | label = newLabel
280 | dirty = true
281 | }
282 |
283 | let newConnected = deriveConnected()
284 | if connected != newConnected {
285 | connected = newConnected
286 | dirty = true
287 | }
288 |
289 | let newCount = deriveCount()
290 | if count != newCount {
291 | count = newCount
292 | dirty = true
293 | }
294 |
295 | let newTouchedAt = deriveTouchedAt()
296 | if touchedAt != newTouchedAt {
297 | touchedAt = newTouchedAt
298 | dirty = true
299 | }
300 |
301 | if dirty {
302 | notifyObservers()
303 | }
304 | }
305 |
306 | private func derivePower() -> Bool {
307 | for light in lights.filter({ $0.connected }) {
308 | if light.power {
309 | return true
310 | }
311 | }
312 | return false
313 | }
314 |
315 | private func deriveTouchedAt() -> Date {
316 | var derivedTouchedAt = self.touchedAt
317 | for light in lights {
318 | if let lightTouchedAt = light.touchedAt {
319 | if lightTouchedAt.timeIntervalSince(Date()) < 0 {
320 | derivedTouchedAt = lightTouchedAt as Date
321 | }
322 | }
323 | }
324 |
325 | return derivedTouchedAt
326 | }
327 |
328 | private func deriveBrightness() -> Double {
329 | let count = lights.count
330 | if count > 0 {
331 | return lights.filter({ $0.connected }).reduce(0.0, { $1.brightness + $0 }) / Double(count)
332 | } else {
333 | return 0.0
334 | }
335 | }
336 |
337 | private func deriveColor() -> Color {
338 | let count = lights.count
339 | if count > 1 {
340 | var hueXTotal: Double = 0.0
341 | var hueYTotal: Double = 0.0
342 | var saturationTotal: Double = 0.0
343 | var kelvinTotal: Int = 0
344 | for light in lights {
345 | let color = light.color
346 | hueXTotal += sin(color.hue * 2.0 * .pi / Color.maxHue)
347 | hueYTotal += cos(color.hue * 2.0 * .pi / Color.maxHue)
348 | saturationTotal += color.saturation
349 | kelvinTotal += color.kelvin
350 | }
351 | var hue: Double = atan2(hueXTotal, hueYTotal) / (2.0 * .pi);
352 | if hue < 0.0 {
353 | hue += 1.0
354 | }
355 | hue *= Color.maxHue
356 | let saturation = saturationTotal / Double(count)
357 | let kelvin = kelvinTotal / count
358 | return Color(hue: hue, saturation: saturation, kelvin: kelvin)
359 | } else if let light = lights.first, count == 1 {
360 | return light.color
361 | } else {
362 | return Color(hue: 0, saturation: 0, kelvin: Color.defaultKelvin)
363 | }
364 | }
365 |
366 | private func deriveLabel() -> String {
367 | switch selector.type {
368 | case .All:
369 | return "All"
370 | case .ID, .Label:
371 | return lights.first?.label ?? ""
372 | case .GroupID:
373 | if let group = lights.filter({ $0.group != nil }).first?.group {
374 | return group.name
375 | } else {
376 | return ""
377 | }
378 | case .LocationID:
379 | if let location = lights.filter({ $0.location != nil }).first?.location {
380 | return location.name
381 | } else {
382 | return ""
383 | }
384 | case .SceneID:
385 | if let index = client.scenes.index(where: { $0.toSelector() == selector }) {
386 | return client.scenes[index].name
387 | } else {
388 | return ""
389 | }
390 | }
391 | }
392 |
393 | private func deriveConnected() -> Bool {
394 | for light in lights {
395 | if light.connected {
396 | return true
397 | }
398 | }
399 | return false
400 | }
401 |
402 | private func deriveCount() -> Int {
403 | return lights.count
404 | }
405 |
406 | public var supportsColor: Bool {
407 | return lights.map { $0.hasColor }.contains(true)
408 | }
409 |
410 | public var supportsIR: Bool {
411 | return lights.map { $0.hasIR }.contains(true)
412 | }
413 |
414 | public var supportsMultiZone: Bool {
415 | return lights.map { $0.hasMultiZone }.contains(true)
416 | }
417 | }
418 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | # LIFXHTTPKit [](https://github.com/tatey/LIFXHTTPKit/releases/latest) [](https://raw.githubusercontent.com/tatey/LIFXHTTPKit/master/LICENSE.txt) [](https://github.com/Carthage/Carthage)
2 |
3 | A nice iOS/watchOS/macOS framework for interacting with the [LIFX HTTP API](http://api.developer.lifx.com/docs)
4 | that has no external dependencies. Suitable for use inside extensions.
5 |
6 | Used by the [official LIFX iOS](https://itunes.apple.com/us/app/lifx/id657758311?mt=8) app and an open source Mac app, [Lighting](https://github.com/tatey/Lighting).
7 |
8 | *NOTE: This is not an official LIFX project.*
9 |
10 | ## Build Dependencies
11 |
12 | * Swift 3.0 (Xcode 8.3+)
13 | * iOS 8.2+
14 | * macOS 10.10+
15 | * watchOS 2+
16 |
17 | Looking for an earlier version of Swift?
18 |
19 | * Use [2.0.0](https://github.com/tatey/LIFXHTTPKit/releases/tag/2.0.0) for Swift 2.3
20 | * Use [1.0.0](https://github.com/tatey/LIFXHTTPKit/releases/tag/1.0.0) for Swift 2.2
21 | * Use [0.0.2](https://github.com/tatey/LIFXHTTPKit/releases/tag/0.0.2) for Swift 1.3
22 |
23 | ## Installation
24 |
25 | ### [Carthage](https://github.com/Carthage/Carthage)
26 |
27 | Add the following to your Cartfile:
28 |
29 | ```
30 | github "tatey/LIFXHTTPKit"
31 | ```
32 |
33 | Then run `$ carthage update`.
34 |
35 | Follow the current instructions in [Carthage's README](https://github.com/Carthage/Carthage)
36 | for up to date installation instructions.
37 |
38 | ## Quick Usage
39 |
40 | Power on all the lights.
41 |
42 | ``` swift
43 | let client = Client(accessToken: "c87c73a896b554367fac61f71dd3656af8d93a525a4e87df5952c6078a89d192")
44 | client.fetch()
45 | let all = client.allLightTarget()
46 | all.setPower(true)
47 | ```
48 |
49 | Toggle power on one light.
50 |
51 | ``` swift
52 | if let lightTarget = all.toLightTargets().first() {
53 | lightTarget.setPower(!lightTarget.power)
54 | }
55 | ```
56 |
57 | Restore a scene.
58 |
59 | ``` swift
60 | if let scene = client.scenes.first {
61 | let lightTarget = client.lightTargetWithSelector(scene.toSelector())
62 | lightTarget.restoreState()
63 | }
64 | ```
65 |
66 | Use a closure to find out when the request completes.
67 |
68 | ``` swift
69 | lightTarget.setPower(true) { (results, error) in
70 | if error != nil {
71 | println(error)
72 | } else {
73 | println(results)
74 | }
75 | }
76 | ```
77 |
78 | Use a closure to observer changes to lights.
79 |
80 | ``` swift
81 | let observer = all.addObserver {
82 | if all.power {
83 | button.titleLabel?.text = "Turn Off"
84 | } else {
85 | button.titleLabel?.text = "Turn On"
86 | }
87 | }
88 | ```
89 |
90 | ...and remove the observer when you're done.
91 |
92 | ``` swift
93 | all.removeObserver(observer)
94 | ```
95 |
96 | Get the light's state.
97 |
98 | ``` swift
99 | lightTarget.selector // =>
100 | lightTarget.power // => true
101 | lightTarget.brightness // => 0.5
102 | lightTarget.color // =>
103 | lightTarget.label // => "Lamp 1"
104 | ```
105 |
106 | ## Concepts
107 |
108 | LIFXHTTPKit has been built with macOS and iOS apps in mind. These APIs
109 | make it easy to consume the LIFX HTTP API without worrying about the specifics
110 | of HTTP or maintaining state.
111 |
112 | Keep these concepts in the back of your mind when using LIFXHTTPKit:
113 |
114 | 1. Everything is a collection. If you're dealing with one light, it's just a
115 | collection with one element. If you're dealing with many lights, it's a
116 | collection with many elements. Collections can be sliced into smaller collections.
117 | Each collection is a new instance and they're known as a `LightTarget`.
118 | 2. Everything is asynchronous and optimistic. If you tell a light target to power on,
119 | then the cached property is immediately updated and observers are notified.
120 | If there is a failure the property reverts back to is original value. Operations
121 | are handled serially, in-order, and in a background queue.
122 | 3. Observers are closure based and notify listeners when state changes. Binding views
123 | to state using observers means you can consolidate your view logic into discrete
124 | methods that respond to network and local changes.
125 | 4. Light state is maintained by an instance of `Client` and shared between all
126 | instances of `LightTarget`. If you power on one light target then all light
127 | targets which share the same underlying light are notified of the change.
128 |
129 | ## Detailed Usage
130 |
131 | `Client` and `LightTarget` are the core classes of LIFXHTTPKit. Clients are
132 | configured with an access token and light targets represent one or many addressable
133 | lights.
134 |
135 | ``` swift
136 | let client = Client(accessToken: "c87c73a896b554367fac61f71dd3656af8d93a525a4e87df5952c6078a89d192")
137 | client.fetch(completionHandler: { (error: NSError?) in -> Void
138 | // Error is nil if everything is A-OK.
139 | })
140 | ```
141 |
142 | Light targets are instantiated using selectors which are identifiers for
143 | addressing lights. They are a first class concept in LIFXHTTPKit and several
144 | convenience methods offer quick access.
145 |
146 | The default light target is known as "all" and it addresses all of the
147 | lights associated with the client.
148 |
149 | ``` swift
150 | let all = client.allLightTarget()
151 | all.setPower(true)
152 | ```
153 |
154 | Light targets can be sliced into smaller light targets. The all light target
155 | can be turned into many individual light targets for fine-grained control.
156 |
157 | ``` swift
158 | let lightTargets = all.toLightTargets()
159 | for lightTarget in lightTarget {
160 | lightTarget.setPower(true)
161 | }
162 | ```
163 |
164 | Light targets can be inspected at any time based on in-memory cache.
165 |
166 | ``` swift
167 | lightTarget.selector // =>
168 | lightTarget.power // => true
169 | lightTarget.brightness // => 0.5
170 | lightTarget.color // =>
171 | lightTarget.label // => "Lamp 1"
172 | lightTarget.connected // => true
173 | lightTarget.count // => 5
174 | lightTarget.lights.first?.group // =>
175 | lightTarget.lights.first?.location // =>
176 | lightTarget.touchedAt // => 2015-12-09 04:02:41 +0000
177 | ```
178 |
179 | The in-memory cache is updated when the client fetches, or an operation is
180 | performed. The results of the operation are inspected and lights which
181 | have become disconnected are marked appropriately.
182 |
183 | ### Selectors
184 |
185 | Any light target can be sliced into a light, group or location light target
186 | using these convenience methods.
187 |
188 | ``` swift
189 | // Get all lights associated with the account
190 | let all = client.allLightTarget()
191 |
192 | // Lights
193 | let lights = all.toLightTargets()
194 | for light in lights {
195 | light.setBrightness(0.5)
196 | }
197 |
198 | // Groups
199 | let groups = all.toGroupTargets()
200 | for group in groups {
201 | group.setBrightness(0.5)
202 | }
203 |
204 | // Locations
205 | let locations = all.toLocationTargets()
206 | for location in locations {
207 | location.setBrightness(0.5)
208 | }
209 | ```
210 |
211 | Alternatively instantiate a light target using a custom selector.
212 |
213 | ``` swift
214 | let selector = LightTargetSelector(type: .GroupID, value: "1c8de82b81f445e7cfaafae49b259c71")
215 | let lights = client.lightTargetWithSelector(selector)
216 | lights.setBrightness(0.5)
217 | ```
218 |
219 | Supported types are `.All`, `.ID`, `.GroupID`, `.LocationID`, and `.SceneID`.
220 |
221 | ### Scenes
222 |
223 | Scenes are like a virtual group with a preset. Users create scenes with the official
224 | suite of LIFX apps.
225 |
226 | When used as a virtual group scenes let you address an arbitrary collection of lights.
227 | This works great for creating nested groups or combining groups beyond their physical
228 | location. You use them like normal light targets.
229 |
230 | ``` swift
231 | if let scene = client.scenes.first {
232 | let lightTarget = client.lightTargetWithSelector(scene.toSelector())
233 | lightTarget.setPower(true)
234 | }
235 | ```
236 |
237 | When used as a preset you can restore the state of the virtual group as intended by
238 | the creator of the scene. There is a special method called `restoreState` which
239 | optimistically updates the local in-memory cache as well as making the appropriate
240 | request to the LIFX HTTP API. All of this happens in a single operation.
241 |
242 | ``` swift
243 | if let scene = client.scenes.first {
244 | let lightTarget = client.lightTargetWithSelector(scene.toSelector())
245 | lightTarget.restoreState()
246 | }
247 | ```
248 |
249 | ### Observers
250 |
251 | Use observers to opt-in to light target state changes. State may change
252 | as the result of a network response, or a locally initiated operation.
253 | Either way, you're less likely to have bugs if you place your logic
254 | for updating views here.
255 |
256 | ``` swift
257 | class LightView: NSView {
258 | // ...
259 |
260 | var observer: LightTargetObserver?
261 | var lightTarget: LightTarget
262 |
263 | func setupObserver()
264 | observer = lightTarget.addObserver({ () -> Void
265 | DispatchQueue.main.async {
266 | self.layer?.backgroundColor = self.lightTarget.color
267 | }
268 | })
269 | }
270 |
271 | deinit {
272 | if let observer = self.observer {
273 | lightTarget.removeObserver(observer)
274 | }
275 | }
276 |
277 | // ...
278 | }
279 | ```
280 |
281 | Keep these things in the back of your mind when using observers:
282 |
283 | * Observers may be notified in the background queue of the client. You can use
284 | `DispatchQueue` to jump to a different queue.
285 | * Observers must be explicitly removed to prevent memory leaks. The destructor
286 | is a good place to remove an observer in the object lifecycle. You can
287 | add as many observers as you want as long as you remove them when you're done.
288 |
289 | ### Get Power
290 |
291 | Determine if the light target is powered on. `true` if any of the `connected`
292 | lights in the light target are powered on.
293 |
294 | ``` swift
295 | lightTarget.power // => true
296 | ```
297 |
298 | ### Set Power
299 |
300 | Turn lights on or off. `true` to turn on, `false` to turn off. The `duration`
301 | is optional and defaults to `0.5`. The duration controls the length of time
302 | it takes for the light to change from on to off, or vise versa.
303 |
304 | ``` swift
305 | lightTarget.setPower(true, duration: 0.5, completionHandler: { (results: [Result], error: NSError?) -> Void
306 | // println(results)
307 | })
308 | ```
309 |
310 | ### Get Brightness
311 |
312 | Returns the average of the brightness for connected lights if the light target
313 | contains mixed brightnesses. A brightness of 0% is `0.0` and a brightness of
314 | 100% is `1.0`.
315 |
316 | ``` swift
317 | lightTarget.brightness // => 0.5
318 | ```
319 |
320 | You can inspect an individual light's brightness instead of using the average in
321 | mixed groups. See [Get Lights](#get-lights).
322 |
323 | ### Set Brightness
324 |
325 | Set the brightness of the lights. A brightness of 75% is `0.75`. The `duration`
326 | is optional and defaults to `0.5`.
327 |
328 | ``` swift
329 | lightTarget.setBrightness(1.0, duration: 0.5, completionHandler: { (results: [Result], error: NSError?) -> Void
330 | // println(results)
331 | })
332 | ```
333 |
334 | ### Get Color
335 |
336 | Returns the average of the colors for connected lights if the light target
337 | contains mixed colors.
338 |
339 | ``` swift
340 | lightTarget.color // =>
341 | ```
342 |
343 | LIFX lights represent color using hue/saturation and whites using kelvin.
344 | Determine if a light is white or color using these predicates.
345 |
346 | ``` swift
347 | let color = lightTarget.color
348 | color.isWhite // => false
349 | color.isColor // => true
350 | ```
351 |
352 | You can inspect an individual light's color instead of using the average in mixed
353 | groups. See [Get Lights](#get-lights).
354 |
355 | ### Set Color
356 |
357 | Sets the color of the lights. The `duration` is optional and defaults to `0.5`.
358 |
359 | ``` swift
360 | let color = Color.color(hue: 180.0, saturation: 1.0)
361 | lightTarget.setColor(color, duration: 0.5, completionHandler: { (results: [Result], error: NSError?) -> Void
362 | // println(results)
363 | })
364 |
365 | let white = Color.white(kelvin: 3500)
366 | lightTarget.setColor(color, duration: 0.5, completionHandler: { (results: [Result], error: NSError?) -> Void
367 | // println(results)
368 | })
369 | ```
370 |
371 | ### Set State (Color, Brightness, and Power Simultaneously)
372 |
373 | Sets the color, brightness, and power of the lights in one operation. The `duration` is optional and defaults to `0.5`.
374 | All other arguments except are optional and default to `nil`. A `nil` value is the equivalent to leaving the
375 | value unchanged.
376 |
377 | ``` swift
378 | let color = Color.color(hue: 180.0, saturation: 1.0)
379 | lightTarget.setState(color, brightness: 0.75, duration: 0.5, power: true, completionHandler: { (results: [Result], error: NSError?) -> Void
380 | // println(results)
381 | })
382 | ```
383 |
384 | ### Restore State
385 |
386 | **Scenes Only**: Sets the state of the lights as defined by the backing scene. The `duration` is optional and
387 | defaults to `0.5`.
388 |
389 | Calling this method on a non-scene based light target will set the error argument in the `completionHandler`
390 | callback.
391 |
392 | ``` swift
393 | lightTarget.restoreState(0.5, completionHandler: { (results: [Result], error: NSError?) -> Void
394 | // println(results)
395 | })
396 | ```
397 |
398 | ### Get Label
399 |
400 | Returns the label for the light target. If the light target is a group or
401 | location then the label will be derived from the group or location.
402 |
403 | ``` swift
404 | lightTarget.label // => "Lamp 1"
405 | ```
406 |
407 | The "all" light target will always return `"All"` as the label.
408 |
409 | ### Get Connected
410 |
411 | Determines if the lights are connected and reachable over the internet.
412 | `true` if at least one light addressed by the light target is reachable.
413 | `false` if all of the lights are unreachable.
414 |
415 | ``` swift
416 | lightTarget.connected // => true
417 | ```
418 |
419 | The connected property is updated each time an operation is performed
420 | using the results returned in the response.
421 |
422 | ### Get Count
423 |
424 | Returns the number of known lights addressable by the light target.
425 |
426 | ``` swift
427 | lightTarget.count // => 5
428 | ```
429 |
430 | ### Get Lights
431 |
432 | Inspect the lights addressable by the light target. If you're dealing
433 | with a mixed group you can inspect each light individually.
434 |
435 | ``` swift
436 | for light in lightTarget.lights {
437 | println(light.id)
438 | }
439 | ```
440 |
441 | A light has the following properties:
442 |
443 | ``` swift
444 | light.id // => "d3b2f2d97452"
445 | light.power // => true
446 | light.brightness // => 0.5
447 | light.color // =>
448 | light.label // => "Lamp 1"
449 | light.connected // => true
450 | light.group // =>
451 | light.location // =>
452 | light.productInfo // => >
453 | ```
454 |
455 | The `group` and `location` properties are optional as they are not required by
456 | the LIFX protocol. In practice these properties are always set.
457 |
458 | ## Testing
459 |
460 | First, copy the example configuration file.
461 |
462 | $ cp Tests/Secrets.example.plist Tests/Secrets.plist
463 |
464 | Then, paste a personal access token into the copied configuration file. The
465 | access token must belong to an account that has at least one connected light.
466 | You can generate a personal access tokens at https://cloud.lifx.com/settings.
467 |
468 | Finally, run tests by selecting "Product > Tests" from the menu bar, or use the
469 | "⌘ + U" shortcut.
470 |
471 | ## Copyright
472 |
473 | Copyright (c) 2015-2016 Tate Johnson. All rights reserved. Licensed under the MIT license.
474 |
--------------------------------------------------------------------------------
/LIFXHTTPKit.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 5D05E61E1BB5296E007B917D /* ClientHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA745131B52492E00BBC2D1 /* ClientHelper.swift */; };
11 | 5D05E61F1BB5296E007B917D /* ClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5898B41B1887530024D47B /* ClientTests.swift */; };
12 | 5D05E6201BB5296E007B917D /* LightTargetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF649D81B2D436A0006EE03 /* LightTargetTests.swift */; };
13 | 5D05E6211BB5296E007B917D /* SecretsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA7AB321B369608008A8130 /* SecretsHelper.swift */; };
14 | 5D05E6231BB5296E007B917D /* LIFXHTTPKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D5898A21B1887530024D47B /* LIFXHTTPKit.framework */; };
15 | 5D05E6251BB5296E007B917D /* Secrets.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5DA7AB2F1B3695BF008A8130 /* Secrets.plist */; };
16 | 5D50E0591BC3A21800AED146 /* Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D50E0581BC3A21800AED146 /* Scene.swift */; };
17 | 5D50E05B1BC3A26A00AED146 /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D50E05A1BC3A26A00AED146 /* State.swift */; };
18 | 5D50E05C1BC3A39200AED146 /* Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D50E0581BC3A21800AED146 /* Scene.swift */; };
19 | 5D50E05D1BC3A39500AED146 /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D50E05A1BC3A26A00AED146 /* State.swift */; };
20 | 5D50E0601BC3AD7A00AED146 /* LightTargetSelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D50E05E1BC3AD7500AED146 /* LightTargetSelectorTests.swift */; };
21 | 5D50E0611BC3AD7B00AED146 /* LightTargetSelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D50E05E1BC3AD7500AED146 /* LightTargetSelectorTests.swift */; };
22 | 5D5398121BC383BE0060AAED /* LightTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5398041BC37FFA0060AAED /* LightTests.swift */; };
23 | 5D5398131BC383BF0060AAED /* LightTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5398041BC37FFA0060AAED /* LightTests.swift */; };
24 | 5D5898AE1B1887530024D47B /* LIFXHTTPKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D5898A21B1887530024D47B /* LIFXHTTPKit.framework */; };
25 | 5D5898B51B1887530024D47B /* ClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5898B41B1887530024D47B /* ClientTests.swift */; };
26 | 5D5898BF1B1887650024D47B /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5898BE1B1887650024D47B /* Client.swift */; };
27 | 5D8BF5371BB5256900A5575C /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F3F1B2BD4CA0006F2A5 /* Errors.swift */; };
28 | 5D8BF5381BB5256B00A5575C /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5898BE1B1887650024D47B /* Client.swift */; };
29 | 5D8BF5391BB5256D00A5575C /* ClientObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F431B2BD5370006F2A5 /* ClientObserver.swift */; };
30 | 5D8BF53B1BB5257200A5575C /* LightTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F471B2BD57C0006F2A5 /* LightTarget.swift */; };
31 | 5D8BF53C1BB5257400A5575C /* LightTargetObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F451B2BD55C0006F2A5 /* LightTargetObserver.swift */; };
32 | 5D8BF53D1BB5257600A5575C /* HTTPSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F491B2BD5B90006F2A5 /* HTTPSession.swift */; };
33 | 5D8BF53E1BB5257900A5575C /* Light.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F4B1B2BD5DD0006F2A5 /* Light.swift */; };
34 | 5D8BF53F1BB5257B00A5575C /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA7AB431B377ACA008A8130 /* Color.swift */; };
35 | 5D8BF5401BB5257D00A5575C /* Group.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D94C6491B5671C300C0BCD2 /* Group.swift */; };
36 | 5D8BF5411BB5258000A5575C /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D94C64B1B56726200C0BCD2 /* Location.swift */; };
37 | 5D8BF5421BB5258200A5575C /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F4D1B2BD5E50006F2A5 /* Result.swift */; };
38 | 5D8C3D9E1BC5FC990059E05D /* LightTargetSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8C3D9D1BC5FC990059E05D /* LightTargetSelector.swift */; };
39 | 5D8C3D9F1BC5FC990059E05D /* LightTargetSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8C3D9D1BC5FC990059E05D /* LightTargetSelector.swift */; };
40 | 5D94C64A1B5671C300C0BCD2 /* Group.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D94C6491B5671C300C0BCD2 /* Group.swift */; };
41 | 5D94C64C1B56726200C0BCD2 /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D94C64B1B56726200C0BCD2 /* Location.swift */; };
42 | 5DA745151B52493500BBC2D1 /* ClientHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA745131B52492E00BBC2D1 /* ClientHelper.swift */; };
43 | 5DA7AB311B3695F3008A8130 /* Secrets.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5DA7AB2F1B3695BF008A8130 /* Secrets.plist */; };
44 | 5DA7AB341B369640008A8130 /* SecretsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA7AB321B369608008A8130 /* SecretsHelper.swift */; };
45 | 5DA7AB441B377ACA008A8130 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA7AB431B377ACA008A8130 /* Color.swift */; };
46 | 5DB41F401B2BD4CA0006F2A5 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F3F1B2BD4CA0006F2A5 /* Errors.swift */; };
47 | 5DB41F441B2BD5370006F2A5 /* ClientObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F431B2BD5370006F2A5 /* ClientObserver.swift */; };
48 | 5DB41F461B2BD55C0006F2A5 /* LightTargetObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F451B2BD55C0006F2A5 /* LightTargetObserver.swift */; };
49 | 5DB41F481B2BD57C0006F2A5 /* LightTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F471B2BD57C0006F2A5 /* LightTarget.swift */; };
50 | 5DB41F4A1B2BD5B90006F2A5 /* HTTPSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F491B2BD5B90006F2A5 /* HTTPSession.swift */; };
51 | 5DB41F4C1B2BD5DD0006F2A5 /* Light.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F4B1B2BD5DD0006F2A5 /* Light.swift */; };
52 | 5DB41F4E1B2BD5E50006F2A5 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F4D1B2BD5E50006F2A5 /* Result.swift */; };
53 | 5DF3F61A1BD45C0F0002E52D /* HTTPOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF3F6191BD45C0F0002E52D /* HTTPOperation.swift */; };
54 | 5DF3F61B1BD45C0F0002E52D /* HTTPOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF3F6191BD45C0F0002E52D /* HTTPOperation.swift */; };
55 | 5DF649D91B2D436A0006EE03 /* LightTargetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF649D81B2D436A0006EE03 /* LightTargetTests.swift */; };
56 | 903A12B81CE050C70071D8F0 /* LIFXHTTPKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 903A12B71CE050C70071D8F0 /* LIFXHTTPKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
57 | 903A12B91CE050C70071D8F0 /* LIFXHTTPKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 903A12B71CE050C70071D8F0 /* LIFXHTTPKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
58 | 903A12BA1CE050C70071D8F0 /* LIFXHTTPKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 903A12B71CE050C70071D8F0 /* LIFXHTTPKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
59 | 903A12C01CE063DD0071D8F0 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F3F1B2BD4CA0006F2A5 /* Errors.swift */; };
60 | 903A12C11CE063EA0071D8F0 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D5898BE1B1887650024D47B /* Client.swift */; };
61 | 903A12C21CE063ED0071D8F0 /* ClientObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F431B2BD5370006F2A5 /* ClientObserver.swift */; };
62 | 903A12C31CE063F00071D8F0 /* LightTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F471B2BD57C0006F2A5 /* LightTarget.swift */; };
63 | 903A12C41CE063F30071D8F0 /* LightTargetSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D8C3D9D1BC5FC990059E05D /* LightTargetSelector.swift */; };
64 | 903A12C51CE063F70071D8F0 /* LightTargetObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F451B2BD55C0006F2A5 /* LightTargetObserver.swift */; };
65 | 903A12C61CE063FA0071D8F0 /* HTTPSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F491B2BD5B90006F2A5 /* HTTPSession.swift */; };
66 | 903A12C71CE063FE0071D8F0 /* HTTPOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF3F6191BD45C0F0002E52D /* HTTPOperation.swift */; };
67 | 903A12C81CE064010071D8F0 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DA7AB431B377ACA008A8130 /* Color.swift */; };
68 | 903A12C91CE064040071D8F0 /* Light.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F4B1B2BD5DD0006F2A5 /* Light.swift */; };
69 | 903A12CA1CE064070071D8F0 /* Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D50E0581BC3A21800AED146 /* Scene.swift */; };
70 | 903A12CB1CE064090071D8F0 /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D50E05A1BC3A26A00AED146 /* State.swift */; };
71 | 903A12CC1CE0640C0071D8F0 /* Group.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D94C6491B5671C300C0BCD2 /* Group.swift */; };
72 | 903A12CD1CE064100071D8F0 /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D94C64B1B56726200C0BCD2 /* Location.swift */; };
73 | 903A12CE1CE064130071D8F0 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB41F4D1B2BD5E50006F2A5 /* Result.swift */; };
74 | DD1E132F81EB962FED0C262F /* ProductInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F4B5781E950F4800D0ED01 /* ProductInformation.swift */; };
75 | DD1E13F233373003945C348D /* ProductInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F4B5781E950F4800D0ED01 /* ProductInformation.swift */; };
76 | DD1E1B4B1DF488D68F6F302D /* ProductInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F4B5781E950F4800D0ED01 /* ProductInformation.swift */; };
77 | DD1E1ED9D4663020C0323F96 /* ProductInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F4B5781E950F4800D0ED01 /* ProductInformation.swift */; };
78 | E9F4B5791E950F4800D0ED01 /* ProductInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F4B5781E950F4800D0ED01 /* ProductInformation.swift */; };
79 | /* End PBXBuildFile section */
80 |
81 | /* Begin PBXContainerItemProxy section */
82 | 5D5898AF1B1887530024D47B /* PBXContainerItemProxy */ = {
83 | isa = PBXContainerItemProxy;
84 | containerPortal = 5D5898991B1887530024D47B /* Project object */;
85 | proxyType = 1;
86 | remoteGlobalIDString = 5D5898A11B1887530024D47B;
87 | remoteInfo = LIFXHTTPKit;
88 | };
89 | 5DE1635C1BB8AF3B0057436C /* PBXContainerItemProxy */ = {
90 | isa = PBXContainerItemProxy;
91 | containerPortal = 5D5898991B1887530024D47B /* Project object */;
92 | proxyType = 1;
93 | remoteGlobalIDString = 5D8BF52E1BB524F400A5575C;
94 | remoteInfo = "LIFXHTTPKit-iOS";
95 | };
96 | /* End PBXContainerItemProxy section */
97 |
98 | /* Begin PBXFileReference section */
99 | 5D05E6291BB5296E007B917D /* LIFXHTTPKit-iOS-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "LIFXHTTPKit-iOS-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
100 | 5D50E0581BC3A21800AED146 /* Scene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scene.swift; sourceTree = ""; };
101 | 5D50E05A1BC3A26A00AED146 /* State.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = State.swift; sourceTree = ""; };
102 | 5D50E05E1BC3AD7500AED146 /* LightTargetSelectorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LightTargetSelectorTests.swift; sourceTree = ""; };
103 | 5D5398041BC37FFA0060AAED /* LightTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LightTests.swift; sourceTree = ""; };
104 | 5D5898A21B1887530024D47B /* LIFXHTTPKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LIFXHTTPKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
105 | 5D5898A61B1887530024D47B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
106 | 5D5898AD1B1887530024D47B /* LIFXHTTPKit-Mac-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "LIFXHTTPKit-Mac-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
107 | 5D5898B31B1887530024D47B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
108 | 5D5898B41B1887530024D47B /* ClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientTests.swift; sourceTree = ""; };
109 | 5D5898BE1B1887650024D47B /* Client.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = ""; };
110 | 5D8BF52F1BB524F400A5575C /* LIFXHTTPKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LIFXHTTPKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
111 | 5D8C3D9D1BC5FC990059E05D /* LightTargetSelector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LightTargetSelector.swift; sourceTree = ""; };
112 | 5D94C6491B5671C300C0BCD2 /* Group.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Group.swift; sourceTree = ""; };
113 | 5D94C64B1B56726200C0BCD2 /* Location.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = ""; };
114 | 5DA745131B52492E00BBC2D1 /* ClientHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientHelper.swift; sourceTree = ""; };
115 | 5DA7AB2F1B3695BF008A8130 /* Secrets.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Secrets.plist; sourceTree = ""; };
116 | 5DA7AB321B369608008A8130 /* SecretsHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretsHelper.swift; sourceTree = ""; };
117 | 5DA7AB431B377ACA008A8130 /* Color.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; };
118 | 5DB41F3F1B2BD4CA0006F2A5 /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; };
119 | 5DB41F431B2BD5370006F2A5 /* ClientObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClientObserver.swift; sourceTree = ""; };
120 | 5DB41F451B2BD55C0006F2A5 /* LightTargetObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LightTargetObserver.swift; sourceTree = ""; };
121 | 5DB41F471B2BD57C0006F2A5 /* LightTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LightTarget.swift; sourceTree = ""; };
122 | 5DB41F491B2BD5B90006F2A5 /* HTTPSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPSession.swift; sourceTree = ""; };
123 | 5DB41F4B1B2BD5DD0006F2A5 /* Light.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Light.swift; sourceTree = ""; };
124 | 5DB41F4D1B2BD5E50006F2A5 /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; };
125 | 5DF3F6191BD45C0F0002E52D /* HTTPOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPOperation.swift; sourceTree = ""; };
126 | 5DF649D81B2D436A0006EE03 /* LightTargetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LightTargetTests.swift; sourceTree = ""; };
127 | 903A12B71CE050C70071D8F0 /* LIFXHTTPKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LIFXHTTPKit.h; sourceTree = ""; };
128 | 908631CD1CDC7629006D9E47 /* LIFXHTTPKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LIFXHTTPKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
129 | E9F4B5781E950F4800D0ED01 /* ProductInformation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductInformation.swift; sourceTree = ""; };
130 | /* End PBXFileReference section */
131 |
132 | /* Begin PBXFrameworksBuildPhase section */
133 | 5D05E6221BB5296E007B917D /* Frameworks */ = {
134 | isa = PBXFrameworksBuildPhase;
135 | buildActionMask = 2147483647;
136 | files = (
137 | 5D05E6231BB5296E007B917D /* LIFXHTTPKit.framework in Frameworks */,
138 | );
139 | runOnlyForDeploymentPostprocessing = 0;
140 | };
141 | 5D58989E1B1887530024D47B /* Frameworks */ = {
142 | isa = PBXFrameworksBuildPhase;
143 | buildActionMask = 2147483647;
144 | files = (
145 | );
146 | runOnlyForDeploymentPostprocessing = 0;
147 | };
148 | 5D5898AA1B1887530024D47B /* Frameworks */ = {
149 | isa = PBXFrameworksBuildPhase;
150 | buildActionMask = 2147483647;
151 | files = (
152 | 5D5898AE1B1887530024D47B /* LIFXHTTPKit.framework in Frameworks */,
153 | );
154 | runOnlyForDeploymentPostprocessing = 0;
155 | };
156 | 5D8BF52B1BB524F400A5575C /* Frameworks */ = {
157 | isa = PBXFrameworksBuildPhase;
158 | buildActionMask = 2147483647;
159 | files = (
160 | );
161 | runOnlyForDeploymentPostprocessing = 0;
162 | };
163 | 908631C91CDC7629006D9E47 /* Frameworks */ = {
164 | isa = PBXFrameworksBuildPhase;
165 | buildActionMask = 2147483647;
166 | files = (
167 | );
168 | runOnlyForDeploymentPostprocessing = 0;
169 | };
170 | /* End PBXFrameworksBuildPhase section */
171 |
172 | /* Begin PBXGroup section */
173 | 5D5898981B1887530024D47B = {
174 | isa = PBXGroup;
175 | children = (
176 | 5D5898A41B1887530024D47B /* Source */,
177 | 5D5898B11B1887530024D47B /* Tests */,
178 | 5D5898A31B1887530024D47B /* Products */,
179 | );
180 | sourceTree = "";
181 | };
182 | 5D5898A31B1887530024D47B /* Products */ = {
183 | isa = PBXGroup;
184 | children = (
185 | 5D5898A21B1887530024D47B /* LIFXHTTPKit.framework */,
186 | 5D5898AD1B1887530024D47B /* LIFXHTTPKit-Mac-Tests.xctest */,
187 | 5D8BF52F1BB524F400A5575C /* LIFXHTTPKit.framework */,
188 | 5D05E6291BB5296E007B917D /* LIFXHTTPKit-iOS-Tests.xctest */,
189 | 908631CD1CDC7629006D9E47 /* LIFXHTTPKit.framework */,
190 | );
191 | name = Products;
192 | sourceTree = "";
193 | };
194 | 5D5898A41B1887530024D47B /* Source */ = {
195 | isa = PBXGroup;
196 | children = (
197 | 5DB41F3F1B2BD4CA0006F2A5 /* Errors.swift */,
198 | 5D5898BE1B1887650024D47B /* Client.swift */,
199 | 5DB41F431B2BD5370006F2A5 /* ClientObserver.swift */,
200 | 5DB41F471B2BD57C0006F2A5 /* LightTarget.swift */,
201 | 5D8C3D9D1BC5FC990059E05D /* LightTargetSelector.swift */,
202 | 5DB41F451B2BD55C0006F2A5 /* LightTargetObserver.swift */,
203 | 5DB41F491B2BD5B90006F2A5 /* HTTPSession.swift */,
204 | 5DF3F6191BD45C0F0002E52D /* HTTPOperation.swift */,
205 | 5DA7AB431B377ACA008A8130 /* Color.swift */,
206 | 5DB41F4B1B2BD5DD0006F2A5 /* Light.swift */,
207 | 5D50E0581BC3A21800AED146 /* Scene.swift */,
208 | 5D50E05A1BC3A26A00AED146 /* State.swift */,
209 | E9F4B5781E950F4800D0ED01 /* ProductInformation.swift */,
210 | 5D94C6491B5671C300C0BCD2 /* Group.swift */,
211 | 5D94C64B1B56726200C0BCD2 /* Location.swift */,
212 | 5DB41F4D1B2BD5E50006F2A5 /* Result.swift */,
213 | 5D5898A51B1887530024D47B /* Supporting Files */,
214 | );
215 | path = Source;
216 | sourceTree = "";
217 | };
218 | 5D5898A51B1887530024D47B /* Supporting Files */ = {
219 | isa = PBXGroup;
220 | children = (
221 | 903A12B71CE050C70071D8F0 /* LIFXHTTPKit.h */,
222 | 5D5898A61B1887530024D47B /* Info.plist */,
223 | );
224 | name = "Supporting Files";
225 | sourceTree = "";
226 | };
227 | 5D5898B11B1887530024D47B /* Tests */ = {
228 | isa = PBXGroup;
229 | children = (
230 | 5D5898B41B1887530024D47B /* ClientTests.swift */,
231 | 5DF649D81B2D436A0006EE03 /* LightTargetTests.swift */,
232 | 5D50E05E1BC3AD7500AED146 /* LightTargetSelectorTests.swift */,
233 | 5D5398041BC37FFA0060AAED /* LightTests.swift */,
234 | 5D5898B21B1887530024D47B /* Supporting Files */,
235 | );
236 | path = Tests;
237 | sourceTree = "";
238 | };
239 | 5D5898B21B1887530024D47B /* Supporting Files */ = {
240 | isa = PBXGroup;
241 | children = (
242 | 5DA745131B52492E00BBC2D1 /* ClientHelper.swift */,
243 | 5DA7AB321B369608008A8130 /* SecretsHelper.swift */,
244 | 5D5898B31B1887530024D47B /* Info.plist */,
245 | 5DA7AB2F1B3695BF008A8130 /* Secrets.plist */,
246 | );
247 | name = "Supporting Files";
248 | sourceTree = "";
249 | };
250 | /* End PBXGroup section */
251 |
252 | /* Begin PBXHeadersBuildPhase section */
253 | 5D58989F1B1887530024D47B /* Headers */ = {
254 | isa = PBXHeadersBuildPhase;
255 | buildActionMask = 2147483647;
256 | files = (
257 | 903A12B91CE050C70071D8F0 /* LIFXHTTPKit.h in Headers */,
258 | );
259 | runOnlyForDeploymentPostprocessing = 0;
260 | };
261 | 5D8BF52C1BB524F400A5575C /* Headers */ = {
262 | isa = PBXHeadersBuildPhase;
263 | buildActionMask = 2147483647;
264 | files = (
265 | 903A12B81CE050C70071D8F0 /* LIFXHTTPKit.h in Headers */,
266 | );
267 | runOnlyForDeploymentPostprocessing = 0;
268 | };
269 | 908631CA1CDC7629006D9E47 /* Headers */ = {
270 | isa = PBXHeadersBuildPhase;
271 | buildActionMask = 2147483647;
272 | files = (
273 | 903A12BA1CE050C70071D8F0 /* LIFXHTTPKit.h in Headers */,
274 | );
275 | runOnlyForDeploymentPostprocessing = 0;
276 | };
277 | /* End PBXHeadersBuildPhase section */
278 |
279 | /* Begin PBXNativeTarget section */
280 | 5D05E61A1BB5296E007B917D /* LIFXHTTPKit-iOS-Tests */ = {
281 | isa = PBXNativeTarget;
282 | buildConfigurationList = 5D05E6261BB5296E007B917D /* Build configuration list for PBXNativeTarget "LIFXHTTPKit-iOS-Tests" */;
283 | buildPhases = (
284 | 5D05E61D1BB5296E007B917D /* Sources */,
285 | 5D05E6221BB5296E007B917D /* Frameworks */,
286 | 5D05E6241BB5296E007B917D /* Resources */,
287 | );
288 | buildRules = (
289 | );
290 | dependencies = (
291 | 5DE1635D1BB8AF3B0057436C /* PBXTargetDependency */,
292 | );
293 | name = "LIFXHTTPKit-iOS-Tests";
294 | productName = LIFXHTTPKitTests;
295 | productReference = 5D05E6291BB5296E007B917D /* LIFXHTTPKit-iOS-Tests.xctest */;
296 | productType = "com.apple.product-type.bundle.unit-test";
297 | };
298 | 5D5898A11B1887530024D47B /* LIFXHTTPKit-Mac */ = {
299 | isa = PBXNativeTarget;
300 | buildConfigurationList = 5D5898B81B1887530024D47B /* Build configuration list for PBXNativeTarget "LIFXHTTPKit-Mac" */;
301 | buildPhases = (
302 | 5D58989D1B1887530024D47B /* Sources */,
303 | 5D58989E1B1887530024D47B /* Frameworks */,
304 | 5D58989F1B1887530024D47B /* Headers */,
305 | 5D5898A01B1887530024D47B /* Resources */,
306 | );
307 | buildRules = (
308 | );
309 | dependencies = (
310 | );
311 | name = "LIFXHTTPKit-Mac";
312 | productName = LIFXHTTPKit;
313 | productReference = 5D5898A21B1887530024D47B /* LIFXHTTPKit.framework */;
314 | productType = "com.apple.product-type.framework";
315 | };
316 | 5D5898AC1B1887530024D47B /* LIFXHTTPKit-Mac-Tests */ = {
317 | isa = PBXNativeTarget;
318 | buildConfigurationList = 5D5898BB1B1887530024D47B /* Build configuration list for PBXNativeTarget "LIFXHTTPKit-Mac-Tests" */;
319 | buildPhases = (
320 | 5D5898A91B1887530024D47B /* Sources */,
321 | 5D5898AA1B1887530024D47B /* Frameworks */,
322 | 5D5898AB1B1887530024D47B /* Resources */,
323 | );
324 | buildRules = (
325 | );
326 | dependencies = (
327 | 5D5898B01B1887530024D47B /* PBXTargetDependency */,
328 | );
329 | name = "LIFXHTTPKit-Mac-Tests";
330 | productName = LIFXHTTPKitTests;
331 | productReference = 5D5898AD1B1887530024D47B /* LIFXHTTPKit-Mac-Tests.xctest */;
332 | productType = "com.apple.product-type.bundle.unit-test";
333 | };
334 | 5D8BF52E1BB524F400A5575C /* LIFXHTTPKit-iOS */ = {
335 | isa = PBXNativeTarget;
336 | buildConfigurationList = 5D8BF5361BB524F400A5575C /* Build configuration list for PBXNativeTarget "LIFXHTTPKit-iOS" */;
337 | buildPhases = (
338 | 5D8BF52A1BB524F400A5575C /* Sources */,
339 | 5D8BF52B1BB524F400A5575C /* Frameworks */,
340 | 5D8BF52C1BB524F400A5575C /* Headers */,
341 | 5D8BF52D1BB524F400A5575C /* Resources */,
342 | );
343 | buildRules = (
344 | );
345 | dependencies = (
346 | );
347 | name = "LIFXHTTPKit-iOS";
348 | productName = "LIFXHTTPKit-iOS";
349 | productReference = 5D8BF52F1BB524F400A5575C /* LIFXHTTPKit.framework */;
350 | productType = "com.apple.product-type.framework";
351 | };
352 | 908631CC1CDC7629006D9E47 /* LIFXHTTPKit-watchOS */ = {
353 | isa = PBXNativeTarget;
354 | buildConfigurationList = 908631D41CDC7629006D9E47 /* Build configuration list for PBXNativeTarget "LIFXHTTPKit-watchOS" */;
355 | buildPhases = (
356 | 908631C81CDC7629006D9E47 /* Sources */,
357 | 908631C91CDC7629006D9E47 /* Frameworks */,
358 | 908631CA1CDC7629006D9E47 /* Headers */,
359 | 908631CB1CDC7629006D9E47 /* Resources */,
360 | );
361 | buildRules = (
362 | );
363 | dependencies = (
364 | );
365 | name = "LIFXHTTPKit-watchOS";
366 | productName = "LIFXHTTPKit-watchOS";
367 | productReference = 908631CD1CDC7629006D9E47 /* LIFXHTTPKit.framework */;
368 | productType = "com.apple.product-type.framework";
369 | };
370 | /* End PBXNativeTarget section */
371 |
372 | /* Begin PBXProject section */
373 | 5D5898991B1887530024D47B /* Project object */ = {
374 | isa = PBXProject;
375 | attributes = {
376 | LastSwiftMigration = 0700;
377 | LastSwiftUpdateCheck = 0700;
378 | LastUpgradeCheck = 0800;
379 | ORGANIZATIONNAME = "Tate Johnson";
380 | TargetAttributes = {
381 | 5D05E61A1BB5296E007B917D = {
382 | LastSwiftMigration = 0820;
383 | };
384 | 5D5898A11B1887530024D47B = {
385 | CreatedOnToolsVersion = 6.3.2;
386 | LastSwiftMigration = 0800;
387 | };
388 | 5D5898AC1B1887530024D47B = {
389 | CreatedOnToolsVersion = 6.3.2;
390 | LastSwiftMigration = 0800;
391 | };
392 | 5D8BF52E1BB524F400A5575C = {
393 | CreatedOnToolsVersion = 7.0;
394 | LastSwiftMigration = 0820;
395 | };
396 | 908631CC1CDC7629006D9E47 = {
397 | CreatedOnToolsVersion = 7.3;
398 | };
399 | };
400 | };
401 | buildConfigurationList = 5D58989C1B1887530024D47B /* Build configuration list for PBXProject "LIFXHTTPKit" */;
402 | compatibilityVersion = "Xcode 3.2";
403 | developmentRegion = English;
404 | hasScannedForEncodings = 0;
405 | knownRegions = (
406 | en,
407 | );
408 | mainGroup = 5D5898981B1887530024D47B;
409 | productRefGroup = 5D5898A31B1887530024D47B /* Products */;
410 | projectDirPath = "";
411 | projectRoot = "";
412 | targets = (
413 | 5D8BF52E1BB524F400A5575C /* LIFXHTTPKit-iOS */,
414 | 5D05E61A1BB5296E007B917D /* LIFXHTTPKit-iOS-Tests */,
415 | 5D5898A11B1887530024D47B /* LIFXHTTPKit-Mac */,
416 | 5D5898AC1B1887530024D47B /* LIFXHTTPKit-Mac-Tests */,
417 | 908631CC1CDC7629006D9E47 /* LIFXHTTPKit-watchOS */,
418 | );
419 | };
420 | /* End PBXProject section */
421 |
422 | /* Begin PBXResourcesBuildPhase section */
423 | 5D05E6241BB5296E007B917D /* Resources */ = {
424 | isa = PBXResourcesBuildPhase;
425 | buildActionMask = 2147483647;
426 | files = (
427 | 5D05E6251BB5296E007B917D /* Secrets.plist in Resources */,
428 | );
429 | runOnlyForDeploymentPostprocessing = 0;
430 | };
431 | 5D5898A01B1887530024D47B /* Resources */ = {
432 | isa = PBXResourcesBuildPhase;
433 | buildActionMask = 2147483647;
434 | files = (
435 | );
436 | runOnlyForDeploymentPostprocessing = 0;
437 | };
438 | 5D5898AB1B1887530024D47B /* Resources */ = {
439 | isa = PBXResourcesBuildPhase;
440 | buildActionMask = 2147483647;
441 | files = (
442 | 5DA7AB311B3695F3008A8130 /* Secrets.plist in Resources */,
443 | );
444 | runOnlyForDeploymentPostprocessing = 0;
445 | };
446 | 5D8BF52D1BB524F400A5575C /* Resources */ = {
447 | isa = PBXResourcesBuildPhase;
448 | buildActionMask = 2147483647;
449 | files = (
450 | );
451 | runOnlyForDeploymentPostprocessing = 0;
452 | };
453 | 908631CB1CDC7629006D9E47 /* Resources */ = {
454 | isa = PBXResourcesBuildPhase;
455 | buildActionMask = 2147483647;
456 | files = (
457 | );
458 | runOnlyForDeploymentPostprocessing = 0;
459 | };
460 | /* End PBXResourcesBuildPhase section */
461 |
462 | /* Begin PBXSourcesBuildPhase section */
463 | 5D05E61D1BB5296E007B917D /* Sources */ = {
464 | isa = PBXSourcesBuildPhase;
465 | buildActionMask = 2147483647;
466 | files = (
467 | 5D50E0611BC3AD7B00AED146 /* LightTargetSelectorTests.swift in Sources */,
468 | 5D5398121BC383BE0060AAED /* LightTests.swift in Sources */,
469 | 5D05E61E1BB5296E007B917D /* ClientHelper.swift in Sources */,
470 | 5D05E61F1BB5296E007B917D /* ClientTests.swift in Sources */,
471 | 5D05E6201BB5296E007B917D /* LightTargetTests.swift in Sources */,
472 | 5D05E6211BB5296E007B917D /* SecretsHelper.swift in Sources */,
473 | DD1E13F233373003945C348D /* ProductInformation.swift in Sources */,
474 | );
475 | runOnlyForDeploymentPostprocessing = 0;
476 | };
477 | 5D58989D1B1887530024D47B /* Sources */ = {
478 | isa = PBXSourcesBuildPhase;
479 | buildActionMask = 2147483647;
480 | files = (
481 | 5D94C64A1B5671C300C0BCD2 /* Group.swift in Sources */,
482 | 5DB41F4A1B2BD5B90006F2A5 /* HTTPSession.swift in Sources */,
483 | 5DB41F401B2BD4CA0006F2A5 /* Errors.swift in Sources */,
484 | 5D8C3D9F1BC5FC990059E05D /* LightTargetSelector.swift in Sources */,
485 | 5D50E05C1BC3A39200AED146 /* Scene.swift in Sources */,
486 | 5D50E05D1BC3A39500AED146 /* State.swift in Sources */,
487 | 5DA7AB441B377ACA008A8130 /* Color.swift in Sources */,
488 | 5DB41F441B2BD5370006F2A5 /* ClientObserver.swift in Sources */,
489 | 5DF3F61B1BD45C0F0002E52D /* HTTPOperation.swift in Sources */,
490 | 5DB41F481B2BD57C0006F2A5 /* LightTarget.swift in Sources */,
491 | 5D5898BF1B1887650024D47B /* Client.swift in Sources */,
492 | 5DB41F4C1B2BD5DD0006F2A5 /* Light.swift in Sources */,
493 | 5DB41F4E1B2BD5E50006F2A5 /* Result.swift in Sources */,
494 | 5D94C64C1B56726200C0BCD2 /* Location.swift in Sources */,
495 | 5DB41F461B2BD55C0006F2A5 /* LightTargetObserver.swift in Sources */,
496 | DD1E1B4B1DF488D68F6F302D /* ProductInformation.swift in Sources */,
497 | );
498 | runOnlyForDeploymentPostprocessing = 0;
499 | };
500 | 5D5898A91B1887530024D47B /* Sources */ = {
501 | isa = PBXSourcesBuildPhase;
502 | buildActionMask = 2147483647;
503 | files = (
504 | 5D50E0601BC3AD7A00AED146 /* LightTargetSelectorTests.swift in Sources */,
505 | 5D5398131BC383BF0060AAED /* LightTests.swift in Sources */,
506 | 5DA745151B52493500BBC2D1 /* ClientHelper.swift in Sources */,
507 | 5D5898B51B1887530024D47B /* ClientTests.swift in Sources */,
508 | 5DF649D91B2D436A0006EE03 /* LightTargetTests.swift in Sources */,
509 | 5DA7AB341B369640008A8130 /* SecretsHelper.swift in Sources */,
510 | DD1E1ED9D4663020C0323F96 /* ProductInformation.swift in Sources */,
511 | );
512 | runOnlyForDeploymentPostprocessing = 0;
513 | };
514 | 5D8BF52A1BB524F400A5575C /* Sources */ = {
515 | isa = PBXSourcesBuildPhase;
516 | buildActionMask = 2147483647;
517 | files = (
518 | 5D8BF5381BB5256B00A5575C /* Client.swift in Sources */,
519 | 5D8BF5411BB5258000A5575C /* Location.swift in Sources */,
520 | 5D8BF53C1BB5257400A5575C /* LightTargetObserver.swift in Sources */,
521 | 5D8C3D9E1BC5FC990059E05D /* LightTargetSelector.swift in Sources */,
522 | 5D8BF5391BB5256D00A5575C /* ClientObserver.swift in Sources */,
523 | 5D50E0591BC3A21800AED146 /* Scene.swift in Sources */,
524 | 5D50E05B1BC3A26A00AED146 /* State.swift in Sources */,
525 | 5D8BF5401BB5257D00A5575C /* Group.swift in Sources */,
526 | 5DF3F61A1BD45C0F0002E52D /* HTTPOperation.swift in Sources */,
527 | 5D8BF53B1BB5257200A5575C /* LightTarget.swift in Sources */,
528 | E9F4B5791E950F4800D0ED01 /* ProductInformation.swift in Sources */,
529 | 5D8BF5421BB5258200A5575C /* Result.swift in Sources */,
530 | 5D8BF5371BB5256900A5575C /* Errors.swift in Sources */,
531 | 5D8BF53E1BB5257900A5575C /* Light.swift in Sources */,
532 | 5D8BF53D1BB5257600A5575C /* HTTPSession.swift in Sources */,
533 | 5D8BF53F1BB5257B00A5575C /* Color.swift in Sources */,
534 | );
535 | runOnlyForDeploymentPostprocessing = 0;
536 | };
537 | 908631C81CDC7629006D9E47 /* Sources */ = {
538 | isa = PBXSourcesBuildPhase;
539 | buildActionMask = 2147483647;
540 | files = (
541 | 903A12C11CE063EA0071D8F0 /* Client.swift in Sources */,
542 | 903A12C51CE063F70071D8F0 /* LightTargetObserver.swift in Sources */,
543 | 903A12C21CE063ED0071D8F0 /* ClientObserver.swift in Sources */,
544 | 903A12C01CE063DD0071D8F0 /* Errors.swift in Sources */,
545 | 903A12C41CE063F30071D8F0 /* LightTargetSelector.swift in Sources */,
546 | 903A12C31CE063F00071D8F0 /* LightTarget.swift in Sources */,
547 | 903A12CE1CE064130071D8F0 /* Result.swift in Sources */,
548 | 903A12CD1CE064100071D8F0 /* Location.swift in Sources */,
549 | 903A12CC1CE0640C0071D8F0 /* Group.swift in Sources */,
550 | 903A12C91CE064040071D8F0 /* Light.swift in Sources */,
551 | 903A12C61CE063FA0071D8F0 /* HTTPSession.swift in Sources */,
552 | 903A12C71CE063FE0071D8F0 /* HTTPOperation.swift in Sources */,
553 | 903A12C81CE064010071D8F0 /* Color.swift in Sources */,
554 | 903A12CA1CE064070071D8F0 /* Scene.swift in Sources */,
555 | 903A12CB1CE064090071D8F0 /* State.swift in Sources */,
556 | DD1E132F81EB962FED0C262F /* ProductInformation.swift in Sources */,
557 | );
558 | runOnlyForDeploymentPostprocessing = 0;
559 | };
560 | /* End PBXSourcesBuildPhase section */
561 |
562 | /* Begin PBXTargetDependency section */
563 | 5D5898B01B1887530024D47B /* PBXTargetDependency */ = {
564 | isa = PBXTargetDependency;
565 | target = 5D5898A11B1887530024D47B /* LIFXHTTPKit-Mac */;
566 | targetProxy = 5D5898AF1B1887530024D47B /* PBXContainerItemProxy */;
567 | };
568 | 5DE1635D1BB8AF3B0057436C /* PBXTargetDependency */ = {
569 | isa = PBXTargetDependency;
570 | target = 5D8BF52E1BB524F400A5575C /* LIFXHTTPKit-iOS */;
571 | targetProxy = 5DE1635C1BB8AF3B0057436C /* PBXContainerItemProxy */;
572 | };
573 | /* End PBXTargetDependency section */
574 |
575 | /* Begin XCBuildConfiguration section */
576 | 5D05E6271BB5296E007B917D /* Debug */ = {
577 | isa = XCBuildConfiguration;
578 | buildSettings = {
579 | COMBINE_HIDPI_IMAGES = YES;
580 | FRAMEWORK_SEARCH_PATHS = (
581 | "$(DEVELOPER_FRAMEWORKS_DIR)",
582 | "$(inherited)",
583 | );
584 | GCC_PREPROCESSOR_DEFINITIONS = (
585 | "DEBUG=1",
586 | "$(inherited)",
587 | );
588 | INFOPLIST_FILE = Tests/Info.plist;
589 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
590 | PRODUCT_BUNDLE_IDENTIFIER = "com.tatey.$(PRODUCT_NAME:rfc1034identifier)";
591 | PRODUCT_NAME = "$(TARGET_NAME)";
592 | SDKROOT = iphoneos;
593 | SWIFT_VERSION = 3.0;
594 | };
595 | name = Debug;
596 | };
597 | 5D05E6281BB5296E007B917D /* Release */ = {
598 | isa = XCBuildConfiguration;
599 | buildSettings = {
600 | COMBINE_HIDPI_IMAGES = YES;
601 | FRAMEWORK_SEARCH_PATHS = (
602 | "$(DEVELOPER_FRAMEWORKS_DIR)",
603 | "$(inherited)",
604 | );
605 | INFOPLIST_FILE = Tests/Info.plist;
606 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
607 | PRODUCT_BUNDLE_IDENTIFIER = "com.tatey.$(PRODUCT_NAME:rfc1034identifier)";
608 | PRODUCT_NAME = "$(TARGET_NAME)";
609 | SDKROOT = iphoneos;
610 | SWIFT_VERSION = 3.0;
611 | };
612 | name = Release;
613 | };
614 | 5D5898B61B1887530024D47B /* Debug */ = {
615 | isa = XCBuildConfiguration;
616 | buildSettings = {
617 | ALWAYS_SEARCH_USER_PATHS = NO;
618 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
619 | CLANG_CXX_LIBRARY = "libc++";
620 | CLANG_ENABLE_MODULES = YES;
621 | CLANG_ENABLE_OBJC_ARC = YES;
622 | CLANG_WARN_BOOL_CONVERSION = YES;
623 | CLANG_WARN_CONSTANT_CONVERSION = YES;
624 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
625 | CLANG_WARN_EMPTY_BODY = YES;
626 | CLANG_WARN_ENUM_CONVERSION = YES;
627 | CLANG_WARN_INFINITE_RECURSION = YES;
628 | CLANG_WARN_INT_CONVERSION = YES;
629 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
630 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
631 | CLANG_WARN_UNREACHABLE_CODE = YES;
632 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
633 | COPY_PHASE_STRIP = NO;
634 | CURRENT_PROJECT_VERSION = 1;
635 | DEBUG_INFORMATION_FORMAT = dwarf;
636 | ENABLE_STRICT_OBJC_MSGSEND = YES;
637 | ENABLE_TESTABILITY = YES;
638 | GCC_C_LANGUAGE_STANDARD = gnu99;
639 | GCC_DYNAMIC_NO_PIC = NO;
640 | GCC_NO_COMMON_BLOCKS = YES;
641 | GCC_OPTIMIZATION_LEVEL = 0;
642 | GCC_PREPROCESSOR_DEFINITIONS = (
643 | "DEBUG=1",
644 | "$(inherited)",
645 | );
646 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
647 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
648 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
649 | GCC_WARN_UNDECLARED_SELECTOR = YES;
650 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
651 | GCC_WARN_UNUSED_FUNCTION = YES;
652 | GCC_WARN_UNUSED_VARIABLE = YES;
653 | MTL_ENABLE_DEBUG_INFO = YES;
654 | ONLY_ACTIVE_ARCH = YES;
655 | SDKROOT = "";
656 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
657 | VERSIONING_SYSTEM = "apple-generic";
658 | VERSION_INFO_PREFIX = "";
659 | };
660 | name = Debug;
661 | };
662 | 5D5898B71B1887530024D47B /* Release */ = {
663 | isa = XCBuildConfiguration;
664 | buildSettings = {
665 | ALWAYS_SEARCH_USER_PATHS = NO;
666 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
667 | CLANG_CXX_LIBRARY = "libc++";
668 | CLANG_ENABLE_MODULES = YES;
669 | CLANG_ENABLE_OBJC_ARC = YES;
670 | CLANG_WARN_BOOL_CONVERSION = YES;
671 | CLANG_WARN_CONSTANT_CONVERSION = YES;
672 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
673 | CLANG_WARN_EMPTY_BODY = YES;
674 | CLANG_WARN_ENUM_CONVERSION = YES;
675 | CLANG_WARN_INFINITE_RECURSION = YES;
676 | CLANG_WARN_INT_CONVERSION = YES;
677 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
678 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
679 | CLANG_WARN_UNREACHABLE_CODE = YES;
680 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
681 | COPY_PHASE_STRIP = NO;
682 | CURRENT_PROJECT_VERSION = 1;
683 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
684 | ENABLE_NS_ASSERTIONS = NO;
685 | ENABLE_STRICT_OBJC_MSGSEND = YES;
686 | GCC_C_LANGUAGE_STANDARD = gnu99;
687 | GCC_NO_COMMON_BLOCKS = YES;
688 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
689 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
690 | GCC_WARN_UNDECLARED_SELECTOR = YES;
691 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
692 | GCC_WARN_UNUSED_FUNCTION = YES;
693 | GCC_WARN_UNUSED_VARIABLE = YES;
694 | MTL_ENABLE_DEBUG_INFO = NO;
695 | SDKROOT = "";
696 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
697 | VERSIONING_SYSTEM = "apple-generic";
698 | VERSION_INFO_PREFIX = "";
699 | };
700 | name = Release;
701 | };
702 | 5D5898B91B1887530024D47B /* Debug */ = {
703 | isa = XCBuildConfiguration;
704 | buildSettings = {
705 | APPLICATION_EXTENSION_API_ONLY = YES;
706 | CLANG_ENABLE_MODULES = YES;
707 | COMBINE_HIDPI_IMAGES = YES;
708 | DEFINES_MODULE = YES;
709 | DYLIB_COMPATIBILITY_VERSION = 1;
710 | DYLIB_CURRENT_VERSION = 1;
711 | DYLIB_INSTALL_NAME_BASE = "@rpath";
712 | FRAMEWORK_VERSION = A;
713 | INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
714 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
715 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
716 | MACOSX_DEPLOYMENT_TARGET = 10.10;
717 | PRODUCT_BUNDLE_IDENTIFIER = com.tatey.LIFXHTTPKit;
718 | PRODUCT_NAME = LIFXHTTPKit;
719 | SDKROOT = macosx;
720 | SKIP_INSTALL = YES;
721 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
722 | SWIFT_VERSION = 3.0;
723 | };
724 | name = Debug;
725 | };
726 | 5D5898BA1B1887530024D47B /* Release */ = {
727 | isa = XCBuildConfiguration;
728 | buildSettings = {
729 | APPLICATION_EXTENSION_API_ONLY = YES;
730 | CLANG_ENABLE_MODULES = YES;
731 | COMBINE_HIDPI_IMAGES = YES;
732 | DEFINES_MODULE = YES;
733 | DYLIB_COMPATIBILITY_VERSION = 1;
734 | DYLIB_CURRENT_VERSION = 1;
735 | DYLIB_INSTALL_NAME_BASE = "@rpath";
736 | FRAMEWORK_VERSION = A;
737 | INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
738 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
739 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
740 | MACOSX_DEPLOYMENT_TARGET = 10.10;
741 | PRODUCT_BUNDLE_IDENTIFIER = com.tatey.LIFXHTTPKit;
742 | PRODUCT_NAME = LIFXHTTPKit;
743 | SDKROOT = macosx;
744 | SKIP_INSTALL = YES;
745 | SWIFT_VERSION = 3.0;
746 | };
747 | name = Release;
748 | };
749 | 5D5898BC1B1887530024D47B /* Debug */ = {
750 | isa = XCBuildConfiguration;
751 | buildSettings = {
752 | COMBINE_HIDPI_IMAGES = YES;
753 | FRAMEWORK_SEARCH_PATHS = (
754 | "$(DEVELOPER_FRAMEWORKS_DIR)",
755 | "$(inherited)",
756 | );
757 | GCC_PREPROCESSOR_DEFINITIONS = (
758 | "DEBUG=1",
759 | "$(inherited)",
760 | );
761 | INFOPLIST_FILE = Tests/Info.plist;
762 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
763 | PRODUCT_BUNDLE_IDENTIFIER = "com.tatey.$(PRODUCT_NAME:rfc1034identifier)";
764 | PRODUCT_NAME = "$(TARGET_NAME)";
765 | SDKROOT = macosx;
766 | SWIFT_VERSION = 3.0;
767 | };
768 | name = Debug;
769 | };
770 | 5D5898BD1B1887530024D47B /* Release */ = {
771 | isa = XCBuildConfiguration;
772 | buildSettings = {
773 | COMBINE_HIDPI_IMAGES = YES;
774 | FRAMEWORK_SEARCH_PATHS = (
775 | "$(DEVELOPER_FRAMEWORKS_DIR)",
776 | "$(inherited)",
777 | );
778 | INFOPLIST_FILE = Tests/Info.plist;
779 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
780 | PRODUCT_BUNDLE_IDENTIFIER = "com.tatey.$(PRODUCT_NAME:rfc1034identifier)";
781 | PRODUCT_NAME = "$(TARGET_NAME)";
782 | SDKROOT = macosx;
783 | SWIFT_VERSION = 3.0;
784 | };
785 | name = Release;
786 | };
787 | 5D8BF5341BB524F400A5575C /* Debug */ = {
788 | isa = XCBuildConfiguration;
789 | buildSettings = {
790 | APPLICATION_EXTENSION_API_ONLY = YES;
791 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
792 | DEFINES_MODULE = YES;
793 | DYLIB_COMPATIBILITY_VERSION = 1;
794 | DYLIB_CURRENT_VERSION = 1;
795 | DYLIB_INSTALL_NAME_BASE = "@rpath";
796 | INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
797 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
798 | IPHONEOS_DEPLOYMENT_TARGET = 8.2;
799 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
800 | PRODUCT_BUNDLE_IDENTIFIER = com.tatey.LIFXHTTPKit;
801 | PRODUCT_NAME = LIFXHTTPKit;
802 | SDKROOT = iphoneos;
803 | SKIP_INSTALL = YES;
804 | SWIFT_VERSION = 3.0;
805 | TARGETED_DEVICE_FAMILY = "1,2";
806 | };
807 | name = Debug;
808 | };
809 | 5D8BF5351BB524F400A5575C /* Release */ = {
810 | isa = XCBuildConfiguration;
811 | buildSettings = {
812 | APPLICATION_EXTENSION_API_ONLY = YES;
813 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
814 | DEFINES_MODULE = YES;
815 | DYLIB_COMPATIBILITY_VERSION = 1;
816 | DYLIB_CURRENT_VERSION = 1;
817 | DYLIB_INSTALL_NAME_BASE = "@rpath";
818 | INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
819 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
820 | IPHONEOS_DEPLOYMENT_TARGET = 8.2;
821 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
822 | PRODUCT_BUNDLE_IDENTIFIER = com.tatey.LIFXHTTPKit;
823 | PRODUCT_NAME = LIFXHTTPKit;
824 | SDKROOT = iphoneos;
825 | SKIP_INSTALL = YES;
826 | SWIFT_VERSION = 3.0;
827 | TARGETED_DEVICE_FAMILY = "1,2";
828 | VALIDATE_PRODUCT = YES;
829 | };
830 | name = Release;
831 | };
832 | 908631D21CDC7629006D9E47 /* Debug */ = {
833 | isa = XCBuildConfiguration;
834 | buildSettings = {
835 | APPLICATION_EXTENSION_API_ONLY = YES;
836 | CLANG_ANALYZER_NONNULL = YES;
837 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
838 | DEFINES_MODULE = YES;
839 | DYLIB_COMPATIBILITY_VERSION = 1;
840 | DYLIB_CURRENT_VERSION = 1;
841 | DYLIB_INSTALL_NAME_BASE = "@rpath";
842 | INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
843 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
844 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
845 | PRODUCT_BUNDLE_IDENTIFIER = com.tatey.LIFXHTTPKit;
846 | PRODUCT_NAME = LIFXHTTPKit;
847 | SDKROOT = watchos;
848 | SKIP_INSTALL = YES;
849 | SWIFT_VERSION = 3.0;
850 | TARGETED_DEVICE_FAMILY = 4;
851 | WATCHOS_DEPLOYMENT_TARGET = 2.2;
852 | };
853 | name = Debug;
854 | };
855 | 908631D31CDC7629006D9E47 /* Release */ = {
856 | isa = XCBuildConfiguration;
857 | buildSettings = {
858 | APPLICATION_EXTENSION_API_ONLY = YES;
859 | CLANG_ANALYZER_NONNULL = YES;
860 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
861 | DEFINES_MODULE = YES;
862 | DYLIB_COMPATIBILITY_VERSION = 1;
863 | DYLIB_CURRENT_VERSION = 1;
864 | DYLIB_INSTALL_NAME_BASE = "@rpath";
865 | INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
866 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
867 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
868 | PRODUCT_BUNDLE_IDENTIFIER = com.tatey.LIFXHTTPKit;
869 | PRODUCT_NAME = LIFXHTTPKit;
870 | SDKROOT = watchos;
871 | SKIP_INSTALL = YES;
872 | SWIFT_VERSION = 3.0;
873 | TARGETED_DEVICE_FAMILY = 4;
874 | VALIDATE_PRODUCT = YES;
875 | WATCHOS_DEPLOYMENT_TARGET = 2.2;
876 | };
877 | name = Release;
878 | };
879 | /* End XCBuildConfiguration section */
880 |
881 | /* Begin XCConfigurationList section */
882 | 5D05E6261BB5296E007B917D /* Build configuration list for PBXNativeTarget "LIFXHTTPKit-iOS-Tests" */ = {
883 | isa = XCConfigurationList;
884 | buildConfigurations = (
885 | 5D05E6271BB5296E007B917D /* Debug */,
886 | 5D05E6281BB5296E007B917D /* Release */,
887 | );
888 | defaultConfigurationIsVisible = 0;
889 | defaultConfigurationName = Release;
890 | };
891 | 5D58989C1B1887530024D47B /* Build configuration list for PBXProject "LIFXHTTPKit" */ = {
892 | isa = XCConfigurationList;
893 | buildConfigurations = (
894 | 5D5898B61B1887530024D47B /* Debug */,
895 | 5D5898B71B1887530024D47B /* Release */,
896 | );
897 | defaultConfigurationIsVisible = 0;
898 | defaultConfigurationName = Release;
899 | };
900 | 5D5898B81B1887530024D47B /* Build configuration list for PBXNativeTarget "LIFXHTTPKit-Mac" */ = {
901 | isa = XCConfigurationList;
902 | buildConfigurations = (
903 | 5D5898B91B1887530024D47B /* Debug */,
904 | 5D5898BA1B1887530024D47B /* Release */,
905 | );
906 | defaultConfigurationIsVisible = 0;
907 | defaultConfigurationName = Release;
908 | };
909 | 5D5898BB1B1887530024D47B /* Build configuration list for PBXNativeTarget "LIFXHTTPKit-Mac-Tests" */ = {
910 | isa = XCConfigurationList;
911 | buildConfigurations = (
912 | 5D5898BC1B1887530024D47B /* Debug */,
913 | 5D5898BD1B1887530024D47B /* Release */,
914 | );
915 | defaultConfigurationIsVisible = 0;
916 | defaultConfigurationName = Release;
917 | };
918 | 5D8BF5361BB524F400A5575C /* Build configuration list for PBXNativeTarget "LIFXHTTPKit-iOS" */ = {
919 | isa = XCConfigurationList;
920 | buildConfigurations = (
921 | 5D8BF5341BB524F400A5575C /* Debug */,
922 | 5D8BF5351BB524F400A5575C /* Release */,
923 | );
924 | defaultConfigurationIsVisible = 0;
925 | defaultConfigurationName = Release;
926 | };
927 | 908631D41CDC7629006D9E47 /* Build configuration list for PBXNativeTarget "LIFXHTTPKit-watchOS" */ = {
928 | isa = XCConfigurationList;
929 | buildConfigurations = (
930 | 908631D21CDC7629006D9E47 /* Debug */,
931 | 908631D31CDC7629006D9E47 /* Release */,
932 | );
933 | defaultConfigurationIsVisible = 0;
934 | defaultConfigurationName = Release;
935 | };
936 | /* End XCConfigurationList section */
937 | };
938 | rootObject = 5D5898991B1887530024D47B /* Project object */;
939 | }
940 |
--------------------------------------------------------------------------------