├── .gitignore ├── Podfile ├── Podfile.lock ├── README.md ├── drone-demo-controller.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── drone-demo-controller.xcworkspace └── contents.xcworkspacedata └── drone-demo-controller ├── AppDelegate.swift ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── BLETypes.swift ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── BeaconParser.swift ├── Config.swift ├── Info.plist ├── Lights.swift ├── Network.swift ├── StringExtensions.swift ├── ViewController+Appearance.swift ├── ViewController+BLE.swift ├── ViewController+ExternalController.swift ├── ViewController+Logging.swift ├── ViewController+OutOfRange.swift └── ViewController.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 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 | profile 13 | xcuserdata/* 14 | 15 | *.swp 16 | *.swo 17 | *.rbo 18 | *.gem 19 | 20 | Wallet.xcworkspace/* 21 | Pods/* 22 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '8.4' 3 | # Uncomment this line if you're using Swift 4 | use_frameworks! 5 | 6 | xcodeproj 'drone-demo-controller.xcodeproj' 7 | 8 | source 'https://github.com/CocoaPods/Specs.git' 9 | 10 | def source_pods 11 | pod "Curry", "2.3.3" 12 | pod "Alamofire", "3.5.0" 13 | pod "Argo", "3.1.0" 14 | pod 'CryptoSwift', :git => "https://github.com/krzyzanowskim/CryptoSwift", :branch => "swift21" 15 | end 16 | 17 | target 'drone-demo-controller' do 18 | source_pods 19 | end 20 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (3.5.0) 3 | - Argo (3.1.0) 4 | - CryptoSwift (0.2.3) 5 | - Curry (2.3.3) 6 | 7 | DEPENDENCIES: 8 | - Alamofire (= 3.5.0) 9 | - Argo (= 3.1.0) 10 | - CryptoSwift (from `https://github.com/krzyzanowskim/CryptoSwift`, branch `swift21`) 11 | - Curry (= 2.3.3) 12 | 13 | EXTERNAL SOURCES: 14 | CryptoSwift: 15 | :branch: swift21 16 | :git: https://github.com/krzyzanowskim/CryptoSwift 17 | 18 | CHECKOUT OPTIONS: 19 | CryptoSwift: 20 | :commit: 0461202e013b8ff2ea07f4d05e42cf5dd373ccd0 21 | :git: https://github.com/krzyzanowskim/CryptoSwift 22 | 23 | SPEC CHECKSUMS: 24 | Alamofire: b70a7352335f8ea5babd0a923eb7e8eacc67b877 25 | Argo: bb0e94e309d6ceb8fa58910205c8968be101e429 26 | CryptoSwift: b2bf37b8e7cc6d682ef3365cb966bc5ae5174aa5 27 | Curry: 37223f4467e4802a08b6a414b526dcd919a4a777 28 | 29 | COCOAPODS: 0.39.0 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drone Demo Controller 2 | iOS app for controlling Chronicled's drone demo 3 | 4 | ### Description 5 | This app verifies Chronicled BLE chips using Apple's [CoreBluetooth](https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/AboutCoreBluetooth/Introduction.html) library. This app also controls the demo's [lights](http://www2.meethue.com/en-us/about-hue?&origin=%7Cmckv%7CsC8ExIQm5_dc&pcrid=78454527516%7Cplid%7C&gclid=Cj0KEQjw1ee_BRD3hK6x993YzeoBEiQA5RH_BBB40tvaoMY9aw0S3Imvvsec8mek3GTGaNahySqvuEUaAovE8P8HAQ) and [drapes](http://www.lutron.com/en-US/Products/Pages/ShadingSystems/SivoiaQS/WindowTreatments/DraperySystems/Drapery.aspx) 6 | 7 | ### Demo Explaination 8 | 9 | - Done approaches iPhone near drapes to be opened 10 | - This app discovers the chip and requests a challenge from the server. The identity used is the `deviceID`. A deviceID is a 6 byte unique hex string. `ble:1.0:1234567890ab` 11 | 12 | ```swift 13 | static func requestChallenge(identity: String, cb: (Response -> ())) { 14 | let req = Alamofire.request(.POST, 15 | "\(Config.domain)requestChallenge", 16 | parameters: ["identity" : identity], 17 | encoding: .JSON, 18 | headers: headers) 19 | 20 | req.responseJSON(completionHandler: cb) 21 | } 22 | ``` 23 | 24 | - If the Registrant has access to the door, a challenge will be returned from the server. If the Registrant doesn't have access, the chip will be rejected. The app will connect to the chip if a challenge is returned: 25 | 26 | ```swift 27 | self.centralManager?.connectPeripheral(peripheral, options: nil) 28 | ``` 29 | 30 | - After connecting to the chip, the app writes the challenge received from the server to the Challenge Characteristic of the BLE chip 31 | 32 | ```swift 33 | peripheral.writeValue(challenge.dataWithHexString(), 34 | forCharacteristic: challengeCharecteristic, 35 | type: .WithResponse) 36 | ``` 37 | 38 | - Now the app waits for the signature to be generated by the chip. The signature will be written to the Signature characteristic. After the signature has been generated, the app reads the signature. 39 | 40 | ```swift 41 | peripheral.readValueForCharacteristic(self.signatureChararacteristic) 42 | ``` 43 | 44 | - The app then sends the signature, challenge, and identity to the verification endpoint. 45 | 46 | ```swift 47 | static func sendVerification(identity: String, 48 | challenge: String, 49 | signature: String, 50 | cb: (Response -> ())) { 51 | let params = [ 52 | "identity" : identity, 53 | "challenge" : challenge, 54 | "signature" : signature 55 | ] 56 | 57 | let req = Alamofire.request(.POST, 58 | "\(Config.domain)verifyChallenge", 59 | parameters: params, 60 | encoding: .JSON, 61 | headers: headers) 62 | 63 | req.responseJSON(completionHandler: cb) 64 | } 65 | ``` 66 | - The server will return whether or not the signature is verified. Using this response, the app will either reject or grand acccess to the chip. 67 | -------------------------------------------------------------------------------- /drone-demo-controller.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 45453FFDFA0D6B8B03B72C27 /* Pods_drone_demo_controller.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1AF191342F3C60748A4290CF /* Pods_drone_demo_controller.framework */; }; 11 | FC31A4F51DA308FA009C36C5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC31A4F41DA308FA009C36C5 /* AppDelegate.swift */; }; 12 | FC31A4F71DA308FA009C36C5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC31A4F61DA308FA009C36C5 /* ViewController.swift */; }; 13 | FC31A4FA1DA308FA009C36C5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FC31A4F81DA308FA009C36C5 /* Main.storyboard */; }; 14 | FC31A4FC1DA308FA009C36C5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FC31A4FB1DA308FA009C36C5 /* Assets.xcassets */; }; 15 | FC31A4FF1DA308FA009C36C5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FC31A4FD1DA308FA009C36C5 /* LaunchScreen.storyboard */; }; 16 | FC4F59CC1DA45E5600455F58 /* ViewController+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4F59CB1DA45E5600455F58 /* ViewController+Appearance.swift */; }; 17 | FC4F59CE1DA45E8C00455F58 /* ViewController+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4F59CD1DA45E8C00455F58 /* ViewController+Logging.swift */; }; 18 | FC4F59D01DA45F0700455F58 /* ViewController+BLE.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4F59CF1DA45F0700455F58 /* ViewController+BLE.swift */; }; 19 | FC4F59D21DA465B200455F58 /* BeaconParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4F59D11DA465B200455F58 /* BeaconParser.swift */; }; 20 | FC4F59D41DA47A0800455F58 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4F59D31DA47A0800455F58 /* Network.swift */; }; 21 | FC4F59D61DA47A6C00455F58 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4F59D51DA47A6C00455F58 /* Config.swift */; }; 22 | FC4F59D81DA571ED00455F58 /* BLETypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4F59D71DA571ED00455F58 /* BLETypes.swift */; }; 23 | FC4F59DA1DA58B4B00455F58 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4F59D91DA58B4B00455F58 /* StringExtensions.swift */; }; 24 | FC4F59DC1DA59F0F00455F58 /* ViewController+ExternalController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4F59DB1DA59F0F00455F58 /* ViewController+ExternalController.swift */; }; 25 | FC5D6A1E1DA6F1C700FCCA0E /* Lights.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5D6A1D1DA6F1C700FCCA0E /* Lights.swift */; }; 26 | FC5D6A201DA72D3F00FCCA0E /* ViewController+OutOfRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5D6A1F1DA72D3F00FCCA0E /* ViewController+OutOfRange.swift */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | 1AF191342F3C60748A4290CF /* Pods_drone_demo_controller.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_drone_demo_controller.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | 98FB3B589C40589566D70C94 /* Pods-drone-demo-controller.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-drone-demo-controller.release.xcconfig"; path = "Pods/Target Support Files/Pods-drone-demo-controller/Pods-drone-demo-controller.release.xcconfig"; sourceTree = ""; }; 32 | ACE3C1EC2B78B1430F9F1DE0 /* Pods-drone-demo-controller.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-drone-demo-controller.debug.xcconfig"; path = "Pods/Target Support Files/Pods-drone-demo-controller/Pods-drone-demo-controller.debug.xcconfig"; sourceTree = ""; }; 33 | FC31A4F11DA308FA009C36C5 /* drone-demo-controller.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "drone-demo-controller.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | FC31A4F41DA308FA009C36C5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 35 | FC31A4F61DA308FA009C36C5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 36 | FC31A4F91DA308FA009C36C5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 37 | FC31A4FB1DA308FA009C36C5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 38 | FC31A4FE1DA308FA009C36C5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 39 | FC31A5001DA308FA009C36C5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | FC4F59CB1DA45E5600455F58 /* ViewController+Appearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ViewController+Appearance.swift"; sourceTree = ""; }; 41 | FC4F59CD1DA45E8C00455F58 /* ViewController+Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ViewController+Logging.swift"; sourceTree = ""; }; 42 | FC4F59CF1DA45F0700455F58 /* ViewController+BLE.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ViewController+BLE.swift"; sourceTree = ""; }; 43 | FC4F59D11DA465B200455F58 /* BeaconParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BeaconParser.swift; sourceTree = ""; }; 44 | FC4F59D31DA47A0800455F58 /* Network.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; 45 | FC4F59D51DA47A6C00455F58 /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; 46 | FC4F59D71DA571ED00455F58 /* BLETypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BLETypes.swift; sourceTree = ""; }; 47 | FC4F59D91DA58B4B00455F58 /* StringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; 48 | FC4F59DB1DA59F0F00455F58 /* ViewController+ExternalController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ViewController+ExternalController.swift"; sourceTree = ""; }; 49 | FC5D6A1D1DA6F1C700FCCA0E /* Lights.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Lights.swift; sourceTree = ""; }; 50 | FC5D6A1F1DA72D3F00FCCA0E /* ViewController+OutOfRange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ViewController+OutOfRange.swift"; sourceTree = ""; }; 51 | /* End PBXFileReference section */ 52 | 53 | /* Begin PBXFrameworksBuildPhase section */ 54 | FC31A4EE1DA308FA009C36C5 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | 45453FFDFA0D6B8B03B72C27 /* Pods_drone_demo_controller.framework in Frameworks */, 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | /* End PBXFrameworksBuildPhase section */ 63 | 64 | /* Begin PBXGroup section */ 65 | A1BB748CE3462169FAB502BB /* Pods */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | ACE3C1EC2B78B1430F9F1DE0 /* Pods-drone-demo-controller.debug.xcconfig */, 69 | 98FB3B589C40589566D70C94 /* Pods-drone-demo-controller.release.xcconfig */, 70 | ); 71 | name = Pods; 72 | sourceTree = ""; 73 | }; 74 | F848C5AEFF133EEBB819EFD2 /* Frameworks */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 1AF191342F3C60748A4290CF /* Pods_drone_demo_controller.framework */, 78 | ); 79 | name = Frameworks; 80 | sourceTree = ""; 81 | }; 82 | FC31A4E81DA308FA009C36C5 = { 83 | isa = PBXGroup; 84 | children = ( 85 | FC31A4F31DA308FA009C36C5 /* drone-demo-controller */, 86 | FC31A4F21DA308FA009C36C5 /* Products */, 87 | A1BB748CE3462169FAB502BB /* Pods */, 88 | F848C5AEFF133EEBB819EFD2 /* Frameworks */, 89 | ); 90 | sourceTree = ""; 91 | }; 92 | FC31A4F21DA308FA009C36C5 /* Products */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | FC31A4F11DA308FA009C36C5 /* drone-demo-controller.app */, 96 | ); 97 | name = Products; 98 | sourceTree = ""; 99 | }; 100 | FC31A4F31DA308FA009C36C5 /* drone-demo-controller */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | FC31A4F41DA308FA009C36C5 /* AppDelegate.swift */, 104 | FC4F59D71DA571ED00455F58 /* BLETypes.swift */, 105 | FC4F59D11DA465B200455F58 /* BeaconParser.swift */, 106 | FC4F59D51DA47A6C00455F58 /* Config.swift */, 107 | FC5D6A1D1DA6F1C700FCCA0E /* Lights.swift */, 108 | FC4F59D31DA47A0800455F58 /* Network.swift */, 109 | FC4F59D91DA58B4B00455F58 /* StringExtensions.swift */, 110 | FC31A4F61DA308FA009C36C5 /* ViewController.swift */, 111 | FC4F59CB1DA45E5600455F58 /* ViewController+Appearance.swift */, 112 | FC4F59CF1DA45F0700455F58 /* ViewController+BLE.swift */, 113 | FC4F59DB1DA59F0F00455F58 /* ViewController+ExternalController.swift */, 114 | FC4F59CD1DA45E8C00455F58 /* ViewController+Logging.swift */, 115 | FC5D6A1F1DA72D3F00FCCA0E /* ViewController+OutOfRange.swift */, 116 | FC31A4F81DA308FA009C36C5 /* Main.storyboard */, 117 | FC31A4FB1DA308FA009C36C5 /* Assets.xcassets */, 118 | FC31A4FD1DA308FA009C36C5 /* LaunchScreen.storyboard */, 119 | FC31A5001DA308FA009C36C5 /* Info.plist */, 120 | ); 121 | path = "drone-demo-controller"; 122 | sourceTree = ""; 123 | }; 124 | /* End PBXGroup section */ 125 | 126 | /* Begin PBXNativeTarget section */ 127 | FC31A4F01DA308FA009C36C5 /* drone-demo-controller */ = { 128 | isa = PBXNativeTarget; 129 | buildConfigurationList = FC31A5031DA308FA009C36C5 /* Build configuration list for PBXNativeTarget "drone-demo-controller" */; 130 | buildPhases = ( 131 | AAD0D1B533C8E1D8DB70EF33 /* Check Pods Manifest.lock */, 132 | FC31A4ED1DA308FA009C36C5 /* Sources */, 133 | FC31A4EE1DA308FA009C36C5 /* Frameworks */, 134 | FC31A4EF1DA308FA009C36C5 /* Resources */, 135 | 479AF7DF5B1F47046DF2D991 /* Embed Pods Frameworks */, 136 | 9DBF7F13BE9F5EBAD3E7134D /* Copy Pods Resources */, 137 | ); 138 | buildRules = ( 139 | ); 140 | dependencies = ( 141 | ); 142 | name = "drone-demo-controller"; 143 | productName = "drone-demo-controller"; 144 | productReference = FC31A4F11DA308FA009C36C5 /* drone-demo-controller.app */; 145 | productType = "com.apple.product-type.application"; 146 | }; 147 | /* End PBXNativeTarget section */ 148 | 149 | /* Begin PBXProject section */ 150 | FC31A4E91DA308FA009C36C5 /* Project object */ = { 151 | isa = PBXProject; 152 | attributes = { 153 | LastSwiftUpdateCheck = 0730; 154 | LastUpgradeCheck = 0730; 155 | ORGANIZATIONNAME = Chronicled; 156 | TargetAttributes = { 157 | FC31A4F01DA308FA009C36C5 = { 158 | CreatedOnToolsVersion = 7.3.1; 159 | }; 160 | }; 161 | }; 162 | buildConfigurationList = FC31A4EC1DA308FA009C36C5 /* Build configuration list for PBXProject "drone-demo-controller" */; 163 | compatibilityVersion = "Xcode 3.2"; 164 | developmentRegion = English; 165 | hasScannedForEncodings = 0; 166 | knownRegions = ( 167 | en, 168 | Base, 169 | ); 170 | mainGroup = FC31A4E81DA308FA009C36C5; 171 | productRefGroup = FC31A4F21DA308FA009C36C5 /* Products */; 172 | projectDirPath = ""; 173 | projectRoot = ""; 174 | targets = ( 175 | FC31A4F01DA308FA009C36C5 /* drone-demo-controller */, 176 | ); 177 | }; 178 | /* End PBXProject section */ 179 | 180 | /* Begin PBXResourcesBuildPhase section */ 181 | FC31A4EF1DA308FA009C36C5 /* Resources */ = { 182 | isa = PBXResourcesBuildPhase; 183 | buildActionMask = 2147483647; 184 | files = ( 185 | FC31A4FF1DA308FA009C36C5 /* LaunchScreen.storyboard in Resources */, 186 | FC31A4FC1DA308FA009C36C5 /* Assets.xcassets in Resources */, 187 | FC31A4FA1DA308FA009C36C5 /* Main.storyboard in Resources */, 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | /* End PBXResourcesBuildPhase section */ 192 | 193 | /* Begin PBXShellScriptBuildPhase section */ 194 | 479AF7DF5B1F47046DF2D991 /* Embed Pods Frameworks */ = { 195 | isa = PBXShellScriptBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | ); 199 | inputPaths = ( 200 | ); 201 | name = "Embed Pods Frameworks"; 202 | outputPaths = ( 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | shellPath = /bin/sh; 206 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-drone-demo-controller/Pods-drone-demo-controller-frameworks.sh\"\n"; 207 | showEnvVarsInLog = 0; 208 | }; 209 | 9DBF7F13BE9F5EBAD3E7134D /* Copy Pods Resources */ = { 210 | isa = PBXShellScriptBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | inputPaths = ( 215 | ); 216 | name = "Copy Pods Resources"; 217 | outputPaths = ( 218 | ); 219 | runOnlyForDeploymentPostprocessing = 0; 220 | shellPath = /bin/sh; 221 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-drone-demo-controller/Pods-drone-demo-controller-resources.sh\"\n"; 222 | showEnvVarsInLog = 0; 223 | }; 224 | AAD0D1B533C8E1D8DB70EF33 /* Check Pods Manifest.lock */ = { 225 | isa = PBXShellScriptBuildPhase; 226 | buildActionMask = 2147483647; 227 | files = ( 228 | ); 229 | inputPaths = ( 230 | ); 231 | name = "Check Pods Manifest.lock"; 232 | outputPaths = ( 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | shellPath = /bin/sh; 236 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 237 | showEnvVarsInLog = 0; 238 | }; 239 | /* End PBXShellScriptBuildPhase section */ 240 | 241 | /* Begin PBXSourcesBuildPhase section */ 242 | FC31A4ED1DA308FA009C36C5 /* Sources */ = { 243 | isa = PBXSourcesBuildPhase; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | FC4F59CE1DA45E8C00455F58 /* ViewController+Logging.swift in Sources */, 247 | FC4F59CC1DA45E5600455F58 /* ViewController+Appearance.swift in Sources */, 248 | FC4F59D21DA465B200455F58 /* BeaconParser.swift in Sources */, 249 | FC4F59D81DA571ED00455F58 /* BLETypes.swift in Sources */, 250 | FC4F59D01DA45F0700455F58 /* ViewController+BLE.swift in Sources */, 251 | FC31A4F71DA308FA009C36C5 /* ViewController.swift in Sources */, 252 | FC5D6A1E1DA6F1C700FCCA0E /* Lights.swift in Sources */, 253 | FC4F59D61DA47A6C00455F58 /* Config.swift in Sources */, 254 | FC4F59D41DA47A0800455F58 /* Network.swift in Sources */, 255 | FC4F59DA1DA58B4B00455F58 /* StringExtensions.swift in Sources */, 256 | FC4F59DC1DA59F0F00455F58 /* ViewController+ExternalController.swift in Sources */, 257 | FC31A4F51DA308FA009C36C5 /* AppDelegate.swift in Sources */, 258 | FC5D6A201DA72D3F00FCCA0E /* ViewController+OutOfRange.swift in Sources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | /* End PBXSourcesBuildPhase section */ 263 | 264 | /* Begin PBXVariantGroup section */ 265 | FC31A4F81DA308FA009C36C5 /* Main.storyboard */ = { 266 | isa = PBXVariantGroup; 267 | children = ( 268 | FC31A4F91DA308FA009C36C5 /* Base */, 269 | ); 270 | name = Main.storyboard; 271 | sourceTree = ""; 272 | }; 273 | FC31A4FD1DA308FA009C36C5 /* LaunchScreen.storyboard */ = { 274 | isa = PBXVariantGroup; 275 | children = ( 276 | FC31A4FE1DA308FA009C36C5 /* Base */, 277 | ); 278 | name = LaunchScreen.storyboard; 279 | sourceTree = ""; 280 | }; 281 | /* End PBXVariantGroup section */ 282 | 283 | /* Begin XCBuildConfiguration section */ 284 | FC31A5011DA308FA009C36C5 /* Debug */ = { 285 | isa = XCBuildConfiguration; 286 | buildSettings = { 287 | ALWAYS_SEARCH_USER_PATHS = NO; 288 | CLANG_ANALYZER_NONNULL = YES; 289 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 290 | CLANG_CXX_LIBRARY = "libc++"; 291 | CLANG_ENABLE_MODULES = YES; 292 | CLANG_ENABLE_OBJC_ARC = YES; 293 | CLANG_WARN_BOOL_CONVERSION = YES; 294 | CLANG_WARN_CONSTANT_CONVERSION = YES; 295 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 296 | CLANG_WARN_EMPTY_BODY = YES; 297 | CLANG_WARN_ENUM_CONVERSION = YES; 298 | CLANG_WARN_INT_CONVERSION = YES; 299 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 300 | CLANG_WARN_UNREACHABLE_CODE = YES; 301 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 302 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 303 | COPY_PHASE_STRIP = NO; 304 | DEBUG_INFORMATION_FORMAT = dwarf; 305 | ENABLE_STRICT_OBJC_MSGSEND = YES; 306 | ENABLE_TESTABILITY = YES; 307 | GCC_C_LANGUAGE_STANDARD = gnu99; 308 | GCC_DYNAMIC_NO_PIC = NO; 309 | GCC_NO_COMMON_BLOCKS = YES; 310 | GCC_OPTIMIZATION_LEVEL = 0; 311 | GCC_PREPROCESSOR_DEFINITIONS = ( 312 | "DEBUG=1", 313 | "$(inherited)", 314 | ); 315 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 316 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 317 | GCC_WARN_UNDECLARED_SELECTOR = YES; 318 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 319 | GCC_WARN_UNUSED_FUNCTION = YES; 320 | GCC_WARN_UNUSED_VARIABLE = YES; 321 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 322 | MTL_ENABLE_DEBUG_INFO = YES; 323 | ONLY_ACTIVE_ARCH = YES; 324 | SDKROOT = iphoneos; 325 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 326 | }; 327 | name = Debug; 328 | }; 329 | FC31A5021DA308FA009C36C5 /* Release */ = { 330 | isa = XCBuildConfiguration; 331 | buildSettings = { 332 | ALWAYS_SEARCH_USER_PATHS = NO; 333 | CLANG_ANALYZER_NONNULL = YES; 334 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 335 | CLANG_CXX_LIBRARY = "libc++"; 336 | CLANG_ENABLE_MODULES = YES; 337 | CLANG_ENABLE_OBJC_ARC = YES; 338 | CLANG_WARN_BOOL_CONVERSION = YES; 339 | CLANG_WARN_CONSTANT_CONVERSION = YES; 340 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 341 | CLANG_WARN_EMPTY_BODY = YES; 342 | CLANG_WARN_ENUM_CONVERSION = YES; 343 | CLANG_WARN_INT_CONVERSION = YES; 344 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 345 | CLANG_WARN_UNREACHABLE_CODE = YES; 346 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 347 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 348 | COPY_PHASE_STRIP = NO; 349 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 350 | ENABLE_NS_ASSERTIONS = NO; 351 | ENABLE_STRICT_OBJC_MSGSEND = YES; 352 | GCC_C_LANGUAGE_STANDARD = gnu99; 353 | GCC_NO_COMMON_BLOCKS = YES; 354 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 355 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 356 | GCC_WARN_UNDECLARED_SELECTOR = YES; 357 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 358 | GCC_WARN_UNUSED_FUNCTION = YES; 359 | GCC_WARN_UNUSED_VARIABLE = YES; 360 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 361 | MTL_ENABLE_DEBUG_INFO = NO; 362 | SDKROOT = iphoneos; 363 | VALIDATE_PRODUCT = YES; 364 | }; 365 | name = Release; 366 | }; 367 | FC31A5041DA308FA009C36C5 /* Debug */ = { 368 | isa = XCBuildConfiguration; 369 | baseConfigurationReference = ACE3C1EC2B78B1430F9F1DE0 /* Pods-drone-demo-controller.debug.xcconfig */; 370 | buildSettings = { 371 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 372 | INFOPLIST_FILE = "drone-demo-controller/Info.plist"; 373 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 374 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 375 | PRODUCT_BUNDLE_IDENTIFIER = "Chronicled.drone-demo-controller"; 376 | PRODUCT_NAME = "$(TARGET_NAME)"; 377 | }; 378 | name = Debug; 379 | }; 380 | FC31A5051DA308FA009C36C5 /* Release */ = { 381 | isa = XCBuildConfiguration; 382 | baseConfigurationReference = 98FB3B589C40589566D70C94 /* Pods-drone-demo-controller.release.xcconfig */; 383 | buildSettings = { 384 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 385 | INFOPLIST_FILE = "drone-demo-controller/Info.plist"; 386 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 387 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 388 | PRODUCT_BUNDLE_IDENTIFIER = "Chronicled.drone-demo-controller"; 389 | PRODUCT_NAME = "$(TARGET_NAME)"; 390 | }; 391 | name = Release; 392 | }; 393 | /* End XCBuildConfiguration section */ 394 | 395 | /* Begin XCConfigurationList section */ 396 | FC31A4EC1DA308FA009C36C5 /* Build configuration list for PBXProject "drone-demo-controller" */ = { 397 | isa = XCConfigurationList; 398 | buildConfigurations = ( 399 | FC31A5011DA308FA009C36C5 /* Debug */, 400 | FC31A5021DA308FA009C36C5 /* Release */, 401 | ); 402 | defaultConfigurationIsVisible = 0; 403 | defaultConfigurationName = Release; 404 | }; 405 | FC31A5031DA308FA009C36C5 /* Build configuration list for PBXNativeTarget "drone-demo-controller" */ = { 406 | isa = XCConfigurationList; 407 | buildConfigurations = ( 408 | FC31A5041DA308FA009C36C5 /* Debug */, 409 | FC31A5051DA308FA009C36C5 /* Release */, 410 | ); 411 | defaultConfigurationIsVisible = 0; 412 | defaultConfigurationName = Release; 413 | }; 414 | /* End XCConfigurationList section */ 415 | }; 416 | rootObject = FC31A4E91DA308FA009C36C5 /* Project object */; 417 | } 418 | -------------------------------------------------------------------------------- /drone-demo-controller.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /drone-demo-controller.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /drone-demo-controller/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | typealias AdvertisementData = [String : AnyObject] 4 | typealias DeviceData = (String, UInt8) 5 | 6 | @UIApplicationMain 7 | class AppDelegate: UIResponder, UIApplicationDelegate { 8 | 9 | var window: UIWindow? 10 | 11 | 12 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 13 | 14 | UIApplication.sharedApplication().statusBarStyle = .LightContent 15 | return true 16 | } 17 | 18 | func applicationWillResignActive(application: UIApplication) { 19 | 20 | } 21 | 22 | func applicationDidEnterBackground(application: UIApplication) { 23 | 24 | } 25 | 26 | func applicationWillEnterForeground(application: UIApplication) { 27 | 28 | } 29 | 30 | func applicationDidBecomeActive(application: UIApplication) { 31 | 32 | } 33 | 34 | func applicationWillTerminate(application: UIApplication) { 35 | 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /drone-demo-controller/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /drone-demo-controller/BLETypes.swift: -------------------------------------------------------------------------------- 1 | enum Characteristic: String { 2 | case AuthState = "2bd76353-be26-4f47-a588-978e3dd7320a" 3 | case Unlock = "448bf545-62fb-4502-af96-b7b541cb749c" 4 | case Signature = "fcf32ac0-93f6-46c3-9462-416b631e1367" 5 | } 6 | 7 | enum Service: String { 8 | case Authentication = "B275CE22-FE42-4D6D-AED9-8C72855D1BD9" 9 | } -------------------------------------------------------------------------------- /drone-demo-controller/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /drone-demo-controller/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /drone-demo-controller/BeaconParser.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct BeaconParser { 4 | private static let deviceIdLocation = 12 5 | private static let deviceIdLength = 6 6 | private static let txPowerLocation = 1 7 | private static let txPowerLength = 1 8 | 9 | private static let eddystoneKey = "kCBAdvDataServiceData" 10 | private static let eddystoneLength = 20 11 | 12 | internal static func getBeaconDataFrom(advertisementData: AdvertisementData) -> DeviceData? { 13 | guard let eddystoneData = parseEddystoneBeacon(advertisementData) else { 14 | return nil 15 | } 16 | 17 | let deviceId = getDeviceIdFrom(eddystoneData) 18 | let txPower = getTxPowerFrom(eddystoneData) 19 | 20 | return (deviceId, txPower) 21 | } 22 | 23 | private static func parseEddystoneBeacon(advertisementData: AdvertisementData) -> NSData? { 24 | guard let eddystone = advertisementData[BeaconParser.eddystoneKey] as? Optional else { 25 | return nil 26 | } 27 | 28 | guard let eddystoneAsData = eddystone?.allValues.first as? NSData 29 | where eddystoneAsData.length == BeaconParser.eddystoneLength else { 30 | return nil 31 | } 32 | 33 | return eddystoneAsData 34 | } 35 | 36 | private static func getTxPowerFrom(eddystoneData: NSData) -> UInt8 { 37 | let txPowerRange = NSRange(location: BeaconParser.txPowerLocation, 38 | length: BeaconParser.txPowerLength) 39 | 40 | let txPowerData = eddystoneData.subdataWithRange(txPowerRange) 41 | var power: UInt8 = 0 42 | 43 | txPowerData.getBytes(&power, length: sizeof(UInt8)) 44 | 45 | return power 46 | } 47 | 48 | private static func getDeviceIdFrom(eddystoneData: NSData) -> String { 49 | let deviceIdRange = NSRange(location: BeaconParser.deviceIdLocation, 50 | length: BeaconParser.deviceIdLength) 51 | 52 | let deviceIdData = eddystoneData.subdataWithRange(deviceIdRange) 53 | 54 | return deviceIdData.hexadecimalString 55 | } 56 | } 57 | 58 | internal extension NSData { 59 | internal var hexadecimalString: String { 60 | var bytes = [UInt8](count: length, repeatedValue: 0) 61 | getBytes(&bytes, length: length) 62 | 63 | let hexString = NSMutableString() 64 | 65 | for byte in bytes { 66 | hexString.appendFormat("%02x", UInt8(byte)) 67 | } 68 | 69 | return String(hexString) 70 | } 71 | } -------------------------------------------------------------------------------- /drone-demo-controller/Config.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Config { 4 | static let phillipsDomain = "https://www.meethue.com/api/nupnp" 5 | static let phillipsID = "" 6 | static let makerDomain = "https://maker.ifttt.com/trigger/" 7 | static let makerKey = "" 8 | 9 | static let domain = "http://drone-demo.chronicled.com/" 10 | static let bearer = "" 11 | } 12 | -------------------------------------------------------------------------------- /drone-demo-controller/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LSApplicationCategoryType 6 | 7 | NSAppTransportSecurity 8 | 9 | NSAllowsArbitraryLoads 10 | 11 | 12 | UIViewControllerBasedStatusBarAppearance 13 | 14 | CFBundleDevelopmentRegion 15 | en 16 | CFBundleExecutable 17 | $(EXECUTABLE_NAME) 18 | CFBundleIdentifier 19 | $(PRODUCT_BUNDLE_IDENTIFIER) 20 | CFBundleInfoDictionaryVersion 21 | 6.0 22 | CFBundleName 23 | $(PRODUCT_NAME) 24 | CFBundlePackageType 25 | APPL 26 | CFBundleShortVersionString 27 | 1.0 28 | CFBundleSignature 29 | ???? 30 | CFBundleVersion 31 | 1 32 | LSRequiresIPhoneOS 33 | 34 | UILaunchStoryboardName 35 | LaunchScreen 36 | UIMainStoryboardFile 37 | Main 38 | UIRequiredDeviceCapabilities 39 | 40 | armv7 41 | 42 | UISupportedInterfaceOrientations 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /drone-demo-controller/Lights.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum LightStatus { 4 | case StandBy, Processing, Granted, Refused 5 | 6 | typealias Params = Dictionary 7 | 8 | func lightConfigurations() -> (Params, Params) { 9 | switch self { 10 | case .StandBy: return (Strip.standBy, Bulb.standBy) 11 | case .Granted: return (Strip.granted, Bulb.granted) 12 | case .Refused: return (Strip.refused, Bulb.refused) 13 | case .Processing: return (Strip.proccessing, Bulb.proccessing) 14 | } 15 | } 16 | } 17 | 18 | private struct Strip { 19 | static let standBy = ["on" : true, "sat" : 254, "bri" : 254, "hue" : 6000] 20 | static let granted = ["on" : true, "sat" : 254, "bri" : 254, "hue" : 13000] 21 | static let refused = ["on" : true, "sat" : 254, "bri" : 254, "hue" : 0] 22 | static let proccessing = ["on" : true, "sat" : 254, "bri" : 254, "hue" : 44000] 23 | } 24 | 25 | private struct Bulb { 26 | static let standBy = ["on" : true, "sat" : 254, "bri" : 1, "hue" : 11000] 27 | static let granted = ["on" : true, "sat" : 254, "bri" : 254, "hue" : 25000, "alert" : "lselect"] 28 | static let refused = ["on" : true, "sat" : 254, "bri" : 254, "hue" : 0] 29 | static let proccessing = ["on" : true, "sat" : 254, "bri" : 254, "hue" : 44000, "alert" : "lselect"] 30 | } 31 | -------------------------------------------------------------------------------- /drone-demo-controller/Network.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Alamofire 3 | 4 | private let headers = ["Authorization" : "Bearer \(Config.bearer)"] 5 | private var IP: String? 6 | 7 | struct Network { 8 | static func requestChallenge(identity: String, cb: (Response -> ())) { 9 | let req = Alamofire.request(.POST, 10 | "\(Config.domain)requestChallenge", 11 | parameters: ["identity" : identity], 12 | encoding: .JSON, 13 | headers: headers) 14 | 15 | req.responseJSON(completionHandler: cb) 16 | } 17 | 18 | static func sendVerification(identity: String, 19 | challenge: String, 20 | signature: String, 21 | cb: (Response -> ())) { 22 | let params = [ 23 | "identity" : identity, 24 | "challenge" : challenge, 25 | "signature" : signature 26 | ] 27 | 28 | let req = Alamofire.request(.POST, 29 | "\(Config.domain)verifyChallenge", 30 | parameters: params, 31 | encoding: .JSON, 32 | headers: headers) 33 | 34 | req.responseJSON(completionHandler: cb) 35 | } 36 | 37 | static func changeLights(to status: LightStatus) { 38 | let (strip, bulb) = status.lightConfigurations() 39 | 40 | Alamofire.request(.PUT, 41 | "http://\(IP!)/api/\(Config.phillipsID)/lights/1/state", 42 | parameters: strip, 43 | encoding: .JSON).responseJSON { _ in } 44 | 45 | Alamofire.request(.PUT, 46 | "http://\(IP!)/api/\(Config.phillipsID)/lights/2/state", 47 | parameters: bulb, 48 | encoding: .JSON).responseJSON { _ in } 49 | } 50 | 51 | static func openDoor() { 52 | let url = "\(Config.makerDomain)granted/with/key/\(Config.makerKey)" 53 | Alamofire.request(.GET, url) 54 | } 55 | 56 | static func closeDoor() { 57 | let url = "\(Config.makerDomain)standby/with/key/\(Config.makerKey)" 58 | Alamofire.request(.GET, url) 59 | } 60 | 61 | static func configureLightIP(completion: () -> ()) { 62 | Alamofire.request(.GET, Config.phillipsDomain).responseJSON() { response in 63 | if let data = response.result.value { 64 | guard let array = data as? [Dictionary] else { 65 | return 66 | } 67 | 68 | guard let value = array.first else { 69 | return 70 | } 71 | 72 | IP = String(value["internalipaddress"]!) 73 | 74 | completion() 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /drone-demo-controller/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | func dataWithHexString() -> NSData { 5 | var hex = self 6 | let data = NSMutableData() 7 | 8 | while hex.characters.count > 0 { 9 | let c = hex.substringToIndex(hex.startIndex.advancedBy(2)) 10 | hex = hex.substringFromIndex(hex.startIndex.advancedBy(2)) 11 | 12 | var ch: UInt32 = 0 13 | NSScanner(string: c).scanHexInt(&ch) 14 | data.appendBytes(&ch, length: 1) 15 | } 16 | 17 | return data 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /drone-demo-controller/ViewController+Appearance.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | extension ViewController { 5 | override func viewWillAppear(animated: Bool) { 6 | let app = UIApplication.sharedApplication() 7 | guard let bar = app.valueForKey("statusBar") else { 8 | return 9 | } 10 | 11 | guard let statusBar = bar as? UIView else { 12 | return 13 | } 14 | 15 | guard statusBar.respondsToSelector(Selector("setBackgroundColor:")) else { 16 | return 17 | } 18 | 19 | app.statusBarHidden = false 20 | app.statusBarStyle = .LightContent 21 | 22 | let color = UIColor(red: 0.20, green: 0.20, blue: 0.20, alpha: 1) 23 | statusBar.backgroundColor = color 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /drone-demo-controller/ViewController+BLE.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CoreBluetooth 3 | 4 | private let demoDeviceID = "c1002fc891f1" 5 | private let bleUUIDs = [CBUUID(string: "FEAA")] 6 | private let options = [CBCentralManagerScanOptionAllowDuplicatesKey : NSNumber(bool: true)] 7 | 8 | // MARK: CBCentralManagerDelegate Implementation 9 | extension ViewController { 10 | func centralManagerDidUpdateState(central: CBCentralManager) { 11 | log("#centralManagerDidUpdateState CALLED") 12 | 13 | centralManager = central 14 | centralManager?.scanForPeripheralsWithServices(bleUUIDs, options: options) 15 | } 16 | 17 | func centralManager(central: CBCentralManager, 18 | didDiscoverPeripheral peripheral: CBPeripheral, 19 | advertisementData: [String : AnyObject], 20 | RSSI: NSNumber) { 21 | 22 | guard let (deviceID, _) = BeaconParser.getBeaconDataFrom(advertisementData) 23 | /*where deviceID == demoDeviceID*/ else { 24 | return 25 | } 26 | 27 | log("#didDiscoverPeripheral - \(deviceID)") 28 | 29 | if !done && currentPeripheral == nil { 30 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) { 31 | DemoState.startProcessing() 32 | 33 | peripheral.delegate = self 34 | 35 | self.currentPeripheral = peripheral 36 | self.identity = "ble:1.0:\(deviceID)" 37 | self.sendChallengeRequestFor(peripheral) 38 | } 39 | } else if (done && currentPeripheral != nil) { 40 | log("ranging \(deviceID)") 41 | updateLastSeenTime() 42 | } 43 | } 44 | 45 | func centralManager(central: CBCentralManager, 46 | didConnectPeripheral peripheral: CBPeripheral) { 47 | log("#didConnectPeripheral CALLED") 48 | peripheral.discoverServices([CBUUID(string: Service.Authentication.rawValue)]) 49 | } 50 | } 51 | 52 | // MARK: CBPeripheralDelegate Implementation 53 | extension ViewController { 54 | func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) { 55 | log("#didDiscoverServices CALLED") 56 | 57 | guard let service = peripheral.services?.first else { 58 | return 59 | } 60 | 61 | let uuids = [CBUUID(string: Characteristic.Unlock.rawValue), 62 | CBUUID(string: Characteristic.Signature.rawValue)] 63 | 64 | peripheral.discoverCharacteristics(uuids, forService: service) 65 | } 66 | 67 | func peripheral(peripheral: CBPeripheral, 68 | didDiscoverCharacteristicsForService service: CBService, 69 | error: NSError?) { 70 | 71 | log("#didDiscoverCharacteristicsForService CALLED") 72 | 73 | guard let chars = service.characteristics where chars.count == 2 else { 74 | self.denyAccess() 75 | self.error("#didDiscoverCharacteristicsForService - invalid count") 76 | return 77 | } 78 | 79 | guard challenge?.characters.count == 64 else { 80 | self.denyAccess() 81 | self.error("#didDiscoverCharacteristicsForService - invalidi challenge") 82 | return 83 | } 84 | 85 | (challengeChar, signatureChar) = (chars[0], chars[1]) 86 | 87 | peripheral.writeValue(challenge!.dataWithHexString(), 88 | forCharacteristic: challengeChar!, 89 | type: .WithResponse) 90 | 91 | let now = dispatch_time_t(DISPATCH_TIME_NOW) 92 | let delay = 4 * Int64(NSEC_PER_SEC) 93 | 94 | dispatch_after(dispatch_time(now, delay), dispatch_get_main_queue()) { 95 | peripheral.readValueForCharacteristic(self.signatureChar!) 96 | } 97 | } 98 | 99 | func peripheral(peripheral: CBPeripheral, 100 | didUpdateValueForCharacteristic characteristic: CBCharacteristic, 101 | error: NSError?) { 102 | log("#didUpdateValueForCharacteristic CALLED") 103 | 104 | guard let signature = characteristic.value?.hexadecimalString else { 105 | self.denyAccess() 106 | return 107 | } 108 | 109 | sendVerificationRequestWith(signature) 110 | } 111 | } 112 | 113 | extension ViewController { 114 | private func sendChallengeRequestFor(peripheral: CBPeripheral) { 115 | Network.requestChallenge(identity!) { response in 116 | switch response.result { 117 | case .Success(let value): 118 | self.log("#requestChallenge") 119 | 120 | guard let data = value as? [String : AnyObject] else { 121 | self.denyAccess() 122 | return 123 | } 124 | 125 | guard data["reason"] == nil else { 126 | self.denyAccess() 127 | self.error("#requestChallenge - FAILED: \(value["reason"])") 128 | return 129 | } 130 | 131 | guard let chal = data["challenge"] as? String else { 132 | self.denyAccess() 133 | self.error("#requestChallenge - NO CHALLENGE)") 134 | return 135 | } 136 | 137 | self.challenge = chal 138 | self.centralManager?.connectPeripheral(peripheral, options: nil) 139 | 140 | case .Failure(let error): 141 | self.error("#requestChallenge - \(error)") 142 | self.denyAccess() 143 | } 144 | } 145 | } 146 | 147 | private func sendVerificationRequestWith(signature: String) { 148 | Network.sendVerification(identity!, 149 | challenge: challenge!, 150 | signature: signature) { response in 151 | 152 | switch response.result { 153 | case .Success(let data): 154 | guard let verified = data["verified"] as? Bool else { 155 | self.error("#didUpdateValueForCharacteristic - VERIFIED FALSE)") 156 | self.denyAccess() 157 | return 158 | } 159 | 160 | if verified { 161 | self.success("VERIFIED - OPEN") 162 | self.grantAccess() 163 | } else { 164 | self.denyAccess() 165 | self.log("identity: \(self.identity!)") 166 | self.log("challenge: \(self.challenge!)") 167 | self.log("signature: \(signature)") 168 | } 169 | 170 | case .Failure(let error): 171 | self.error("#didUpdateValueForCharacteristic - \(error)") 172 | self.denyAccess() 173 | } 174 | } 175 | } 176 | 177 | private func grantAccess() { 178 | done = true 179 | DemoState.grantAccess() 180 | centralManager?.cancelPeripheralConnection(currentPeripheral!) 181 | updateLastSeenTime() 182 | startOutOfRangeTimer() 183 | } 184 | 185 | private func denyAccess() { 186 | done = true 187 | DemoState.rejectAccess() 188 | centralManager?.cancelPeripheralConnection(currentPeripheral!) 189 | updateLastSeenTime() 190 | startOutOfRangeTimer() 191 | } 192 | } -------------------------------------------------------------------------------- /drone-demo-controller/ViewController+ExternalController.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension ViewController { 4 | struct DemoState { 5 | static func standBy() { 6 | Network.changeLights(to: .StandBy) 7 | Network.closeDoor() 8 | } 9 | 10 | static func startProcessing() { 11 | Network.changeLights(to: .Processing) 12 | } 13 | 14 | static func grantAccess() { 15 | Network.changeLights(to: .Granted) 16 | Network.openDoor() 17 | } 18 | 19 | static func rejectAccess() { 20 | Network.changeLights(to: .Refused) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /drone-demo-controller/ViewController+Logging.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | 4 | private let red = UIColor(red: 0.85, green: 0.33, blue: 0.30, alpha: 1) 5 | private let gray = UIColor(red: 0.82, green: 0.82, blue: 0.82, alpha: 1) 6 | private let green = UIColor(red: 0.59, green: 0.84, blue: 0.67, alpha: 1) 7 | 8 | extension ViewController { 9 | func log(string: String) { 10 | message(string, color: gray) 11 | } 12 | 13 | func success(string: String) { 14 | message(string, color: green) 15 | } 16 | 17 | func error(string: String) { 18 | message(string, color: red) 19 | } 20 | 21 | private func message(string: String, color: UIColor) { 22 | let attributedOptions = [NSForegroundColorAttributeName : color] 23 | let message = "\(getTime()) \(string)" 24 | 25 | let attributedString = NSAttributedString(string: "\(message)\n", 26 | attributes: attributedOptions) 27 | 28 | history.appendAttributedString(attributedString) 29 | logView.attributedText = history 30 | 31 | let bottom = logView.attributedText.length - 1 32 | logView.scrollRangeToVisible(NSMakeRange(bottom, 1)) 33 | } 34 | 35 | private func getTime() -> String { 36 | let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian) 37 | let units: NSCalendarUnit = [.Hour, .Minute, .Second] 38 | 39 | guard let comps = calendar?.components(units, fromDate: NSDate()) else { 40 | return "" 41 | } 42 | 43 | return "[\(comps.hour):\(comps.minute):\(comps.second)]" 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /drone-demo-controller/ViewController+OutOfRange.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension ViewController { 4 | func startOutOfRangeTimer() { 5 | lastTimeSeenTimer = NSTimer.scheduledTimerWithTimeInterval( 6 | 2.0, 7 | target: self, 8 | selector: #selector(checkIfTagOutOfRange), 9 | userInfo: nil, 10 | repeats: true 11 | ) 12 | } 13 | 14 | func updateLastSeenTime() { 15 | lastTimeSeen = NSDate() 16 | } 17 | 18 | func checkIfTagOutOfRange() { 19 | let timeUntilOutOfRange = 20.0 20 | let now = NSDate() 21 | let diff = Double(now.timeIntervalSinceDate(lastTimeSeen!)) 22 | 23 | if diff > timeUntilOutOfRange { 24 | self.success("out of range") 25 | resetDemo() 26 | } 27 | } 28 | 29 | private func resetDemo() { 30 | DemoState.standBy() 31 | 32 | lastTimeSeenTimer?.invalidate() 33 | lastTimeSeenTimer = nil 34 | 35 | challenge = nil 36 | identity = nil 37 | done = false 38 | currentPeripheral = nil 39 | } 40 | } -------------------------------------------------------------------------------- /drone-demo-controller/ViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import CoreBluetooth 3 | 4 | class ViewController: UIViewController, CBCentralManagerDelegate, CBPeripheralDelegate { 5 | @IBOutlet weak var logView: UITextView! 6 | internal var history: NSMutableAttributedString = NSMutableAttributedString() 7 | 8 | internal var centralManager: CBCentralManager? 9 | internal var challengeChar: CBCharacteristic? 10 | internal var signatureChar: CBCharacteristic? 11 | internal var authStateChar: CBCharacteristic? 12 | internal var currentPeripheral: CBPeripheral? 13 | 14 | internal var challenge: String? 15 | internal var identity: String? 16 | 17 | internal var lastTimeSeen: NSDate? 18 | internal var lastTimeSeenTimer: NSTimer? 19 | 20 | internal var done = false 21 | 22 | override func viewDidLoad() { 23 | Network.configureLightIP { 24 | self.success("Welcome to the Chronicled Drone Demo") 25 | 26 | DemoState.standBy() 27 | 28 | let now = dispatch_time_t(DISPATCH_TIME_NOW) 29 | let delay = 10 * Int64(NSEC_PER_SEC) 30 | 31 | dispatch_after(dispatch_time(now, delay), dispatch_get_main_queue()) { 32 | self.log("Let's Begin") 33 | self.centralManager = CBCentralManager(delegate: self, 34 | queue: dispatch_get_main_queue()) 35 | } 36 | } 37 | } 38 | } --------------------------------------------------------------------------------