├── .gitignore ├── .swift-version ├── LICENSE ├── Nuimo.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Nuimo ├── Info.plist └── Nuimo.h ├── NuimoSwift.podspec ├── NuimoTests ├── Info.plist └── NuimoTests.swift ├── README.md └── SDK ├── BLEDevice.swift ├── BLEDiscoveryManager.swift ├── NuimoBluetoothController.swift ├── NuimoBuiltInLEDMatrix.swift ├── NuimoController.swift ├── NuimoDiscoveryManager.swift ├── NuimoErrorDomain.swift ├── NuimoGesture.swift ├── NuimoGestureEvent.swift ├── NuimoLEDMatrix.swift └── NuimoSwift.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | 3 | build/ 4 | xcuserdata 5 | *.pbxuser 6 | *.xccheckout 7 | *.xcuserstate 8 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Senic GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Nuimo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 46427FFC1DB8B04000A4C354 /* NuimoBuiltInLEDMatrix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46427FFB1DB8B04000A4C354 /* NuimoBuiltInLEDMatrix.swift */; }; 11 | 46FB8E871C1B06EA00ED88DE /* Nuimo.h in Headers */ = {isa = PBXBuildFile; fileRef = 46FB8E861C1B06EA00ED88DE /* Nuimo.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | 46FB8E8E1C1B06EA00ED88DE /* Nuimo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46FB8E831C1B06EA00ED88DE /* Nuimo.framework */; }; 13 | 46FB8E931C1B06EA00ED88DE /* NuimoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46FB8E921C1B06EA00ED88DE /* NuimoTests.swift */; }; 14 | 46FB8EA81C1B086600ED88DE /* BLEDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46FB8E9E1C1B086600ED88DE /* BLEDevice.swift */; }; 15 | 46FB8EA91C1B086600ED88DE /* BLEDiscoveryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46FB8E9F1C1B086600ED88DE /* BLEDiscoveryManager.swift */; }; 16 | 46FB8EAA1C1B086600ED88DE /* NuimoBluetoothController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46FB8EA01C1B086600ED88DE /* NuimoBluetoothController.swift */; }; 17 | 46FB8EAB1C1B086600ED88DE /* NuimoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46FB8EA11C1B086600ED88DE /* NuimoController.swift */; }; 18 | 46FB8EAC1C1B086600ED88DE /* NuimoDiscoveryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46FB8EA21C1B086600ED88DE /* NuimoDiscoveryManager.swift */; }; 19 | 46FB8EAD1C1B086600ED88DE /* NuimoGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46FB8EA31C1B086600ED88DE /* NuimoGesture.swift */; }; 20 | 46FB8EAE1C1B086600ED88DE /* NuimoGestureEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46FB8EA41C1B086600ED88DE /* NuimoGestureEvent.swift */; }; 21 | 46FB8EAF1C1B086600ED88DE /* NuimoLEDMatrix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46FB8EA51C1B086600ED88DE /* NuimoLEDMatrix.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXContainerItemProxy section */ 25 | 46FB8E8F1C1B06EA00ED88DE /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = 46FB8E7A1C1B06EA00ED88DE /* Project object */; 28 | proxyType = 1; 29 | remoteGlobalIDString = 46FB8E821C1B06EA00ED88DE; 30 | remoteInfo = Nuimo; 31 | }; 32 | /* End PBXContainerItemProxy section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 3CC3C9C54E9948600B67A0F4 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; 36 | 46427FFB1DB8B04000A4C354 /* NuimoBuiltInLEDMatrix.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NuimoBuiltInLEDMatrix.swift; sourceTree = ""; }; 37 | 46FB8E831C1B06EA00ED88DE /* Nuimo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Nuimo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 46FB8E861C1B06EA00ED88DE /* Nuimo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Nuimo.h; sourceTree = ""; }; 39 | 46FB8E881C1B06EA00ED88DE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 46FB8E8D1C1B06EA00ED88DE /* NuimoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NuimoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 46FB8E921C1B06EA00ED88DE /* NuimoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NuimoTests.swift; sourceTree = ""; }; 42 | 46FB8E941C1B06EA00ED88DE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | 46FB8E9E1C1B086600ED88DE /* BLEDevice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BLEDevice.swift; sourceTree = ""; }; 44 | 46FB8E9F1C1B086600ED88DE /* BLEDiscoveryManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BLEDiscoveryManager.swift; sourceTree = ""; }; 45 | 46FB8EA01C1B086600ED88DE /* NuimoBluetoothController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NuimoBluetoothController.swift; sourceTree = ""; }; 46 | 46FB8EA11C1B086600ED88DE /* NuimoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NuimoController.swift; sourceTree = ""; }; 47 | 46FB8EA21C1B086600ED88DE /* NuimoDiscoveryManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NuimoDiscoveryManager.swift; sourceTree = ""; }; 48 | 46FB8EA31C1B086600ED88DE /* NuimoGesture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NuimoGesture.swift; sourceTree = ""; }; 49 | 46FB8EA41C1B086600ED88DE /* NuimoGestureEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NuimoGestureEvent.swift; sourceTree = ""; }; 50 | 46FB8EA51C1B086600ED88DE /* NuimoLEDMatrix.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NuimoLEDMatrix.swift; sourceTree = ""; }; 51 | 6F56754F7BEEA5425C497D7F /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 8F8EEB89589377F962BD46B2 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; 53 | /* End PBXFileReference section */ 54 | 55 | /* Begin PBXFrameworksBuildPhase section */ 56 | 46FB8E8A1C1B06EA00ED88DE /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | 46FB8E8E1C1B06EA00ED88DE /* Nuimo.framework in Frameworks */, 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | 46FB8E791C1B06EA00ED88DE = { 68 | isa = PBXGroup; 69 | children = ( 70 | 46FB8E851C1B06EA00ED88DE /* Nuimo */, 71 | 46FB8E911C1B06EA00ED88DE /* NuimoTests */, 72 | 46FB8E9D1C1B086600ED88DE /* SDK */, 73 | 46FB8E841C1B06EA00ED88DE /* Products */, 74 | 8F99C384716A98A956BDE19D /* Pods */, 75 | B0B45EDFF78310BEDB8228C8 /* Frameworks */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | 46FB8E841C1B06EA00ED88DE /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 46FB8E831C1B06EA00ED88DE /* Nuimo.framework */, 83 | 46FB8E8D1C1B06EA00ED88DE /* NuimoTests.xctest */, 84 | ); 85 | name = Products; 86 | sourceTree = ""; 87 | }; 88 | 46FB8E851C1B06EA00ED88DE /* Nuimo */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 46FB8E861C1B06EA00ED88DE /* Nuimo.h */, 92 | 46FB8E881C1B06EA00ED88DE /* Info.plist */, 93 | ); 94 | path = Nuimo; 95 | sourceTree = ""; 96 | }; 97 | 46FB8E911C1B06EA00ED88DE /* NuimoTests */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 46FB8E921C1B06EA00ED88DE /* NuimoTests.swift */, 101 | 46FB8E941C1B06EA00ED88DE /* Info.plist */, 102 | ); 103 | path = NuimoTests; 104 | sourceTree = ""; 105 | }; 106 | 46FB8E9D1C1B086600ED88DE /* SDK */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 46FB8E9E1C1B086600ED88DE /* BLEDevice.swift */, 110 | 46FB8E9F1C1B086600ED88DE /* BLEDiscoveryManager.swift */, 111 | 46FB8EA01C1B086600ED88DE /* NuimoBluetoothController.swift */, 112 | 46427FFB1DB8B04000A4C354 /* NuimoBuiltInLEDMatrix.swift */, 113 | 46FB8EA11C1B086600ED88DE /* NuimoController.swift */, 114 | 46FB8EA21C1B086600ED88DE /* NuimoDiscoveryManager.swift */, 115 | 46FB8EA31C1B086600ED88DE /* NuimoGesture.swift */, 116 | 46FB8EA41C1B086600ED88DE /* NuimoGestureEvent.swift */, 117 | 46FB8EA51C1B086600ED88DE /* NuimoLEDMatrix.swift */, 118 | ); 119 | path = SDK; 120 | sourceTree = ""; 121 | }; 122 | 8F99C384716A98A956BDE19D /* Pods */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 3CC3C9C54E9948600B67A0F4 /* Pods.debug.xcconfig */, 126 | 8F8EEB89589377F962BD46B2 /* Pods.release.xcconfig */, 127 | ); 128 | name = Pods; 129 | sourceTree = ""; 130 | }; 131 | B0B45EDFF78310BEDB8228C8 /* Frameworks */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 6F56754F7BEEA5425C497D7F /* Pods.framework */, 135 | ); 136 | name = Frameworks; 137 | sourceTree = ""; 138 | }; 139 | /* End PBXGroup section */ 140 | 141 | /* Begin PBXHeadersBuildPhase section */ 142 | 46FB8E801C1B06EA00ED88DE /* Headers */ = { 143 | isa = PBXHeadersBuildPhase; 144 | buildActionMask = 2147483647; 145 | files = ( 146 | 46FB8E871C1B06EA00ED88DE /* Nuimo.h in Headers */, 147 | ); 148 | runOnlyForDeploymentPostprocessing = 0; 149 | }; 150 | /* End PBXHeadersBuildPhase section */ 151 | 152 | /* Begin PBXNativeTarget section */ 153 | 46FB8E821C1B06EA00ED88DE /* Nuimo */ = { 154 | isa = PBXNativeTarget; 155 | buildConfigurationList = 46FB8E971C1B06EA00ED88DE /* Build configuration list for PBXNativeTarget "Nuimo" */; 156 | buildPhases = ( 157 | 46FB8E7E1C1B06EA00ED88DE /* Sources */, 158 | 46FB8E801C1B06EA00ED88DE /* Headers */, 159 | 46FB8E811C1B06EA00ED88DE /* Resources */, 160 | ); 161 | buildRules = ( 162 | ); 163 | dependencies = ( 164 | ); 165 | name = Nuimo; 166 | productName = Nuimo; 167 | productReference = 46FB8E831C1B06EA00ED88DE /* Nuimo.framework */; 168 | productType = "com.apple.product-type.framework"; 169 | }; 170 | 46FB8E8C1C1B06EA00ED88DE /* NuimoTests */ = { 171 | isa = PBXNativeTarget; 172 | buildConfigurationList = 46FB8E9A1C1B06EA00ED88DE /* Build configuration list for PBXNativeTarget "NuimoTests" */; 173 | buildPhases = ( 174 | 46FB8E891C1B06EA00ED88DE /* Sources */, 175 | 46FB8E8A1C1B06EA00ED88DE /* Frameworks */, 176 | 46FB8E8B1C1B06EA00ED88DE /* Resources */, 177 | ); 178 | buildRules = ( 179 | ); 180 | dependencies = ( 181 | 46FB8E901C1B06EA00ED88DE /* PBXTargetDependency */, 182 | ); 183 | name = NuimoTests; 184 | productName = NuimoTests; 185 | productReference = 46FB8E8D1C1B06EA00ED88DE /* NuimoTests.xctest */; 186 | productType = "com.apple.product-type.bundle.unit-test"; 187 | }; 188 | /* End PBXNativeTarget section */ 189 | 190 | /* Begin PBXProject section */ 191 | 46FB8E7A1C1B06EA00ED88DE /* Project object */ = { 192 | isa = PBXProject; 193 | attributes = { 194 | LastSwiftUpdateCheck = 0720; 195 | LastUpgradeCheck = 0720; 196 | ORGANIZATIONNAME = senic; 197 | TargetAttributes = { 198 | 46FB8E821C1B06EA00ED88DE = { 199 | CreatedOnToolsVersion = 7.2; 200 | DevelopmentTeam = 326587HQT3; 201 | LastSwiftMigration = 0800; 202 | }; 203 | 46FB8E8C1C1B06EA00ED88DE = { 204 | CreatedOnToolsVersion = 7.2; 205 | LastSwiftMigration = 0800; 206 | }; 207 | }; 208 | }; 209 | buildConfigurationList = 46FB8E7D1C1B06EA00ED88DE /* Build configuration list for PBXProject "Nuimo" */; 210 | compatibilityVersion = "Xcode 3.2"; 211 | developmentRegion = English; 212 | hasScannedForEncodings = 0; 213 | knownRegions = ( 214 | en, 215 | ); 216 | mainGroup = 46FB8E791C1B06EA00ED88DE; 217 | productRefGroup = 46FB8E841C1B06EA00ED88DE /* Products */; 218 | projectDirPath = ""; 219 | projectRoot = ""; 220 | targets = ( 221 | 46FB8E821C1B06EA00ED88DE /* Nuimo */, 222 | 46FB8E8C1C1B06EA00ED88DE /* NuimoTests */, 223 | ); 224 | }; 225 | /* End PBXProject section */ 226 | 227 | /* Begin PBXResourcesBuildPhase section */ 228 | 46FB8E811C1B06EA00ED88DE /* Resources */ = { 229 | isa = PBXResourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | ); 233 | runOnlyForDeploymentPostprocessing = 0; 234 | }; 235 | 46FB8E8B1C1B06EA00ED88DE /* Resources */ = { 236 | isa = PBXResourcesBuildPhase; 237 | buildActionMask = 2147483647; 238 | files = ( 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | /* End PBXResourcesBuildPhase section */ 243 | 244 | /* Begin PBXSourcesBuildPhase section */ 245 | 46FB8E7E1C1B06EA00ED88DE /* Sources */ = { 246 | isa = PBXSourcesBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | 46FB8EAD1C1B086600ED88DE /* NuimoGesture.swift in Sources */, 250 | 46FB8EAF1C1B086600ED88DE /* NuimoLEDMatrix.swift in Sources */, 251 | 46FB8EAB1C1B086600ED88DE /* NuimoController.swift in Sources */, 252 | 46FB8EAA1C1B086600ED88DE /* NuimoBluetoothController.swift in Sources */, 253 | 46427FFC1DB8B04000A4C354 /* NuimoBuiltInLEDMatrix.swift in Sources */, 254 | 46FB8EAC1C1B086600ED88DE /* NuimoDiscoveryManager.swift in Sources */, 255 | 46FB8EA91C1B086600ED88DE /* BLEDiscoveryManager.swift in Sources */, 256 | 46FB8EAE1C1B086600ED88DE /* NuimoGestureEvent.swift in Sources */, 257 | 46FB8EA81C1B086600ED88DE /* BLEDevice.swift in Sources */, 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | }; 261 | 46FB8E891C1B06EA00ED88DE /* Sources */ = { 262 | isa = PBXSourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | 46FB8E931C1B06EA00ED88DE /* NuimoTests.swift in Sources */, 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | }; 269 | /* End PBXSourcesBuildPhase section */ 270 | 271 | /* Begin PBXTargetDependency section */ 272 | 46FB8E901C1B06EA00ED88DE /* PBXTargetDependency */ = { 273 | isa = PBXTargetDependency; 274 | target = 46FB8E821C1B06EA00ED88DE /* Nuimo */; 275 | targetProxy = 46FB8E8F1C1B06EA00ED88DE /* PBXContainerItemProxy */; 276 | }; 277 | /* End PBXTargetDependency section */ 278 | 279 | /* Begin XCBuildConfiguration section */ 280 | 46FB8E951C1B06EA00ED88DE /* Debug */ = { 281 | isa = XCBuildConfiguration; 282 | buildSettings = { 283 | ALWAYS_SEARCH_USER_PATHS = NO; 284 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 285 | CLANG_CXX_LIBRARY = "libc++"; 286 | CLANG_ENABLE_MODULES = YES; 287 | CLANG_ENABLE_OBJC_ARC = YES; 288 | CLANG_WARN_BOOL_CONVERSION = YES; 289 | CLANG_WARN_CONSTANT_CONVERSION = YES; 290 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 291 | CLANG_WARN_EMPTY_BODY = YES; 292 | CLANG_WARN_ENUM_CONVERSION = YES; 293 | CLANG_WARN_INT_CONVERSION = YES; 294 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 295 | CLANG_WARN_UNREACHABLE_CODE = YES; 296 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 297 | CODE_SIGN_IDENTITY = "-"; 298 | COPY_PHASE_STRIP = NO; 299 | CURRENT_PROJECT_VERSION = 1; 300 | DEBUG_INFORMATION_FORMAT = dwarf; 301 | ENABLE_STRICT_OBJC_MSGSEND = YES; 302 | ENABLE_TESTABILITY = YES; 303 | GCC_C_LANGUAGE_STANDARD = gnu99; 304 | GCC_DYNAMIC_NO_PIC = NO; 305 | GCC_NO_COMMON_BLOCKS = YES; 306 | GCC_OPTIMIZATION_LEVEL = 0; 307 | GCC_PREPROCESSOR_DEFINITIONS = ( 308 | "DEBUG=1", 309 | "$(inherited)", 310 | ); 311 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 312 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 313 | GCC_WARN_UNDECLARED_SELECTOR = YES; 314 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 315 | GCC_WARN_UNUSED_FUNCTION = YES; 316 | GCC_WARN_UNUSED_VARIABLE = YES; 317 | MACOSX_DEPLOYMENT_TARGET = 10.11; 318 | MTL_ENABLE_DEBUG_INFO = YES; 319 | ONLY_ACTIVE_ARCH = YES; 320 | SDKROOT = macosx; 321 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator"; 322 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 323 | VALID_ARCHS = "i386 x86_64 arm64 armv7s armv7"; 324 | VERSIONING_SYSTEM = "apple-generic"; 325 | VERSION_INFO_PREFIX = ""; 326 | }; 327 | name = Debug; 328 | }; 329 | 46FB8E961C1B06EA00ED88DE /* Release */ = { 330 | isa = XCBuildConfiguration; 331 | buildSettings = { 332 | ALWAYS_SEARCH_USER_PATHS = NO; 333 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 334 | CLANG_CXX_LIBRARY = "libc++"; 335 | CLANG_ENABLE_MODULES = YES; 336 | CLANG_ENABLE_OBJC_ARC = YES; 337 | CLANG_WARN_BOOL_CONVERSION = YES; 338 | CLANG_WARN_CONSTANT_CONVERSION = YES; 339 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 340 | CLANG_WARN_EMPTY_BODY = YES; 341 | CLANG_WARN_ENUM_CONVERSION = YES; 342 | CLANG_WARN_INT_CONVERSION = YES; 343 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 344 | CLANG_WARN_UNREACHABLE_CODE = YES; 345 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 346 | CODE_SIGN_IDENTITY = "-"; 347 | COPY_PHASE_STRIP = NO; 348 | CURRENT_PROJECT_VERSION = 1; 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 | MACOSX_DEPLOYMENT_TARGET = 10.11; 361 | MTL_ENABLE_DEBUG_INFO = NO; 362 | SDKROOT = macosx; 363 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator"; 364 | VALID_ARCHS = "i386 x86_64 arm64 armv7s armv7"; 365 | VERSIONING_SYSTEM = "apple-generic"; 366 | VERSION_INFO_PREFIX = ""; 367 | }; 368 | name = Release; 369 | }; 370 | 46FB8E981C1B06EA00ED88DE /* Debug */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | COMBINE_HIDPI_IMAGES = YES; 374 | DEFINES_MODULE = YES; 375 | DYLIB_COMPATIBILITY_VERSION = 1; 376 | DYLIB_CURRENT_VERSION = 1; 377 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 378 | FRAMEWORK_VERSION = A; 379 | INFOPLIST_FILE = Nuimo/Info.plist; 380 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 381 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 382 | PRODUCT_BUNDLE_IDENTIFIER = com.senic.Nuimo; 383 | PRODUCT_NAME = "$(TARGET_NAME)"; 384 | SKIP_INSTALL = YES; 385 | SWIFT_VERSION = 2.3; 386 | }; 387 | name = Debug; 388 | }; 389 | 46FB8E991C1B06EA00ED88DE /* Release */ = { 390 | isa = XCBuildConfiguration; 391 | buildSettings = { 392 | COMBINE_HIDPI_IMAGES = YES; 393 | DEFINES_MODULE = YES; 394 | DYLIB_COMPATIBILITY_VERSION = 1; 395 | DYLIB_CURRENT_VERSION = 1; 396 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 397 | FRAMEWORK_VERSION = A; 398 | INFOPLIST_FILE = Nuimo/Info.plist; 399 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 400 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 401 | PRODUCT_BUNDLE_IDENTIFIER = com.senic.Nuimo; 402 | PRODUCT_NAME = "$(TARGET_NAME)"; 403 | SKIP_INSTALL = YES; 404 | SWIFT_VERSION = 2.3; 405 | }; 406 | name = Release; 407 | }; 408 | 46FB8E9B1C1B06EA00ED88DE /* Debug */ = { 409 | isa = XCBuildConfiguration; 410 | buildSettings = { 411 | COMBINE_HIDPI_IMAGES = YES; 412 | FRAMEWORK_SEARCH_PATHS = ( 413 | "$(SDKROOT)", 414 | "$(inherited)", 415 | ); 416 | INFOPLIST_FILE = NuimoTests/Info.plist; 417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks @loader_path/Frameworks @executable_path/Frameworks "; 418 | PRODUCT_BUNDLE_IDENTIFIER = com.senic.NuimoTests; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | SWIFT_VERSION = 2.3; 421 | }; 422 | name = Debug; 423 | }; 424 | 46FB8E9C1C1B06EA00ED88DE /* Release */ = { 425 | isa = XCBuildConfiguration; 426 | buildSettings = { 427 | COMBINE_HIDPI_IMAGES = YES; 428 | FRAMEWORK_SEARCH_PATHS = ( 429 | "$(SDKROOT)", 430 | "$(inherited)", 431 | ); 432 | INFOPLIST_FILE = NuimoTests/Info.plist; 433 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks @loader_path/Frameworks @executable_path/Frameworks "; 434 | PRODUCT_BUNDLE_IDENTIFIER = com.senic.NuimoTests; 435 | PRODUCT_NAME = "$(TARGET_NAME)"; 436 | SWIFT_VERSION = 2.3; 437 | }; 438 | name = Release; 439 | }; 440 | /* End XCBuildConfiguration section */ 441 | 442 | /* Begin XCConfigurationList section */ 443 | 46FB8E7D1C1B06EA00ED88DE /* Build configuration list for PBXProject "Nuimo" */ = { 444 | isa = XCConfigurationList; 445 | buildConfigurations = ( 446 | 46FB8E951C1B06EA00ED88DE /* Debug */, 447 | 46FB8E961C1B06EA00ED88DE /* Release */, 448 | ); 449 | defaultConfigurationIsVisible = 0; 450 | defaultConfigurationName = Release; 451 | }; 452 | 46FB8E971C1B06EA00ED88DE /* Build configuration list for PBXNativeTarget "Nuimo" */ = { 453 | isa = XCConfigurationList; 454 | buildConfigurations = ( 455 | 46FB8E981C1B06EA00ED88DE /* Debug */, 456 | 46FB8E991C1B06EA00ED88DE /* Release */, 457 | ); 458 | defaultConfigurationIsVisible = 0; 459 | defaultConfigurationName = Release; 460 | }; 461 | 46FB8E9A1C1B06EA00ED88DE /* Build configuration list for PBXNativeTarget "NuimoTests" */ = { 462 | isa = XCConfigurationList; 463 | buildConfigurations = ( 464 | 46FB8E9B1C1B06EA00ED88DE /* Debug */, 465 | 46FB8E9C1C1B06EA00ED88DE /* Release */, 466 | ); 467 | defaultConfigurationIsVisible = 0; 468 | defaultConfigurationName = Release; 469 | }; 470 | /* End XCConfigurationList section */ 471 | }; 472 | rootObject = 46FB8E7A1C1B06EA00ED88DE /* Project object */; 473 | } 474 | -------------------------------------------------------------------------------- /Nuimo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Nuimo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2015 senic. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Nuimo/Nuimo.h: -------------------------------------------------------------------------------- 1 | // 2 | // Nuimo.h 3 | // Nuimo 4 | // 5 | // Created by Lars Blumberg on 12/11/15. 6 | // Copyright © 2015 senic. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Nuimo. 12 | FOUNDATION_EXPORT double NuimoVersionNumber; 13 | 14 | //! Project version string for Nuimo. 15 | FOUNDATION_EXPORT const unsigned char NuimoVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /NuimoSwift.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "NuimoSwift" 3 | s.version = "0.8.0" 4 | s.summary = "Swift library for connecting and communicating with Senic's Nuimo controllers" 5 | s.description = <<-DESC 6 | Swift library for connecting and communicating with Senic's Nuimo controllers 7 | * Discover and connect Nuimo controllers via bluetooth low energy or websockets 8 | * Receive user gestures from Nuimo controllers 9 | * Send LED matrices to Nuimo controllers 10 | DESC 11 | s.documentation_url = 'https://github.com/getSenic/nuimo-swift' 12 | s.homepage = "http://senic.com" 13 | s.license = { :type => "MIT", :file => "LICENSE" } 14 | 15 | s.author = { "Lars Blumberg (Senic GmbH)" => "lars@senic.com" } 16 | s.social_media_url = "http://twitter.com/heysenic" 17 | s.ios.deployment_target = "8.0" 18 | s.osx.deployment_target = "10.10" 19 | s.tvos.deployment_target = "9.0" 20 | 21 | s.source = { :git => "https://github.com/getSenic/nuimo-swift.git", :tag => "#{s.version}" } 22 | s.source_files = "SDK/*.swift" 23 | s.framework = 'CoreBluetooth' 24 | end 25 | -------------------------------------------------------------------------------- /NuimoTests/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 | -------------------------------------------------------------------------------- /NuimoTests/NuimoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NuimoTests.swift 3 | // NuimoTests 4 | // 5 | // Created by Lars Blumberg on 12/11/15. 6 | // Copyright © 2015 senic. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import CoreBluetooth 11 | @testable import Nuimo 12 | 13 | class NuimoTests: XCTestCase { 14 | 15 | override func setUp() { 16 | super.setUp() 17 | continueAfterFailure = false 18 | } 19 | 20 | func testDiscoveryManagerDiscoversNuimoController() { 21 | let expectation = expectationWithDescription("Discovery manager should discover nuimo controller") 22 | let discovery = NuimoDiscoveryManager() 23 | discovery.delegate = NuimoDiscoveryDelegateClosures(onDiscoverController: { _ in 24 | discovery.stopDiscovery() 25 | expectation.fulfill() 26 | }) 27 | discovery.startDiscovery() 28 | waitForExpectationsWithTimeout(10.0) {_ in discovery.stopDiscovery() } 29 | } 30 | 31 | func testDiscoveryManagerDiscoversSameNuimoControllerOnlyOnce() { 32 | continueAfterFailure = false 33 | let expectation = expectationWithDescription("Discovery manager should discovery same nuimo controller only once") 34 | var controllers = [String]() 35 | let discovery = NuimoDiscoveryManager() 36 | discovery.delegate = NuimoDiscoveryDelegateClosures(onDiscoverController: { 37 | print("Found \($0.uuid)") 38 | XCTAssertFalse(controllers.contains($0.uuid)) 39 | controllers.append($0.uuid) 40 | }) 41 | discovery.startDiscovery() 42 | after(19.0) { 43 | expectation.fulfill() 44 | } 45 | waitForExpectationsWithTimeout(20.0) {_ in 46 | discovery.stopDiscovery() 47 | } 48 | continueAfterFailure = true 49 | } 50 | 51 | func testNuimoControllerConnects() { 52 | let expectation = expectationWithDescription("Nuimo controller should connect") 53 | let discovery = NuimoDiscoveryManager() 54 | discovery.delegate = NuimoDiscoveryDelegateClosures(onDiscoverController: { controller in 55 | discovery.stopDiscovery() 56 | controller.delegate = NuimoControllerDelegateClosures(onConnect: { 57 | controller.disconnect() 58 | expectation.fulfill() 59 | }) 60 | controller.connect() 61 | }) 62 | discovery.startDiscovery() 63 | waitForExpectationsWithTimeout(10.0) {_ in discovery.stopDiscovery() } 64 | } 65 | 66 | func testNuimoControllerDisplaysLEDMatrix() { 67 | let expectation = expectationWithDescription("Nuimo controller should display LED matrix") 68 | let discovery = NuimoDiscoveryManager() 69 | discovery.delegate = NuimoDiscoveryDelegateClosures(onDiscoverController: { controller in 70 | discovery.stopDiscovery() 71 | controller.delegate = NuimoControllerDelegateClosures( 72 | onConnect: { 73 | controller.writeMatrix(NuimoLEDMatrix(string: String(count: 81, repeatedValue: Character("*"))), interval: 5.0) 74 | }, 75 | onLEDMatrixDisplayed: { 76 | after(2.0) { 77 | controller.disconnect() 78 | expectation.fulfill() 79 | } 80 | } 81 | ) 82 | controller.connect() 83 | }) 84 | discovery.startDiscovery() 85 | waitForExpectationsWithTimeout(10.0) {_ in discovery.stopDiscovery() } 86 | } 87 | 88 | func testNuimoControllerDisplaysLEDMatrixAnimation() { 89 | let expectation = expectationWithDescription("Nuimo controller should display LED matrix animation") 90 | let discovery = NuimoDiscoveryManager() 91 | discovery.delegate = NuimoDiscoveryDelegateClosures(onDiscoverController: { controller in 92 | discovery.stopDiscovery() 93 | var frameIndex = 0 94 | let displayFrame = { 95 | controller.writeMatrix(NuimoLEDMatrix(string: String(count: (frameIndex % 81) + 1, repeatedValue: Character("*"))), interval: 5.0) 96 | } 97 | controller.delegate = NuimoControllerDelegateClosures( 98 | onConnect: { 99 | displayFrame() 100 | }, 101 | onLEDMatrixDisplayed: { 102 | frameIndex += 1 103 | switch (frameIndex) { 104 | case 110: after(2.0) { 105 | controller.disconnect() 106 | expectation.fulfill() 107 | } 108 | default: displayFrame() 109 | } 110 | } 111 | ) 112 | controller.connect() 113 | }) 114 | discovery.startDiscovery() 115 | waitForExpectationsWithTimeout(30.0) {_ in discovery.stopDiscovery() } 116 | } 117 | 118 | func testNuimoControllerSkipsLEDAnimationFramesIfFramesAreSentTooFast() { 119 | let sendFramesRepeatInterval = 0.01 120 | let sendFramesCount = 500 121 | let expectedMinDisplayedFrameCount = 20 122 | let expectedMaxDisplayedFrameCount = 100 123 | var framesDisplayed = 0 124 | let expectation = expectationWithDescription("All animation frames should be sent") 125 | let discovery = NuimoDiscoveryManager() 126 | discovery.delegate = NuimoDiscoveryDelegateClosures(onDiscoverController: { controller in 127 | discovery.stopDiscovery() 128 | controller.delegate = NuimoControllerDelegateClosures( 129 | onConnect: { 130 | var frameIndex = 0 131 | var nextFrame = {} 132 | nextFrame = { 133 | after(sendFramesRepeatInterval) { 134 | guard frameIndex < sendFramesCount else { 135 | controller.disconnect() 136 | expectation.fulfill() 137 | return 138 | } 139 | frameIndex += 1 140 | controller.writeMatrix(NuimoLEDMatrix(string: String(count: frameIndex % 81, repeatedValue: Character("*"))), interval: 2.0) 141 | nextFrame() 142 | } 143 | } 144 | nextFrame() 145 | }, 146 | onLEDMatrixDisplayed: { 147 | framesDisplayed += 1 148 | } 149 | ) 150 | controller.connect() 151 | }) 152 | discovery.startDiscovery() 153 | waitForExpectationsWithTimeout(20.0) {_ in discovery.stopDiscovery() } 154 | 155 | XCTAssert(framesDisplayed >= expectedMinDisplayedFrameCount, "Nuimo controller should display at least \(expectedMinDisplayedFrameCount) animation frames but it displayed only \(framesDisplayed) frames") 156 | XCTAssert(framesDisplayed <= expectedMaxDisplayedFrameCount, "Nuimo controller should not display more than \(expectedMaxDisplayedFrameCount) but it displayed \(framesDisplayed)") 157 | } 158 | } 159 | 160 | func after(delay: NSTimeInterval, block: dispatch_block_t) { 161 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), block) 162 | } 163 | 164 | //TODO: Make this part of the SDK 165 | class NuimoDiscoveryDelegateClosures : NuimoDiscoveryDelegate { 166 | let onDiscoverController: ((NuimoController) -> Void) 167 | 168 | init(onDiscoverController: ((NuimoController) -> Void)) { 169 | self.onDiscoverController = onDiscoverController 170 | } 171 | 172 | @objc func nuimoDiscoveryManager(discovery: NuimoDiscoveryManager, didDiscoverNuimoController controller: NuimoController) { 173 | onDiscoverController(controller) 174 | } 175 | } 176 | 177 | //TODO: Make this part of the SDK 178 | class NuimoControllerDelegateClosures : NuimoControllerDelegate { 179 | let onConnect: (() -> Void)? 180 | let onLEDMatrixDisplayed: (() -> Void)? 181 | 182 | init(onConnect: (() -> Void)? = nil, onLEDMatrixDisplayed: (() -> Void)? = nil) { 183 | self.onConnect = onConnect 184 | self.onLEDMatrixDisplayed = onLEDMatrixDisplayed 185 | } 186 | 187 | @objc func nuimoController(controller: NuimoController, didChangeConnectionState state: NuimoConnectionState, withError error: NSError?) { 188 | if state == .Connected { 189 | onConnect?() 190 | } 191 | } 192 | @objc func nuimoControllerDidDisplayLEDMatrix(controller: NuimoController) { 193 | onLEDMatrixDisplayed?() 194 | } 195 | 196 | @objc func nuimoController(controller: NuimoController, didReceiveGestureEvent event: NuimoGestureEvent) { 197 | } 198 | 199 | @objc func nuimoController(controller: NuimoController, didUpdateBatteryLevel bateryLevel: Int) { 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuimo SDK 2 | 3 | The Nuimo controller is an intuitive controller for your computer and connected smart devices. This document demonstrates how to integrate your iOS and MacOS applications with Nuimo controllers using the Nuimo SDK. The Nuimo SDK supports both applications written in Swift and in Objective-C. 4 | 5 | ## Installation 6 | [The Nuimo Swift SDK is available](https://cocoapods.org/pods/NuimoSwift) through [CocoaPods](https://cocoapods.org/), a very good dependency manager for Swift and Objective-C applications. (If you don't want to use CocoaPods just copy all `.swift` files from folder `SDK` into your Xcode project and skip to the next step.) 7 | 8 | ##### Prepare your project to use CocoaPods 9 | If you haven't set up your project yet to use CocoaPods, please follow these steps first: 10 | 11 | 1. Install CocoaPods itself (if not yet installed). Open a terminal and run: `sudo gem install cocoapods` 12 | 13 | 2. Close Xcode 14 | 15 | 3. Create a file inside your project's root folder named `Podfile` and paste the following content. Make sure to adopt the right platform: 16 | 17 | platform :ios, '8.0' 18 | #Use the following line instead if you're developing for MacOS: 19 | #platform :osx, '10.9' 20 | use_frameworks! 21 | 22 | 4. From now on always open the workspace file `.xcworkspace` in Xcode. Otherwise the just added CocoaPods dependencies won't be available (see next step) 23 | 24 | ##### Add a dependency to the NuimoSwift SDK 25 | Edit your project's `Podfile` to add the following line: 26 | ``` 27 | pod 'NuimoSwift', '~> 0.7.1' 28 | ``` 29 | Then from a terminal within your project's root folder run: 30 | ```bash 31 | pod install 32 | ``` 33 | This should install the Nuimo Swift SDK and add it to your workspace. Now open your project's workspace and start playing around with the Nuimo Swift SDK. Don't forget to import the module `NuimoSwift` where necessary. 34 | 35 | ## Usage 36 | 37 | #### Basic usage 38 | 39 | The Nuimo SDK makes it very easy to connect your iOS and MacOS applications with Nuimo controllers. It only takes three steps and a very few lines of code to discover your Nuimo and receive gesture events: 40 | 41 | 1. Assign a delegate to an instance of `NuimoDiscoveryManager` and call `startDiscovery()`. This will discover Nuimo controllers nearby. 42 | 43 | 2. Receive discovered controllers by implementing the delegate method `nuimoDiscoveryManager:didDiscoverNuimoController:`. Here you can 44 | 1. Set the delegate of the discovered controller 45 | 2. Initiate the Bluetooth connection to the discovered controller by calling `connect()` 46 | 47 | 3. Implement the delegate method `nuimoController:didReceiveGestureEvent:` to access user events performed with the Nuimo controller 48 | 49 | The following code example demonstrates how to discover, connect and receive gesture events from your Nuimo. As you might know, use either `UIViewController` on iOS or `NSViewController` on MacOS systems. 50 | 51 | #### Example code 52 | 53 | ```swift 54 | import NuimoSwift 55 | 56 | class ViewController : UIViewController|NSViewController, NuimoDiscoveryDelegate, NuimoControllerDelegate { 57 | let discovery = NuimoDiscoveryManager.sharedManager 58 | 59 | override func viewDidLoad() { 60 | super.viewDidLoad() 61 | discovery.delegate = self 62 | discovery.startDiscovery() 63 | } 64 | 65 | func nuimoDiscoveryManager(discovery: NuimoDiscoveryManager, didDiscoverNuimoController controller: NuimoController) { 66 | controller.delegate = self 67 | controller.connect() 68 | } 69 | 70 | func nuimoController(controller: NuimoController, didReceiveGestureEvent event: NuimoGestureEvent) { 71 | print("Received event: \(event.gesture.identifier), value: \(event.value)") 72 | } 73 | } 74 | ``` 75 | 76 | #### A ready to checkout MacOS demo application 77 | 78 | We've provided a ready to checkout application that demonstrates discovering, connecting and receiving events from your Nuimo controllers. Simply clone the [Nuimo MacOS demo repository](https://github.com/getSenic/nuimo-swift-demo-osx), open the included Xcode workspace and hit the _Run_ button to execute the application. Before that, make sure that the correct target `NuimoDemoOSX` is selected. 79 | 80 | ## Advanced use cases 81 | The NuimoSwift SDK is much more powerful than the use cases presented above. More details to follow here soon. 82 | 83 | ## Contact & Support 84 | Have questions or suggestions? Drop us a mail at developers@senic.com. We'll be happy to hear from you. 85 | 86 | ## License 87 | The NuimoSwift source code is available under the MIT License. 88 | -------------------------------------------------------------------------------- /SDK/BLEDevice.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BluetoothDevice.swift 3 | // Nuimo 4 | // 5 | // Created by Lars Blumberg on 12/10/15. 6 | // Copyright © 2015 Senic. All rights reserved. 7 | // 8 | // This software may be modified and distributed under the terms 9 | // of the MIT license. See the LICENSE file for details. 10 | 11 | import Foundation 12 | import CoreBluetooth 13 | 14 | /** 15 | Represents a Bluetooth Low Energy (BLE) device. 16 | - Automatically discovers its services when connected 17 | - Automatically discovers its characteristics 18 | - Automatically subscribes for characteristic change notifications 19 | - Automatically updates its reachability 20 | - Automatically reconnects if peripheral becomes reachable again (if `autoReconnect` was set to true) 21 | */ 22 | open class BLEDevice: NSObject { 23 | /// Maximum interval between two advertising packages. If the OS doesn't receive a successive advertisement package after that interval the device is assumed to be unreachable. If not overridden, this interval defaults to `nil`, meaning that the device will never be assumed unreachable, even in case the OS doesn't receive any more advertising packages. 24 | //TODO: Make this an instance variable, so subclasses can update it on the fly if necessary or provide different values for the same device type 25 | open class var maxAdvertisingPackageInterval: TimeInterval? { get { return nil } } 26 | open class var connectionRetryCount: Int { return 0 } 27 | 28 | open var serviceUUIDs: [CBUUID] { get { return [] } } 29 | open var charactericUUIDsForServiceUUID: [CBUUID : [CBUUID]] { get { return [:] } } 30 | open var notificationCharacteristicUUIDs: [CBUUID] { get { return [] } } 31 | 32 | public let discoveryManager: BLEDiscoveryManager 33 | public let uuid: UUID 34 | public private(set) var peripheral: CBPeripheral? 35 | public var centralManager: CBCentralManager { return discoveryManager.centralManager } 36 | public var queue: DispatchQueue { return discoveryManager.queue } 37 | public var isAdvertising: Bool { return !(advertisingTimeoutDispatchWorkItem?.isCancelled ?? true) } 38 | public var isReachable: Bool { 39 | if centralManager.state != .poweredOn { return false } 40 | if peripheral?.state == .connected { return true } 41 | return isAdvertising 42 | } 43 | 44 | private var advertisingTimeoutDispatchWorkItem: DispatchWorkItem? 45 | private var connectionAttempt = 0 46 | private var autoReconnect = false 47 | 48 | public required init(discoveryManager: BLEDiscoveryManager, peripheral: CBPeripheral) { 49 | self.discoveryManager = discoveryManager 50 | self.uuid = peripheral.identifier 51 | super.init() 52 | restore(from: peripheral) 53 | } 54 | 55 | internal func restore(from peripheral: CBPeripheral) { 56 | queue.assertIsDispatching() 57 | self.peripheral = peripheral 58 | peripheral.delegate = self 59 | defer { didUpdateState() } 60 | assumeNotAdvertising() 61 | guard centralManager.state == .poweredOn, peripheral.state == .connected else { return } 62 | discoverServices() 63 | } 64 | 65 | open func didAdvertise(_ advertisementData: [String: Any], RSSI: NSNumber, willReceiveSuccessiveAdvertisingData: Bool) { 66 | queue.assertIsDispatching() 67 | guard peripheral != nil else { return } 68 | guard willReceiveSuccessiveAdvertisingData, let maxAdvertisingPackageInterval = type(of: self).maxAdvertisingPackageInterval else { return } 69 | advertisingTimeoutDispatchWorkItem?.cancel() 70 | advertisingTimeoutDispatchWorkItem = DispatchWorkItem() { [weak self] in self?.didStopAdvertising() } 71 | queue.asyncAfter(deadline: .now() + maxAdvertisingPackageInterval, execute: advertisingTimeoutDispatchWorkItem!) 72 | didUpdateState() 73 | } 74 | 75 | open func didStopAdvertising() { 76 | queue.assertIsDispatching() 77 | advertisingTimeoutDispatchWorkItem?.cancel() 78 | discoveryManager.deviceDidStopAdvertising(self) 79 | didUpdateState() 80 | } 81 | 82 | open func connect(autoReconnect: Bool = false) { 83 | queue.async { 84 | self.autoReconnect = autoReconnect 85 | guard let peripheral = self.peripheral, self.centralManager.state == .poweredOn else { return } 86 | NuimoSwift.DDLogDebug("BLEDevice \(peripheral.identifier.uuidString) connect(auto connect = \(autoReconnect))") 87 | self.connectionAttempt = 0 88 | self.centralManager.connect(peripheral, options: nil) 89 | self.didUpdateState() 90 | } 91 | } 92 | 93 | open func didConnect() { 94 | queue.assertIsDispatching() 95 | guard peripheral != nil else { return } 96 | assumeNotAdvertising() 97 | discoverServices() 98 | didUpdateState() 99 | } 100 | 101 | open func didFailToConnect(error: Error?) { 102 | queue.assertIsDispatching() 103 | guard let peripheral = peripheral else { return } 104 | assumeNotAdvertising() 105 | if connectionAttempt < type(of: self).connectionRetryCount { 106 | connectionAttempt += 1 107 | centralManager.connect(peripheral, options: nil) 108 | } 109 | didUpdateState(error: error) 110 | } 111 | 112 | open func disconnect() { 113 | queue.async { 114 | self.autoReconnect = false 115 | guard let peripheral = self.peripheral else { return } 116 | NuimoSwift.DDLogDebug("BLEDevice \(peripheral.identifier.uuidString) disconnect()") 117 | self.centralManager.cancelPeripheralConnection(peripheral) 118 | } 119 | } 120 | 121 | open func didDisconnect(error: Error?) { 122 | queue.assertIsDispatching() 123 | if autoReconnect { 124 | connect(autoReconnect: true) 125 | } 126 | didUpdateState(error: error) 127 | } 128 | 129 | open func willDiscoverServices() { 130 | } 131 | 132 | private func discoverServices() { 133 | queue.assertIsDispatching() 134 | guard let peripheral = peripheral else { return } 135 | willDiscoverServices() 136 | // Collect any already known service and characteristic (i.e. from device restoring) 137 | peripheral.services?.forEach { 138 | // Notify already discovered services, it will discover their characteristics if not already discovered 139 | self.peripheral(peripheral, didDiscoverServices: nil) 140 | // Notify already discovered characteristics 141 | self.peripheral(peripheral, didDiscoverCharacteristicsFor: $0, error: nil) 142 | } 143 | // Discover not yet discovered services and characteristics 144 | peripheral.discoverServices(serviceUUIDs.filter{ !peripheral.serviceUUIDs.contains($0) }) 145 | } 146 | 147 | open func didUpdateState(error: Error? = nil) { 148 | } 149 | 150 | internal func centralManagerDidUpdateState() { 151 | queue.assertIsDispatching() 152 | switch centralManager.state { 153 | case .poweredOn: 154 | if autoReconnect { 155 | connect(autoReconnect: true) 156 | } 157 | break 158 | case .poweredOff: 159 | break 160 | default: 161 | peripheral = nil 162 | } 163 | didUpdateState() 164 | } 165 | 166 | private func assumeNotAdvertising() { 167 | advertisingTimeoutDispatchWorkItem?.cancel() 168 | } 169 | } 170 | 171 | extension BLEDevice: CBPeripheralDelegate { 172 | open func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { 173 | queue.assertIsDispatching() 174 | peripheral.services? 175 | .flatMap{ service in (service, charactericUUIDsForServiceUUID[service.uuid]?.filter { !service.characteristicUUIDs.contains($0) } ?? [] ) } 176 | .forEach{ peripheral.discoverCharacteristics($0.1, for: $0.0) } 177 | } 178 | 179 | open func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { 180 | queue.assertIsDispatching() 181 | service.characteristics?.forEach{ characteristic in 182 | if notificationCharacteristicUUIDs.contains(characteristic.uuid) { 183 | peripheral.setNotifyValue(true, for: characteristic) 184 | } 185 | } 186 | } 187 | 188 | open func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { 189 | queue.assertIsDispatching() 190 | } 191 | 192 | open func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { 193 | queue.assertIsDispatching() 194 | } 195 | 196 | open func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { 197 | queue.assertIsDispatching() 198 | } 199 | } 200 | 201 | extension CBPeripheral { 202 | var serviceUUIDs: [CBUUID] { return services?.map{ $0.uuid } ?? [] } 203 | 204 | func service(with UUID: CBUUID) -> CBService? { 205 | return services?.filter{ $0.uuid == UUID }.first 206 | } 207 | } 208 | 209 | extension CBService { 210 | var characteristicUUIDs: [CBUUID] { return characteristics?.map{ $0.uuid } ?? [] } 211 | 212 | func characteristic(with UUID: CBUUID) -> CBCharacteristic? { 213 | return characteristics?.filter{ $0.uuid == UUID }.first 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /SDK/BLEDiscoveryManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BLEDiscoveryManager.swift 3 | // Nuimo 4 | // 5 | // Created by Lars Blumberg on 12/10/15. 6 | // Copyright © 2015 Senic. All rights reserved. 7 | // 8 | // This software may be modified and distributed under the terms 9 | // of the MIT license. See the LICENSE file for details. 10 | 11 | import CoreBluetooth 12 | 13 | /** 14 | Allows for easy discovering bluetooth devices. 15 | Automatically re-starts discovery if bluetooth was disabled for a previous discovery. 16 | */ 17 | public class BLEDiscoveryManager: NSObject { 18 | public weak var delegate: BLEDiscoveryManagerDelegate? 19 | public var centralManager: CBCentralManager! { 20 | guard let centralManager = _centralManager else { 21 | print("BLEDiscoveryManager.centralManager is accessed while it is not yet set. This happens when CBCentralManager() calls a delegate function while still instantiating BLEDiscoveryManager. If BLEDiscoveryManager.centralManager is accessed without checking for `nil` the program will consequently crash.") 22 | return nil 23 | } 24 | return centralManager 25 | } 26 | 27 | internal let queue: DispatchQueue 28 | 29 | fileprivate var didRestoreState = false 30 | fileprivate var knownPeripheralUUIDs: [UUID] 31 | fileprivate var deviceForUUID: [UUID : BLEDevice] = [:] 32 | fileprivate var alreadyDiscoveredUUIDs: Set = [] 33 | fileprivate var serviceUUIDs: [CBUUID] = [] 34 | fileprivate var updateReachability = false 35 | fileprivate var shouldDiscover = false 36 | 37 | private var _centralManager: CBCentralManager? 38 | 39 | public init(delegate: BLEDiscoveryManagerDelegate? = nil, queue: DispatchQueue? = nil, restoreIdentifier: String? = nil, knownPeripheralUUIDs: [UUID] = []) { 40 | self.delegate = delegate 41 | self.queue = queue ?? DispatchQueue.main 42 | self.knownPeripheralUUIDs = knownPeripheralUUIDs 43 | super.init() 44 | 45 | var centralManagerOptions: [String : Any] = [:] 46 | if let restoreIdentifier = restoreIdentifier { 47 | #if os(iOS) || os(tvOS) 48 | centralManagerOptions[CBCentralManagerOptionRestoreIdentifierKey] = restoreIdentifier 49 | #endif 50 | } 51 | self._centralManager = CBCentralManager(delegate: self, queue: self.queue, options: centralManagerOptions) 52 | } 53 | 54 | /// If detectUnreachableDevices is set to true, it will invalidate devices if they stop advertising. Consumes more energy since `CBCentralManagerScanOptionAllowDuplicatesKey` is set to true. 55 | public func startDiscovery(serviceUUIDs: [CBUUID], updateReachability: Bool = false) { 56 | self.serviceUUIDs = serviceUUIDs 57 | self.updateReachability = updateReachability 58 | self.shouldDiscover = true 59 | self.alreadyDiscoveredUUIDs = [] 60 | 61 | continueDiscovery() 62 | } 63 | 64 | fileprivate func continueDiscovery() { 65 | guard centralManager.state == .poweredOn else { return } 66 | centralManager.scanForPeripherals(withServices: serviceUUIDs, options: [CBCentralManagerScanOptionAllowDuplicatesKey : updateReachability]) 67 | } 68 | 69 | public func stopDiscovery() { 70 | shouldDiscover = false 71 | } 72 | 73 | internal func deviceDidStopAdvertising(_ device: BLEDevice) { 74 | alreadyDiscoveredUUIDs.remove(device.uuid) 75 | delegate?.bleDiscoveryManager(self, didStopAdvertising: device) 76 | } 77 | } 78 | 79 | extension BLEDiscoveryManager: CBCentralManagerDelegate { 80 | public func centralManager(_ central: CBCentralManager, willRestoreState state: [String : Any]) { 81 | didRestoreState = true 82 | 83 | NuimoSwift.DDLogDebug("BLEDiscoveryManager willRestoreState with state \(central.state.rawValue) on queue \(DispatchQueue.currentQueueLabel)") 84 | guard let centralManager = centralManager else { 85 | queue.async { self.centralManager(central, willRestoreState: state) } 86 | return 87 | } 88 | 89 | var restorablePeripherals: [CBPeripheral] = [] 90 | 91 | #if os(iOS) || os(tvOS) 92 | restorablePeripherals += state[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] ?? [] 93 | #endif 94 | 95 | restorablePeripherals += centralManager.retrievePeripherals(withIdentifiers: knownPeripheralUUIDs).filter { peripheral in 96 | !restorablePeripherals.contains(where: { $0.identifier == peripheral.identifier }) 97 | } 98 | 99 | restorablePeripherals 100 | .flatMap { delegate?.bleDiscoveryManager(self, deviceFor: $0, advertisementData: [:]) } 101 | .forEach { 102 | deviceForUUID[$0.uuid] = $0 103 | delegate?.bleDiscoveryManager(self, didRestore: $0) 104 | } 105 | 106 | restorablePeripherals.forEach { 107 | NuimoSwift.DDLogDebug("Restored/retrieved \($0.identifier.uuidString) with state \($0.state.rawValue)") 108 | } 109 | } 110 | 111 | public func centralManagerDidUpdateState(_ central: CBCentralManager) { 112 | NuimoSwift.DDLogDebug("BLEDiscoveryManager didUpdateState: \(central.state.rawValue)") 113 | guard let centralManager = centralManager else { 114 | queue.async { self.centralManagerDidUpdateState(central) } 115 | return 116 | } 117 | 118 | if centralManager.state.rawValue >= CBCentralManagerState.poweredOff.rawValue { 119 | if !didRestoreState { 120 | self.centralManager(central, willRestoreState: [:]) 121 | } 122 | 123 | // Update all devices with a freshly retrieved peripheral from central manager for those which have an invalidated peripheral 124 | centralManager.retrievePeripherals(withIdentifiers: Array(deviceForUUID.keys)).forEach { 125 | guard let device = self.deviceForUUID[$0.identifier], device.peripheral == nil else { return } 126 | device.restore(from: $0) 127 | NuimoSwift.DDLogDebug("Restored/retrieved \($0.identifier.uuidString) with state \($0.state.rawValue)") 128 | self.delegate?.bleDiscoveryManager(self, didRestore: device) 129 | } 130 | } 131 | 132 | deviceForUUID.values.forEach { $0.centralManagerDidUpdateState() } 133 | 134 | if central.state == .poweredOn && shouldDiscover { 135 | continueDiscovery() 136 | } 137 | } 138 | 139 | public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { 140 | let device = deviceForUUID[peripheral.identifier] 141 | 142 | if !alreadyDiscoveredUUIDs.contains(peripheral.identifier) { 143 | alreadyDiscoveredUUIDs.insert(peripheral.identifier) 144 | 145 | if let device = device ?? delegate?.bleDiscoveryManager(self, deviceFor: peripheral, advertisementData: advertisementData) { 146 | deviceForUUID[peripheral.identifier] = device 147 | delegate?.bleDiscoveryManager(self, didDiscover: device) 148 | } 149 | } 150 | 151 | device?.didAdvertise(advertisementData, RSSI: RSSI, willReceiveSuccessiveAdvertisingData: updateReachability) 152 | } 153 | 154 | public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { 155 | deviceForUUID[peripheral.identifier]?.didConnect() 156 | } 157 | 158 | public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { 159 | alreadyDiscoveredUUIDs.remove(peripheral.identifier) 160 | deviceForUUID[peripheral.identifier]?.didFailToConnect(error: error) 161 | } 162 | 163 | public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { 164 | alreadyDiscoveredUUIDs.remove(peripheral.identifier) 165 | deviceForUUID[peripheral.identifier]?.didDisconnect(error: error) 166 | } 167 | } 168 | 169 | public protocol BLEDiscoveryManagerDelegate: class { 170 | func bleDiscoveryManager(_ discovery: BLEDiscoveryManager, deviceFor peripheral: CBPeripheral, advertisementData: [String : Any]) -> BLEDevice? 171 | func bleDiscoveryManager(_ discovery: BLEDiscoveryManager, didDiscover device: BLEDevice) 172 | func bleDiscoveryManager(_ discovery: BLEDiscoveryManager, didRestore device: BLEDevice) 173 | func bleDiscoveryManager(_ discovery: BLEDiscoveryManager, didStopAdvertising device: BLEDevice) 174 | } 175 | 176 | internal extension DispatchQueue { 177 | static var currentQueueLabel: String { return String(cString: __dispatch_queue_get_label(nil), encoding: .utf8)! } 178 | 179 | func assertIsDispatching() { 180 | if DispatchQueue.currentQueueLabel != label { 181 | NuimoSwift.DDLogError("Dispatching should take place on queue '\(label)' but is taking place on queue '\(DispatchQueue.currentQueueLabel)'") 182 | Thread.callStackSymbols.forEach{ NuimoSwift.DDLogError($0) } 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /SDK/NuimoBluetoothController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NuimoController.swift 3 | // Nuimo 4 | // 5 | // Created by Lars Blumberg on 9/23/15. 6 | // Copyright © 2015 Senic. All rights reserved. 7 | // 8 | // This software may be modified and distributed under the terms 9 | // of the MIT license. See the LICENSE file for details. 10 | 11 | import CoreBluetooth 12 | 13 | // Represents a bluetooth low energy (BLE) Nuimo controller 14 | open class NuimoBluetoothController: BLEDevice, NuimoController { 15 | public static let serviceUUIDs = nuimoServiceUUIDs 16 | 17 | open override class var connectionRetryCount: Int { return 5 } 18 | open override class var maxAdvertisingPackageInterval: TimeInterval? { return 10.0 } 19 | 20 | public weak var delegate: NuimoControllerDelegate? 21 | public private(set) var connectionState = NuimoConnectionState.disconnected 22 | public var defaultMatrixDisplayInterval: TimeInterval = 2.0 23 | public var matrixBrightness: Float = 1.0 24 | 25 | public private(set) var hardwareVersion: String? 26 | public private(set) var firmwareVersion: String? { didSet { didUpdateState() } } 27 | public private(set) var color: String? 28 | 29 | open override var serviceUUIDs: [CBUUID] { return nuimoServiceUUIDs } 30 | open override var charactericUUIDsForServiceUUID: [CBUUID : [CBUUID]] { return nuimoCharactericUUIDsForServiceUUID } 31 | open override var notificationCharacteristicUUIDs: [CBUUID] { return nuimoNotificationCharacteristicnUUIDs } 32 | 33 | public var supportsRebootToDFUMode: Bool { return rebootToDFUModeCharacteristic != nil } 34 | public var supportsFlySensorCalibration: Bool { return flySensorCalibrationCharacteristic != nil } 35 | public var heartBeatInterval: TimeInterval = 0.0 { didSet { writeHeartBeatInterval() } } 36 | 37 | private lazy var matrixWriter: LEDMatrixWriter = LEDMatrixWriter(controller: self) 38 | private var connectTimeoutTimer: Timer? 39 | private var rebootToDFUModeCharacteristic: CBCharacteristic? { return peripheral?.service(with: kSensorServiceUUID)?.characteristic(with: kRebootToDFUModeCharacteristicUUID) } 40 | private var flySensorCalibrationCharacteristic: CBCharacteristic? { return peripheral?.service(with: kSensorServiceUUID)?.characteristic(with: kFlySensorCalibrationCharacteristicUUID) } 41 | 42 | open override func didUpdateState(error: Error? = nil) { 43 | super.didUpdateState() 44 | if peripheral?.state != .connected { 45 | matrixWriter.matrixCharacteristic = nil 46 | } 47 | let newState: NuimoConnectionState = { 48 | guard isReachable, let peripheral = peripheral else { return .invalidated } 49 | if peripheral.state == .connected { 50 | return firmwareVersion == nil ? .connecting : .connected 51 | } 52 | else if peripheral.state == .connecting { 53 | return .connecting 54 | } 55 | else if peripheral.state == .disconnected { 56 | return .disconnected 57 | } 58 | else { 59 | #if os(iOS) || os(tvOS) 60 | if #available(iOS 9.0, tvOS 9.0, *) { 61 | if peripheral.state == .disconnecting { return .disconnecting } 62 | } 63 | #endif 64 | fatalError("Unexpected peripheral state: \(peripheral.state.rawValue)") 65 | } 66 | }() 67 | guard newState != connectionState else { return } 68 | connectionState = newState 69 | delegate?.nuimoController(self, didChangeConnectionState: connectionState, withError: error) 70 | } 71 | 72 | public func display(matrix: NuimoLEDMatrix, interval: TimeInterval, options: Int) { 73 | queue.async { 74 | self.matrixWriter.write(matrix: matrix, interval: interval, options: options) 75 | } 76 | } 77 | 78 | @discardableResult public func rebootToDFUMode() -> Bool { 79 | guard 80 | let peripheral = peripheral, peripheral.state == .connected, 81 | let rebootToDFUModeCharacteristic = rebootToDFUModeCharacteristic 82 | else { 83 | return false 84 | } 85 | queue.async { 86 | peripheral.writeValue(Data(bytes: UnsafePointer([UInt8(1)]), count: 1), for: rebootToDFUModeCharacteristic, type: .withResponse) 87 | } 88 | return true 89 | } 90 | 91 | @discardableResult public func calibrateFlySensor() -> Bool { 92 | guard 93 | let peripheral = peripheral, peripheral.state == .connected, 94 | let flySensorCalibrationCharacteristic = flySensorCalibrationCharacteristic 95 | else { 96 | return false 97 | } 98 | queue.async { 99 | peripheral.writeValue(Data(bytes: UnsafePointer([UInt8(1)]), count: 1), for: flySensorCalibrationCharacteristic, type: .withResponse) 100 | } 101 | return true 102 | } 103 | 104 | fileprivate func writeHeartBeatInterval() { 105 | guard 106 | let peripheral = peripheral, peripheral.state == .connected, 107 | let service = peripheral.services?.filter({ $0.uuid == kSensorServiceUUID }).first, 108 | let characteristic = service.characteristics?.filter({ $0.uuid == kHeartBeatCharacteristicUUID }).first 109 | else { 110 | return 111 | } 112 | let interval = UInt8(max(0, min(255, heartBeatInterval))) 113 | queue.async { 114 | peripheral.writeValue(Data(bytes: UnsafePointer([interval]), count: 1), for: characteristic, type: .withResponse) 115 | } 116 | } 117 | 118 | //MARK: CBPeripheralDelegate 119 | 120 | open override func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { 121 | super.peripheral(peripheral, didDiscoverCharacteristicsFor: service, error: error) 122 | service.characteristics?.forEach{ characteristic in 123 | switch characteristic.uuid { 124 | case kHardwareVersionCharacteristicUUID: fallthrough 125 | case kFirmwareVersionCharacteristicUUID: fallthrough 126 | case kModelNumberCharacteristicUUID: fallthrough 127 | case kBatteryCharacteristicUUID: peripheral.readValue(for: characteristic) 128 | case kLEDMatrixCharacteristicUUID: matrixWriter.matrixCharacteristic = characteristic 129 | case kHeartBeatCharacteristicUUID: writeHeartBeatInterval() 130 | default: 131 | break 132 | } 133 | } 134 | } 135 | 136 | open override func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { 137 | super.peripheral(peripheral, didUpdateValueFor: characteristic, error: error) 138 | 139 | guard let data = characteristic.value else { return } 140 | 141 | switch characteristic.uuid { 142 | case kHardwareVersionCharacteristicUUID: hardwareVersion = String(data: data, encoding: .utf8) 143 | case kFirmwareVersionCharacteristicUUID: firmwareVersion = String(data: data, encoding: .utf8) 144 | case kModelNumberCharacteristicUUID: color = String(data: data, encoding: .utf8) 145 | case kBatteryCharacteristicUUID: delegate?.nuimoController(self, didUpdateBatteryLevel: Int((data as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count).pointee)) 146 | case kHeartBeatCharacteristicUUID: NotificationCenter.default.post(name: .NuimoBluetoothControllerDidSendHeartBeat, object: self, userInfo: nil) 147 | default: if let event = characteristic.nuimoGestureEvent() { delegate?.nuimoController(self, didReceiveGestureEvent: event) } 148 | } 149 | } 150 | 151 | open override func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { 152 | super.peripheral(peripheral, didWriteValueFor: characteristic, error: error) 153 | if characteristic.uuid == kLEDMatrixCharacteristicUUID { 154 | matrixWriter.didRetrieveMatrixWriteResponse() 155 | delegate?.nuimoControllerDidDisplayLEDMatrix(self) 156 | } 157 | } 158 | } 159 | 160 | public let NuimoBluetoothControllerDidSendHeartBeatNotification = "NuimoBluetoothControllerDidSendHeartBeatNotification" 161 | public extension Notification.Name { 162 | public static let NuimoBluetoothControllerDidSendHeartBeat = Notification.Name(rawValue: NuimoBluetoothControllerDidSendHeartBeatNotification) 163 | } 164 | 165 | //MARK: - LED matrix writing 166 | 167 | private class LEDMatrixWriter { 168 | unowned let controller: NuimoBluetoothController 169 | var matrixCharacteristic: CBCharacteristic? 170 | 171 | private var currentMatrix: NuimoLEDMatrix? 172 | private var currentDisplayInterval: TimeInterval = 0.0 173 | private var currentWithFadeTransition = false 174 | private var lastWrittenMatrix = NuimoLEDMatrix(string: "") 175 | private var lastWrittenMatrixDate = Date(timeIntervalSince1970: 0.0) 176 | private var lastWrittenMatrixDisplayInterval: TimeInterval = 0.0 177 | private var isWaitingForWriteResponse = false 178 | private var writeOnResponseReceived = false 179 | private var writeResponseTimeoutDispatchWorkItem: DispatchWorkItem? 180 | 181 | init(controller: NuimoBluetoothController) { 182 | self.controller = controller 183 | } 184 | 185 | func write(matrix: NuimoLEDMatrix, interval: TimeInterval, options: Int) { 186 | let resendsSameMatrix = options & NuimoLEDMatrixWriteOption.ignoreDuplicates.rawValue == 0 187 | let withFadeTransition = options & NuimoLEDMatrixWriteOption.withFadeTransition.rawValue != 0 188 | let withWriteResponse = options & NuimoLEDMatrixWriteOption.withoutWriteResponse.rawValue == 0 189 | 190 | guard 191 | resendsSameMatrix || 192 | lastWrittenMatrix != matrix || 193 | (lastWrittenMatrixDisplayInterval > 0 && -lastWrittenMatrixDate.timeIntervalSinceNow >= lastWrittenMatrixDisplayInterval) 194 | else { 195 | return 196 | } 197 | 198 | currentMatrix = matrix 199 | currentDisplayInterval = interval 200 | currentWithFadeTransition = withFadeTransition 201 | 202 | if withWriteResponse && isWaitingForWriteResponse { 203 | writeOnResponseReceived = true 204 | } 205 | else { 206 | writeMatrixNow(withWriteResponse: withWriteResponse) 207 | } 208 | } 209 | 210 | private func writeMatrixNow(withWriteResponse: Bool) { 211 | guard 212 | let peripheral = controller.peripheral, 213 | peripheral.state == .connected, 214 | let matrixCharacteristic = matrixCharacteristic 215 | else { 216 | return 217 | } 218 | guard let currentMatrix = currentMatrix else { fatalError("Invalid matrix write request") } 219 | var matrixBytes = currentMatrix.matrixBytes 220 | guard currentMatrix.matrixBytes.count == 11 && !(withWriteResponse && isWaitingForWriteResponse) else { fatalError("Invalid matrix write request") } 221 | 222 | matrixBytes[10] = matrixBytes[10] + 223 | (currentWithFadeTransition ? UInt8(1 << 4) : 0) + 224 | (currentMatrix is NuimoBuiltInLEDMatrix ? UInt8(1 << 5) : 0) 225 | matrixBytes += [UInt8(min(max(controller.matrixBrightness, 0.0), 1.0) * 255), UInt8(currentDisplayInterval * 10.0)] 226 | peripheral.writeValue(Data(bytes: UnsafePointer(matrixBytes), count: matrixBytes.count), for: matrixCharacteristic, type: withWriteResponse ? .withResponse : .withoutResponse) 227 | 228 | isWaitingForWriteResponse = withWriteResponse 229 | lastWrittenMatrix = currentMatrix 230 | lastWrittenMatrixDate = Date() 231 | lastWrittenMatrixDisplayInterval = currentDisplayInterval 232 | 233 | if withWriteResponse { 234 | // When the matrix write response is not retrieved within 500ms we assume the response to have timed out 235 | writeResponseTimeoutDispatchWorkItem?.cancel() 236 | writeResponseTimeoutDispatchWorkItem = DispatchWorkItem() { [weak self] in self?.didRetrieveMatrixWriteResponse() } 237 | controller.queue.asyncAfter(deadline: .now() + 0.5, execute: writeResponseTimeoutDispatchWorkItem!) 238 | } 239 | } 240 | 241 | dynamic func didRetrieveMatrixWriteResponse() { 242 | guard isWaitingForWriteResponse else { return } 243 | isWaitingForWriteResponse = false 244 | writeResponseTimeoutDispatchWorkItem?.cancel() 245 | 246 | // Write next matrix if any 247 | if writeOnResponseReceived { 248 | writeOnResponseReceived = false 249 | writeMatrixNow(withWriteResponse: true) 250 | } 251 | } 252 | } 253 | 254 | //MARK: Nuimo BLE GATT service and characteristic UUIDs 255 | 256 | private let kBatteryServiceUUID = CBUUID(string: "180F") 257 | private let kBatteryCharacteristicUUID = CBUUID(string: "2A19") 258 | private let kDeviceInformationServiceUUID = CBUUID(string: "180A") 259 | private let kHardwareVersionCharacteristicUUID = CBUUID(string: "2A27") 260 | private let kFirmwareVersionCharacteristicUUID = CBUUID(string: "2A26") 261 | private let kModelNumberCharacteristicUUID = CBUUID(string: "2A24") 262 | private let kLEDMatrixServiceUUID = CBUUID(string: "F29B1523-CB19-40F3-BE5C-7241ECB82FD1") 263 | private let kLEDMatrixCharacteristicUUID = CBUUID(string: "F29B1524-CB19-40F3-BE5C-7241ECB82FD1") 264 | private let kSensorServiceUUID = CBUUID(string: "F29B1525-CB19-40F3-BE5C-7241ECB82FD2") 265 | private let kSensorFlyCharacteristicUUID = CBUUID(string: "F29B1526-CB19-40F3-BE5C-7241ECB82FD2") 266 | private let kSensorTouchCharacteristicUUID = CBUUID(string: "F29B1527-CB19-40F3-BE5C-7241ECB82FD2") 267 | private let kSensorRotationCharacteristicUUID = CBUUID(string: "F29B1528-CB19-40F3-BE5C-7241ECB82FD2") 268 | private let kSensorButtonCharacteristicUUID = CBUUID(string: "F29B1529-CB19-40F3-BE5C-7241ECB82FD2") 269 | private let kRebootToDFUModeCharacteristicUUID = CBUUID(string: "F29B152A-CB19-40F3-BE5C-7241ECB82FD2") 270 | private let kHeartBeatCharacteristicUUID = CBUUID(string: "F29B152B-CB19-40F3-BE5C-7241ECB82FD2") 271 | private let kFlySensorCalibrationCharacteristicUUID = CBUUID(string: "F29B152C-CB19-40F3-BE5C-7241ECB82FD2") 272 | 273 | internal let nuimoServiceUUIDs: [CBUUID] = [ 274 | kBatteryServiceUUID, 275 | kDeviceInformationServiceUUID, 276 | kLEDMatrixServiceUUID, 277 | kSensorServiceUUID 278 | ] 279 | 280 | private let nuimoCharactericUUIDsForServiceUUID = [ 281 | kBatteryServiceUUID: [kBatteryCharacteristicUUID], 282 | kDeviceInformationServiceUUID: [ 283 | kHardwareVersionCharacteristicUUID, 284 | kFirmwareVersionCharacteristicUUID, 285 | kModelNumberCharacteristicUUID 286 | ], 287 | kLEDMatrixServiceUUID: [kLEDMatrixCharacteristicUUID], 288 | kSensorServiceUUID: [ 289 | kSensorFlyCharacteristicUUID, 290 | kSensorTouchCharacteristicUUID, 291 | kSensorRotationCharacteristicUUID, 292 | kSensorButtonCharacteristicUUID, 293 | kRebootToDFUModeCharacteristicUUID, 294 | kHeartBeatCharacteristicUUID, 295 | kFlySensorCalibrationCharacteristicUUID 296 | ] 297 | ] 298 | 299 | private let nuimoNotificationCharacteristicnUUIDs = [ 300 | kBatteryCharacteristicUUID, 301 | kSensorFlyCharacteristicUUID, 302 | kSensorTouchCharacteristicUUID, 303 | kSensorRotationCharacteristicUUID, 304 | kSensorButtonCharacteristicUUID, 305 | kHeartBeatCharacteristicUUID 306 | ] 307 | 308 | //MARK: - Private extensions 309 | 310 | //MARK: Initializers for NuimoGestureEvents from BLE GATT data 311 | 312 | private extension NuimoGestureEvent { 313 | convenience init?(gattFlyData data: Data) { 314 | let bytes = (data as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count) 315 | let directionByte = bytes.pointee 316 | let speedByte = bytes.advanced(by: 1).pointee 317 | guard let gesture: NuimoGesture = [0: .flyLeft, 1: .flyRight, 4: .flyUpDown][directionByte] else { return nil } 318 | self.init(gesture: gesture, value: gesture == .flyUpDown ? Int(speedByte) : nil) 319 | } 320 | 321 | convenience init?(gattTouchData data: Data) { 322 | let bytes = (data as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count) 323 | guard let gesture: NuimoGesture = [ 324 | 0: .swipeLeft, 325 | 1: .swipeRight, 326 | 2: .swipeUp, 327 | 3: .swipeDown, 328 | 4: .touchLeft, 329 | 5: .touchRight, 330 | 6: .touchTop, 331 | 7: .touchBottom, 332 | 8: .longTouchLeft, 333 | 9: .longTouchRight, 334 | 10: .longTouchTop, 335 | 11: .longTouchBottom 336 | ][bytes.pointee] else { return nil } 337 | self.init(gesture: gesture, value: nil) 338 | } 339 | 340 | convenience init(gattRotationData data: Data) { 341 | let value = Int((data as NSData).bytes.bindMemory(to: Int16.self, capacity: data.count).pointee) 342 | self.init(gesture: .rotate, value: value) 343 | } 344 | 345 | convenience init(gattButtonData data: Data) { 346 | let value = Int((data as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count).pointee) 347 | self.init(gesture: value == 1 ? .buttonPress : .buttonRelease, value: value) 348 | } 349 | } 350 | 351 | //MARK: Matrix string to byte array conversion 352 | 353 | private extension NuimoLEDMatrix { 354 | var matrixBytes: [UInt8] { 355 | return leds 356 | .chunk(8) 357 | .map{ $0 358 | .enumerated() 359 | .map { (i: Int, b: Bool) -> Int in return b ? 1 << i : 0 } 360 | .reduce(UInt8(0)) { (s: UInt8, v: Int) -> UInt8 in s + UInt8(v) } 361 | } 362 | } 363 | } 364 | 365 | private extension Sequence { 366 | func chunk(_ n: Int) -> [[Iterator.Element]] { 367 | var chunks: [[Iterator.Element]] = [] 368 | var chunk: [Iterator.Element] = [] 369 | chunk.reserveCapacity(n) 370 | chunks.reserveCapacity(underestimatedCount / n) 371 | var i = n 372 | self.forEach { 373 | chunk.append($0) 374 | i -= 1 375 | if i == 0 { 376 | chunks.append(chunk) 377 | chunk.removeAll(keepingCapacity: true) 378 | i = n 379 | } 380 | } 381 | if !chunk.isEmpty { chunks.append(chunk) } 382 | return chunks 383 | } 384 | } 385 | 386 | //MARK: Extension methods for CoreBluetooth 387 | 388 | private extension CBCharacteristic { 389 | func nuimoGestureEvent() -> NuimoGestureEvent? { 390 | guard let data = value else { return nil } 391 | 392 | switch uuid { 393 | case kSensorFlyCharacteristicUUID: return NuimoGestureEvent(gattFlyData: data) 394 | case kSensorTouchCharacteristicUUID: return NuimoGestureEvent(gattTouchData: data) 395 | case kSensorRotationCharacteristicUUID: return NuimoGestureEvent(gattRotationData: data) 396 | case kSensorButtonCharacteristicUUID: return NuimoGestureEvent(gattButtonData: data) 397 | default: return nil 398 | } 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /SDK/NuimoBuiltInLEDMatrix.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NuimoBuiltInLEDMatrix.swift 3 | // Pods 4 | // 5 | // Created by Lars Blumberg on 10/17/16. 6 | // 7 | // 8 | 9 | public class NuimoBuiltInLEDMatrix: NuimoLEDMatrix { 10 | public static let busy = NuimoBuiltInLEDMatrix(identifier: 1) 11 | 12 | private init(identifier: UInt8) { 13 | var leds = Array(repeating: false, count: 81) 14 | var n = identifier 15 | var i = 0 16 | while n > 0 { 17 | leds[i] = n % 2 > 0 18 | n = n / 2 19 | i += 1 20 | } 21 | super.init(leds: leds) 22 | } 23 | 24 | internal override func equals(_ other: NuimoLEDMatrix) -> Bool { 25 | guard let other = other as? NuimoBuiltInLEDMatrix else { return false } 26 | return super.equals(other) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SDK/NuimoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NuimoController.swift 3 | // Nuimo 4 | // 5 | // Created by Lars Blumberg on 10/9/15. 6 | // Copyright © 2015 Senic. All rights reserved. 7 | // 8 | // This software may be modified and distributed under the terms 9 | // of the MIT license. See the LICENSE file for details. 10 | 11 | public protocol NuimoController: class { 12 | var uuid: UUID {get} 13 | var delegate: NuimoControllerDelegate? {get set} 14 | 15 | var connectionState: NuimoConnectionState {get} 16 | /// Display interval in seconds 17 | var defaultMatrixDisplayInterval: TimeInterval {get set} 18 | /// Brightness 0..1 (1=max) 19 | var matrixBrightness: Float {get set} 20 | 21 | var hardwareVersion: String? {get} 22 | var firmwareVersion: String? {get} 23 | var color: String? {get} 24 | 25 | func connect(autoReconnect: Bool) 26 | 27 | func disconnect() 28 | 29 | /// Displays an LED matrix for an interval with options (options is of type Int for compatibility with Objective-C) 30 | func display(matrix: NuimoLEDMatrix, interval: TimeInterval, options: Int) 31 | } 32 | 33 | public extension NuimoController { 34 | public func connect() { 35 | self.connect(autoReconnect: false) 36 | } 37 | 38 | /// Displays an LED matrix with options defaulting to ResendsSameMatrix and WithWriteResponse 39 | public func display(matrix: NuimoLEDMatrix, interval: TimeInterval) { 40 | display(matrix: matrix, interval: interval, options: 0) 41 | } 42 | 43 | /// Displays an LED matrix using the default display interval and with options defaulting to ResendsSameMatrix and WithWriteResponse 44 | public func display(matrix: NuimoLEDMatrix) { 45 | display(matrix: matrix, interval: defaultMatrixDisplayInterval) 46 | } 47 | 48 | /// Displays an LED matrix for an interval and with options 49 | public func display(matrix: NuimoLEDMatrix, interval: TimeInterval, options: NuimoLEDMatrixWriteOptions) { 50 | display(matrix: matrix, interval: interval, options: options.rawValue) 51 | } 52 | 53 | /// Displays an LED matrix using the default display interval and with options defaulting to ResendsSameMatrix and WithWriteResponse 54 | public func display(matrix: NuimoLEDMatrix, options: NuimoLEDMatrixWriteOptions) { 55 | display(matrix: matrix, interval: defaultMatrixDisplayInterval, options: options) 56 | } 57 | } 58 | 59 | public enum NuimoLEDMatrixWriteOption: Int { 60 | case ignoreDuplicates = 1 61 | case withFadeTransition = 2 62 | case withoutWriteResponse = 4 63 | } 64 | 65 | public struct NuimoLEDMatrixWriteOptions: OptionSet { 66 | public let rawValue: Int 67 | 68 | public init(rawValue: Int) { 69 | self.rawValue = rawValue 70 | } 71 | 72 | public static let IgnoreDuplicates = NuimoLEDMatrixWriteOptions(rawValue: NuimoLEDMatrixWriteOption.ignoreDuplicates.rawValue) 73 | public static let WithFadeTransition = NuimoLEDMatrixWriteOptions(rawValue: NuimoLEDMatrixWriteOption.withFadeTransition.rawValue) 74 | public static let WithoutWriteResponse = NuimoLEDMatrixWriteOptions(rawValue: NuimoLEDMatrixWriteOption.withoutWriteResponse.rawValue) 75 | } 76 | 77 | public enum NuimoConnectionState { 78 | case connecting 79 | case connected 80 | case disconnecting 81 | case disconnected 82 | case invalidated 83 | } 84 | 85 | public protocol NuimoControllerDelegate: class { 86 | func nuimoController(_ controller: NuimoController, didChangeConnectionState state: NuimoConnectionState, withError error: Error?) 87 | func nuimoController(_ controller: NuimoController, didUpdateBatteryLevel batteryLevel: Int) 88 | func nuimoController(_ controller: NuimoController, didReceiveGestureEvent event: NuimoGestureEvent) 89 | func nuimoControllerDidDisplayLEDMatrix(_ controller: NuimoController) 90 | } 91 | 92 | public extension NuimoControllerDelegate { 93 | func nuimoController(_ controller: NuimoController, didChangeConnectionState state: NuimoConnectionState, withError error: Error?) {} 94 | func nuimoController(_ controller: NuimoController, didUpdateBatteryLevel batteryLevel: Int) {} 95 | func nuimoController(_ controller: NuimoController, didReceiveGestureEvent event: NuimoGestureEvent) {} 96 | func nuimoControllerDidDisplayLEDMatrix(_ controller: NuimoController) {} 97 | } 98 | -------------------------------------------------------------------------------- /SDK/NuimoDiscoveryManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NuimoDiscoveryManager.swift 3 | // Nuimo 4 | // 5 | // Created by Lars Blumberg on 9/23/15. 6 | // Copyright © 2015 Senic. All rights reserved. 7 | // 8 | // This software may be modified and distributed under the terms 9 | // of the MIT license. See the LICENSE file for details. 10 | 11 | import CoreBluetooth 12 | 13 | // Allows for discovering Nuimo BLE hardware controllers and virtual (websocket) controllers 14 | public class NuimoDiscoveryManager { 15 | 16 | public static let sharedManager = NuimoDiscoveryManager() 17 | 18 | public private(set) var bleDiscoveryManager: BLEDiscoveryManager! 19 | public var centralManager: CBCentralManager { return self.bleDiscoveryManager.centralManager } 20 | public weak var delegate: NuimoDiscoveryDelegate? 21 | 22 | private var bleDiscoveryDelegate: NuimoDiscoveryManagerPrivate! 23 | 24 | public init(delegate: NuimoDiscoveryDelegate? = nil, queue: DispatchQueue? = nil, restoreIdentifier: String? = nil, knownNuimoUUIDs: [UUID] = []) { 25 | self.delegate = delegate 26 | self.bleDiscoveryDelegate = NuimoDiscoveryManagerPrivate(nuimoDiscoveryManager: self) 27 | self.bleDiscoveryManager = BLEDiscoveryManager(delegate: self.bleDiscoveryDelegate, queue: queue, restoreIdentifier: restoreIdentifier, knownPeripheralUUIDs: knownNuimoUUIDs) 28 | } 29 | 30 | public func startDiscovery(serviceUUIDs: [CBUUID] = nuimoServiceUUIDs, updateReachability: Bool = false) { 31 | bleDiscoveryManager.startDiscovery(serviceUUIDs: serviceUUIDs, updateReachability: updateReachability) 32 | } 33 | 34 | public func stopDiscovery() { 35 | bleDiscoveryManager.stopDiscovery() 36 | } 37 | } 38 | 39 | private class NuimoDiscoveryManagerPrivate: BLEDiscoveryManagerDelegate { 40 | weak var manager: NuimoDiscoveryManager? 41 | 42 | init(nuimoDiscoveryManager: NuimoDiscoveryManager) { 43 | self.manager = nuimoDiscoveryManager 44 | } 45 | 46 | func bleDiscoveryManager(_ discovery: BLEDiscoveryManager, deviceFor peripheral: CBPeripheral, advertisementData: [String : Any]) -> BLEDevice? { 47 | guard let manager = manager else { return nil } 48 | return manager.delegate?.nuimoDiscoveryManager(manager, deviceForPeripheral: peripheral, advertisementData: advertisementData) 49 | } 50 | 51 | func bleDiscoveryManager(_ discovery: BLEDiscoveryManager, didDiscover device: BLEDevice) { 52 | guard let manager = manager else { return } 53 | manager.delegate?.nuimoDiscoveryManager(manager, didDiscoverNuimoController: device as! NuimoController) 54 | } 55 | 56 | func bleDiscoveryManager(_ discovery: BLEDiscoveryManager, didRestore device: BLEDevice) { 57 | guard let manager = manager else { return } 58 | manager.delegate?.nuimoDiscoveryManager(manager, didRestoreNuimoController: device as! NuimoController) 59 | } 60 | 61 | func bleDiscoveryManager(_ discovery: BLEDiscoveryManager, didStopAdvertising device: BLEDevice) { 62 | guard let manager = manager else { return } 63 | manager.delegate?.nuimoDiscoveryManager(manager, didStopAdvertising: device as! NuimoController) 64 | } 65 | } 66 | 67 | public protocol NuimoDiscoveryDelegate: class { 68 | //TODO: Rename delegate methods to `deviceFor:` etc. 69 | func nuimoDiscoveryManager(_ discovery: NuimoDiscoveryManager, deviceForPeripheral peripheral: CBPeripheral, advertisementData: [String : Any]) -> BLEDevice? 70 | func nuimoDiscoveryManager(_ discovery: NuimoDiscoveryManager, didDiscoverNuimoController controller: NuimoController) 71 | func nuimoDiscoveryManager(_ discovery: NuimoDiscoveryManager, didRestoreNuimoController controller: NuimoController) 72 | func nuimoDiscoveryManager(_ discovery: NuimoDiscoveryManager, didStopAdvertising controller: NuimoController) 73 | } 74 | 75 | public extension NuimoDiscoveryDelegate { 76 | func nuimoDiscoveryManager(_ discovery: NuimoDiscoveryManager, deviceForPeripheral peripheral: CBPeripheral, advertisementData: [String : Any]) -> BLEDevice? { 77 | guard peripheral.name == "Nuimo" || advertisementData[CBAdvertisementDataLocalNameKey] as? String == "Nuimo" else { return nil } 78 | return NuimoBluetoothController(discoveryManager: discovery.bleDiscoveryManager, peripheral: peripheral) 79 | } 80 | func nuimoDiscoveryManager(_ discovery: NuimoDiscoveryManager, didRestoreNuimoController controller: NuimoController) {} 81 | func nuimoDiscoveryManager(_ discovery: NuimoDiscoveryManager, didStopAdvertising controller: NuimoController) {} 82 | } 83 | -------------------------------------------------------------------------------- /SDK/NuimoErrorDomain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NuimoErrorDomain.swift 3 | // Pods 4 | // 5 | // Created by Lars Blumberg on 4/4/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public let NuimoErrorDomain = "NuimoErrorDomain" 12 | 13 | public let NuimoBLEDeviceFailedToConnect = 1001 14 | -------------------------------------------------------------------------------- /SDK/NuimoGesture.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NuimoGesture.swift 3 | // Nuimo 4 | // 5 | // Created by Lars Blumberg on 9/23/15. 6 | // Copyright © 2015 Senic. All rights reserved. 7 | // 8 | // This software may be modified and distributed under the terms 9 | // of the MIT license. See the LICENSE file for details. 10 | 11 | public enum NuimoGesture { 12 | case buttonPress 13 | case buttonRelease 14 | case rotate 15 | case touchLeft 16 | case touchRight 17 | case touchTop 18 | case touchBottom 19 | case longTouchLeft 20 | case longTouchRight 21 | case longTouchTop 22 | case longTouchBottom 23 | case swipeLeft 24 | case swipeRight 25 | case swipeUp 26 | case swipeDown 27 | case flyLeft 28 | case flyRight 29 | case flyUpDown 30 | 31 | public init?(identifier: String) { 32 | guard let gesture = gestureForIdentifier[identifier] else { return nil } 33 | self = gesture 34 | } 35 | 36 | public var identifier: String { return identifierForGesture[self]! } 37 | } 38 | 39 | private let identifierForGesture: [NuimoGesture : String] = [ 40 | .buttonPress : "ButtonPress", 41 | .buttonRelease : "ButtonRelease", 42 | .rotate : "Rotate", 43 | .touchLeft : "TouchLeft", 44 | .touchRight : "TouchRight", 45 | .touchTop : "TouchTop", 46 | .touchBottom : "TouchBottom", 47 | .longTouchLeft : "LongTouchLeft", 48 | .longTouchRight : "LongTouchRight", 49 | .longTouchTop : "LongTouchTop", 50 | .longTouchBottom : "LongTouchBottom", 51 | .swipeLeft : "SwipeLeft", 52 | .swipeRight : "SwipeRight", 53 | .swipeUp : "SwipeUp", 54 | .swipeDown : "SwipeDown", 55 | .flyLeft : "FlyLeft", 56 | .flyRight : "FlyRight", 57 | .flyUpDown : "FlyUpDown" 58 | ] 59 | 60 | private let gestureForIdentifier: [String : NuimoGesture] = { 61 | var dictionary = [String : NuimoGesture]() 62 | for (gesture, identifier) in identifierForGesture { 63 | dictionary[identifier] = gesture 64 | } 65 | return dictionary 66 | }() 67 | -------------------------------------------------------------------------------- /SDK/NuimoGestureEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NuimoGestureEvent.swift 3 | // Nuimo 4 | // 5 | // Created by je on 8/11/15. 6 | // Copyright © 2015 Senic. All rights reserved. 7 | // 8 | // This software may be modified and distributed under the terms 9 | // of the MIT license. See the LICENSE file for details. 10 | 11 | public class NuimoGestureEvent { 12 | public let gesture: NuimoGesture 13 | public let value: Int? 14 | 15 | public init(gesture: NuimoGesture, value: Int?) { 16 | self.gesture = gesture 17 | self.value = value 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /SDK/NuimoLEDMatrix.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NuimoLEDMatrix.swift 3 | // Nuimo 4 | // 5 | // Created by Lars Blumberg on 11/02/15. 6 | // Copyright © 2015 Senic. All rights reserved. 7 | // 8 | // This software may be modified and distributed under the terms 9 | // of the MIT license. See the LICENSE file for details. 10 | 11 | open class NuimoLEDMatrix { 12 | public static let ledCount = 81 13 | public static let ledOffCharacters: [Character] = [" ", "0"] 14 | 15 | public let leds: [Bool] 16 | 17 | public init(matrix: NuimoLEDMatrix) { 18 | leds = matrix.leds 19 | } 20 | 21 | public init(string: String) { 22 | leds = string 23 | // Cut off after count of LEDs 24 | .substring(to: string.characters.index(string.startIndex, offsetBy: min(string.characters.count, NuimoLEDMatrix.ledCount))) 25 | // Right fill up to count of LEDs 26 | .padding(toLength: NuimoLEDMatrix.ledCount, withPad: " ", startingAt: 0) 27 | .characters 28 | .map{!NuimoLEDMatrix.ledOffCharacters.contains($0)} 29 | } 30 | 31 | public init(leds: [Bool]) { 32 | self.leds = leds.prefix(81) + (leds.count < 81 ? Array(repeating: false, count: 81 - leds.count) : []) 33 | } 34 | 35 | //TODO: Have only one init(progress) method and pass presentation style as 2nd argument 36 | public convenience init(progressWithVerticalBar progress: Double) { 37 | let string = (0..<9) 38 | .reversed() 39 | .map{progress > Double($0) / 9.0 ? " . " : " "} 40 | .reduce("", +) 41 | self.init(string: string) 42 | } 43 | 44 | public convenience init(progressWithVolumeBar progress: Double) { 45 | let width = Int(ceil(max(0.0, min(1.0, progress)) * 9)) 46 | let string = (0..<9) 47 | .map{String(repeating: " ", count: 9 - ($0 + 1)) + String(repeating: ".", count: $0 + 1)} 48 | .enumerated() 49 | .map{$0.element 50 | .substring(to: $0.element.characters.index($0.element.startIndex, offsetBy: width)) 51 | .padding(toLength: 9, withPad: " ", startingAt: 0)} 52 | .reduce("", +) 53 | self.init(string: string) 54 | } 55 | 56 | internal func equals(_ other: NuimoLEDMatrix) -> Bool { 57 | return leds == other.leds 58 | } 59 | } 60 | 61 | extension NuimoLEDMatrix: Equatable {} 62 | 63 | public func ==(lhs: NuimoLEDMatrix, rhs: NuimoLEDMatrix) -> Bool { 64 | return lhs.equals(rhs) 65 | } 66 | 67 | //MARK: Predefined matrices 68 | 69 | extension NuimoLEDMatrix { 70 | public static var emptyMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 71 | " " + 72 | " " + 73 | " " + 74 | " " + 75 | " " + 76 | " " + 77 | " " + 78 | " " + 79 | " ")} 80 | 81 | public static var musicNoteMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 82 | " " + 83 | " ..... " + 84 | " ..... " + 85 | " . . " + 86 | " . . " + 87 | " . . " + 88 | " .. .. " + 89 | "... ... " + 90 | " . . ")} 91 | 92 | public static var lightBulbMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 93 | " " + 94 | " ... " + 95 | " . . " + 96 | " . . " + 97 | " . . " + 98 | " ... " + 99 | " ... " + 100 | " ... " + 101 | " . ")} 102 | 103 | public static var powerOnMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 104 | " " + 105 | " " + 106 | " ... " + 107 | " ..... " + 108 | " ..... " + 109 | " ..... " + 110 | " ... " + 111 | " " + 112 | " ")} 113 | 114 | public static var powerOffMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 115 | " " + 116 | " " + 117 | " ... " + 118 | " . . " + 119 | " . . " + 120 | " . . " + 121 | " ... " + 122 | " " + 123 | " ")} 124 | 125 | public static var shuffleMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 126 | " " + 127 | " " + 128 | " .. .. " + 129 | " . . " + 130 | " . " + 131 | " . . " + 132 | " .. .. " + 133 | " " + 134 | " ")} 135 | 136 | public static var letterBMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 137 | " " + 138 | " ... " + 139 | " . . " + 140 | " . . " + 141 | " ... " + 142 | " . . " + 143 | " . . " + 144 | " ... " + 145 | " ")} 146 | 147 | public static var letterOMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 148 | " " + 149 | " ... " + 150 | " . . " + 151 | " . . " + 152 | " . . " + 153 | " . . " + 154 | " . . " + 155 | " ... " + 156 | " ")} 157 | 158 | public static var letterGMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 159 | " " + 160 | " ... " + 161 | " . . " + 162 | " . " + 163 | " . ... " + 164 | " . . " + 165 | " . . " + 166 | " ... " + 167 | " ")} 168 | 169 | public static var letterWMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 170 | " " + 171 | " . . " + 172 | " . . " + 173 | " . . " + 174 | " . . " + 175 | " . . . " + 176 | " . . . " + 177 | " .. .. " + 178 | " ")} 179 | 180 | public static var letterYMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 181 | " " + 182 | " . . " + 183 | " . . " + 184 | " . . " + 185 | " . " + 186 | " . " + 187 | " . " + 188 | " . " + 189 | " ")} 190 | 191 | public static var playMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 192 | " " + 193 | " . " + 194 | " .. " + 195 | " ... " + 196 | " .... " + 197 | " ... " + 198 | " .. " + 199 | " . " + 200 | " ")} 201 | 202 | public static var pauseMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 203 | " " + 204 | " .. .. " + 205 | " .. .. " + 206 | " .. .. " + 207 | " .. .. " + 208 | " .. .. " + 209 | " .. .. " + 210 | " .. .. " + 211 | " ")} 212 | 213 | public static var nextMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 214 | " " + 215 | " " + 216 | " . . " + 217 | " .. . " + 218 | " .... " + 219 | " .. . " + 220 | " . . " + 221 | " " + 222 | " ")} 223 | 224 | public static var previousMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 225 | " " + 226 | " " + 227 | " . . " + 228 | " . .. " + 229 | " .... " + 230 | " . .. " + 231 | " . . " + 232 | " " + 233 | " ")} 234 | 235 | public static var questionMarkMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 236 | " ... " + 237 | " . . " + 238 | " . . " + 239 | " . " + 240 | " . " + 241 | " . " + 242 | " . " + 243 | " " + 244 | " . ")} 245 | 246 | public static var bluetoothMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 247 | " * " + 248 | " ** " + 249 | " * * * " + 250 | " *** " + 251 | " * " + 252 | " *** " + 253 | " * * * " + 254 | " ** " + 255 | " * ")} 256 | 257 | public static var upArrowMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 258 | " . " + 259 | " ... " + 260 | " ..... " + 261 | " ....... " + 262 | " ... " + 263 | " ... " + 264 | " ... " + 265 | " ... " + 266 | " ")} 267 | 268 | public static var downArrowMatrix: NuimoLEDMatrix {return NuimoLEDMatrix(string: 269 | " ... " + 270 | " ... " + 271 | " ... " + 272 | " ... " + 273 | " ....... " + 274 | " ..... " + 275 | " ... " + 276 | " . " + 277 | " ")} 278 | 279 | } 280 | -------------------------------------------------------------------------------- /SDK/NuimoSwift.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NuimoSwift.swift 3 | // Nuimo 4 | // 5 | // Created by Lars Blumberg on 01/10/17. 6 | // Copyright © 2017 Senic. All rights reserved. 7 | // 8 | // This software may be modified and distributed under the terms 9 | // of the MIT license. See the LICENSE file for details. 10 | 11 | public struct NuimoSwift { 12 | public static var DDLogDebug: (_ message: String) -> Void = { message in } 13 | public static var DDLogError: (_ message: String) -> Void = { message in print(message) } 14 | } 15 | --------------------------------------------------------------------------------