├── 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 [![GitHub release](https://img.shields.io/github/release/tatey/LIFXHTTPKit.svg)](https://github.com/tatey/LIFXHTTPKit/releases/latest) [![GitHub license](https://img.shields.io/github/license/tatey/LIFXHTTPKit.svg)](https://raw.githubusercontent.com/tatey/LIFXHTTPKit/master/LICENSE.txt) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](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 | --------------------------------------------------------------------------------