├── Tests ├── en.lproj │ └── InfoPlist.strings ├── Spark_IOSTests.m └── Spark IOSTests-Info.plist ├── Interfaces ├── en.lproj │ └── InfoPlist.strings └── Base.lproj │ └── Main_iPad.storyboard ├── Tools └── moarfonts ├── Resources ├── cyan.png ├── gray.png ├── blank.png ├── emerald.png ├── white.png ├── alizarin.png ├── black-50.png ├── blank@2x.png ├── login-btn.png ├── sunflower.png ├── 09.02-gear.png ├── 20.01-pencil.png ├── 21.01-back.png ├── connect-btn.png ├── login-btn@2x.png ├── signup-btn.png ├── 01.03-spinner.png ├── 02.01-checkbox.png ├── 09.02-gear@2x.png ├── 21.01-back@2x.png ├── connect-btn@2x.png ├── not-found-box.png ├── not-found-btn.png ├── signup-btn@2x.png ├── 01.01-spark-logo.png ├── 01.03-spinner@2x.png ├── 02.01-checkbox@2x.png ├── 04.02-spark-logo.png ├── 08.01-point-at-d7.png ├── 09.01-hamburger.png ├── 09.03-core-shadow.png ├── 09.04-tinker-logo.png ├── 20.01-pencil@2x.png ├── 20.02-down-arrow.png ├── 20.02-right-arrow.png ├── not-found-box@2x.png ├── not-found-btn@2x.png ├── 01.01-spark-logo@2x.png ├── 04.02-spark-logo@2x.png ├── 05.01-check-circle.png ├── 09.01-hamburger@2x.png ├── 20.03-down-arrow@2x.png ├── core-subheading-bg.png ├── network-config-box.png ├── 03.01-login-background.png ├── 04.03-aes-check-circle.png ├── 05.01-check-circle@2x.png ├── 08.01-point-at-d7@2x.png ├── 09.03-core-shadow@2x.png ├── 09.04-tinker-logo@2x.png ├── 20.02-right-arrow@2x.png ├── network-config-box@2x.png ├── 01.02-signup-background.png ├── 01.02-signup-background@2x.png ├── 03.01-login-background@2x.png ├── 04.03-aes-check-circle@2x.png ├── 09.05-smart-config-button.png ├── 09.06-smart-config-clicked.png ├── 04.01-core-blurred-background.png ├── 09.05-smart-config-button@2x.png ├── 09.06-smart-config-clicked@2x.png ├── 04.01-core-blurred-background@2x.png └── Images.xcassets │ ├── AppIcon.appiconset │ ├── AppIcon29x29.png │ ├── AppIcon40x40.png │ ├── AppIcon76x76-1.png │ ├── AppIcon29x29@2x-1.png │ ├── AppIcon29x29@2x.png │ ├── AppIcon40x40@2x-1.png │ ├── AppIcon40x40@2x.png │ ├── AppIcon60x60@2x.png │ ├── AppIcon76x76@2x.png │ └── Contents.json │ └── LaunchImage.launchimage │ └── Contents.json ├── Vendor ├── TI │ ├── libFTC.a │ └── FirstTimeConfig.h ├── Lumberjack │ ├── DDASLLogger.h │ ├── DDASLLogger.m │ ├── DDAbstractDatabaseLogger.h │ └── DDTTYLogger.h ├── AFNetworking │ ├── AFNetworking.h │ ├── AFPropertyListRequestOperation.h │ ├── AFJSONRequestOperation.h │ ├── AFXMLRequestOperation.h │ ├── AFPropertyListRequestOperation.m │ ├── AFImageRequestOperation.h │ ├── AFHTTPRequestOperation.h │ ├── AFJSONRequestOperation.m │ └── AFXMLRequestOperation.m └── Apple │ └── KeychainItemWrapper.h ├── Categories ├── NSString+HexData.h ├── NSData+HexString.h ├── NSString+HexData.m └── NSData+HexString.m ├── App ├── SPKDevLogFormatter.h ├── SPKPrivateDefines.h ├── SPKAppDelegate.h ├── main.m ├── SPKSpark.h ├── Spark-Prefix.pch ├── SPKWebClient.h ├── SPKDevLogFormatter.m ├── Spark-Info.plist ├── SPKAppDelegate.m └── SPKSpark.m ├── .gitignore ├── Controllers ├── SPKLoadingViewController.h ├── SPKTimedOutViewController.h ├── SPKCoresViewController.h ├── SPKUIViewController.h ├── SPKSettingsViewController.h ├── SPKTimedOutViewController.m ├── SPKCoreNamingViewController.h ├── SPKLoginViewController.h ├── SPKTinkerViewController.h ├── SPKRegisterViewController.h ├── SPKNetworkSettingsViewController.h ├── SPKLoadingViewController.m ├── SPKSettingsViewController.m ├── SPKUIViewController.m ├── SPKCoresViewController.m ├── SPKLoginViewController.m ├── SPKCoreNamingViewController.m ├── SPKRegisterViewController.m ├── SPKTinkerViewController.m └── SPKNetworkSettingsViewController.m ├── Spark.entitlements ├── Models ├── SPKTimer.h ├── SPKUser.h ├── SPKCore.h ├── SPKSmartConfig.h ├── SPKTimer.m ├── SPKUser.m ├── SPKCorePin.h ├── SPKCorePin.m ├── SPKSmartConfig.m └── SPKCore.m ├── Views ├── SPKCoreCell.h ├── SPKCorePinView.h ├── SPKPinFunctionView.h ├── SPKPinFunctionView.m └── SPKCoreCell.m └── README.md /Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Interfaces/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Tools/moarfonts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Tools/moarfonts -------------------------------------------------------------------------------- /Resources/cyan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/cyan.png -------------------------------------------------------------------------------- /Resources/gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/gray.png -------------------------------------------------------------------------------- /Vendor/TI/libFTC.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Vendor/TI/libFTC.a -------------------------------------------------------------------------------- /Resources/blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/blank.png -------------------------------------------------------------------------------- /Resources/emerald.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/emerald.png -------------------------------------------------------------------------------- /Resources/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/white.png -------------------------------------------------------------------------------- /Resources/alizarin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/alizarin.png -------------------------------------------------------------------------------- /Resources/black-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/black-50.png -------------------------------------------------------------------------------- /Resources/blank@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/blank@2x.png -------------------------------------------------------------------------------- /Resources/login-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/login-btn.png -------------------------------------------------------------------------------- /Resources/sunflower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/sunflower.png -------------------------------------------------------------------------------- /Resources/09.02-gear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/09.02-gear.png -------------------------------------------------------------------------------- /Resources/20.01-pencil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/20.01-pencil.png -------------------------------------------------------------------------------- /Resources/21.01-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/21.01-back.png -------------------------------------------------------------------------------- /Resources/connect-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/connect-btn.png -------------------------------------------------------------------------------- /Resources/login-btn@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/login-btn@2x.png -------------------------------------------------------------------------------- /Resources/signup-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/signup-btn.png -------------------------------------------------------------------------------- /Resources/01.03-spinner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/01.03-spinner.png -------------------------------------------------------------------------------- /Resources/02.01-checkbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/02.01-checkbox.png -------------------------------------------------------------------------------- /Resources/09.02-gear@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/09.02-gear@2x.png -------------------------------------------------------------------------------- /Resources/21.01-back@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/21.01-back@2x.png -------------------------------------------------------------------------------- /Resources/connect-btn@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/connect-btn@2x.png -------------------------------------------------------------------------------- /Resources/not-found-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/not-found-box.png -------------------------------------------------------------------------------- /Resources/not-found-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/not-found-btn.png -------------------------------------------------------------------------------- /Resources/signup-btn@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/signup-btn@2x.png -------------------------------------------------------------------------------- /Vendor/TI/FirstTimeConfig.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Vendor/TI/FirstTimeConfig.h -------------------------------------------------------------------------------- /Resources/01.01-spark-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/01.01-spark-logo.png -------------------------------------------------------------------------------- /Resources/01.03-spinner@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/01.03-spinner@2x.png -------------------------------------------------------------------------------- /Resources/02.01-checkbox@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/02.01-checkbox@2x.png -------------------------------------------------------------------------------- /Resources/04.02-spark-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/04.02-spark-logo.png -------------------------------------------------------------------------------- /Resources/08.01-point-at-d7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/08.01-point-at-d7.png -------------------------------------------------------------------------------- /Resources/09.01-hamburger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/09.01-hamburger.png -------------------------------------------------------------------------------- /Resources/09.03-core-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/09.03-core-shadow.png -------------------------------------------------------------------------------- /Resources/09.04-tinker-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/09.04-tinker-logo.png -------------------------------------------------------------------------------- /Resources/20.01-pencil@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/20.01-pencil@2x.png -------------------------------------------------------------------------------- /Resources/20.02-down-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/20.02-down-arrow.png -------------------------------------------------------------------------------- /Resources/20.02-right-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/20.02-right-arrow.png -------------------------------------------------------------------------------- /Resources/not-found-box@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/not-found-box@2x.png -------------------------------------------------------------------------------- /Resources/not-found-btn@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/not-found-btn@2x.png -------------------------------------------------------------------------------- /Resources/01.01-spark-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/01.01-spark-logo@2x.png -------------------------------------------------------------------------------- /Resources/04.02-spark-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/04.02-spark-logo@2x.png -------------------------------------------------------------------------------- /Resources/05.01-check-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/05.01-check-circle.png -------------------------------------------------------------------------------- /Resources/09.01-hamburger@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/09.01-hamburger@2x.png -------------------------------------------------------------------------------- /Resources/20.03-down-arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/20.03-down-arrow@2x.png -------------------------------------------------------------------------------- /Resources/core-subheading-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/core-subheading-bg.png -------------------------------------------------------------------------------- /Resources/network-config-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/network-config-box.png -------------------------------------------------------------------------------- /Resources/03.01-login-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/03.01-login-background.png -------------------------------------------------------------------------------- /Resources/04.03-aes-check-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/04.03-aes-check-circle.png -------------------------------------------------------------------------------- /Resources/05.01-check-circle@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/05.01-check-circle@2x.png -------------------------------------------------------------------------------- /Resources/08.01-point-at-d7@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/08.01-point-at-d7@2x.png -------------------------------------------------------------------------------- /Resources/09.03-core-shadow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/09.03-core-shadow@2x.png -------------------------------------------------------------------------------- /Resources/09.04-tinker-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/09.04-tinker-logo@2x.png -------------------------------------------------------------------------------- /Resources/20.02-right-arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/20.02-right-arrow@2x.png -------------------------------------------------------------------------------- /Resources/network-config-box@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/network-config-box@2x.png -------------------------------------------------------------------------------- /Resources/01.02-signup-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/01.02-signup-background.png -------------------------------------------------------------------------------- /Categories/NSString+HexData.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSString (HexData) 4 | 5 | - (NSData *)dataFromHex; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /Resources/01.02-signup-background@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/01.02-signup-background@2x.png -------------------------------------------------------------------------------- /Resources/03.01-login-background@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/03.01-login-background@2x.png -------------------------------------------------------------------------------- /Resources/04.03-aes-check-circle@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/04.03-aes-check-circle@2x.png -------------------------------------------------------------------------------- /Resources/09.05-smart-config-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/09.05-smart-config-button.png -------------------------------------------------------------------------------- /Resources/09.06-smart-config-clicked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/09.06-smart-config-clicked.png -------------------------------------------------------------------------------- /Resources/04.01-core-blurred-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/04.01-core-blurred-background.png -------------------------------------------------------------------------------- /Resources/09.05-smart-config-button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/09.05-smart-config-button@2x.png -------------------------------------------------------------------------------- /Resources/09.06-smart-config-clicked@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/09.06-smart-config-clicked@2x.png -------------------------------------------------------------------------------- /Resources/04.01-core-blurred-background@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/04.01-core-blurred-background@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/AppIcon29x29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/Images.xcassets/AppIcon.appiconset/AppIcon29x29.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/AppIcon40x40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/Images.xcassets/AppIcon.appiconset/AppIcon40x40.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/AppIcon76x76-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/Images.xcassets/AppIcon.appiconset/AppIcon76x76-1.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/AppIcon29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/Images.xcassets/AppIcon.appiconset/AppIcon29x29@2x-1.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/AppIcon29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/Images.xcassets/AppIcon.appiconset/AppIcon29x29@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/AppIcon40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/Images.xcassets/AppIcon.appiconset/AppIcon40x40@2x-1.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/AppIcon40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/Images.xcassets/AppIcon.appiconset/AppIcon40x40@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/AppIcon60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/Images.xcassets/AppIcon.appiconset/AppIcon60x60@2x.png -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/AppIcon76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot-archived/spark-core-app-ios/HEAD/Resources/Images.xcassets/AppIcon.appiconset/AppIcon76x76@2x.png -------------------------------------------------------------------------------- /App/SPKDevLogFormatter.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKDevLogFormatter.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface SPKDevLogFormatter : NSObject 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /App/SPKPrivateDefines.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKPrivateDefines.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #define SPK_CLIENT_USERNAME @"spark" 9 | #define SPK_CLIENT_PASSWORD @"kuybsvbeu67ibf4cb7o8a3rn2och8nm9fofjnlf987h87bsh43NWJ" 10 | -------------------------------------------------------------------------------- /Categories/NSData+HexString.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+HexString.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface NSData (HexString) 11 | 12 | - (NSString *)hexString; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | .idea/ 19 | -------------------------------------------------------------------------------- /Controllers/SPKLoadingViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKLoadingViewController.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface SPKLoadingViewController : UIViewController 11 | 12 | - (void)showLogin; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /App/SPKAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKAppDelegate.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface SPKAppDelegate : UIResponder 11 | 12 | @property (strong, nonatomic) UIWindow *window; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Controllers/SPKTimedOutViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKTimedOutViewController.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface SPKTimedOutViewController : UIViewController 11 | 12 | @property (weak) IBOutlet UIButton *tinkerButton; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Spark.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | keychain-access-groups 6 | 7 | $(AppIdentifierPrefix)io.spark.sparkcore 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Models/SPKTimer.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKTimer.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | /* 11 | A GCD based recurring timer 12 | */ 13 | @interface SPKTimer : NSObject 14 | 15 | + (SPKTimer *)repeatingTimerWithTimeInterval:(NSTimeInterval)seconds queue:(dispatch_queue_t)queue block:(dispatch_block_t)block; 16 | 17 | - (void)invalidate; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Controllers/SPKCoresViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKCoresViewController.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface SPKCoresViewController : UIViewController 11 | 12 | @property (weak) IBOutlet UITableView *tableView; 13 | 14 | - (IBAction)expandToggled:(id)sender; 15 | - (IBAction)reflashTinker:(id)sender; 16 | - (IBAction)editName:(id)sender; 17 | - (IBAction)clearTinker:(id)sender; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Controllers/SPKUIViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKUIViewController.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | /* 11 | A base class for some of the other view controllers - mainly the user/login/account 12 | related ones. 13 | */ 14 | @interface SPKUIViewController : UIViewController 15 | 16 | - (CGFloat)keyboardHeightAdjust; 17 | - (void)dismissKeyboard; 18 | - (void)setViewMovedUp:(BOOL)movedUp; 19 | - (BOOL)isValidEmail:(NSString *)checkString; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Models/SPKUser.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKUser.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | /* 11 | This class manages the user and includes keychain wrapping. 12 | */ 13 | @interface SPKUser : NSObject 14 | 15 | @property (nonatomic, strong) NSString *userId; 16 | @property (nonatomic, strong) NSString *password; 17 | @property (nonatomic, strong) NSString *token; 18 | @property (nonatomic, assign) BOOL firstTime; 19 | 20 | - (BOOL)found; 21 | - (void)store; 22 | - (void)clear; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Controllers/SPKSettingsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKSettingsViewController.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface SPKSettingsViewController : UIViewController 11 | 12 | @property (weak) IBOutlet UILabel *userLabel; 13 | 14 | - (IBAction)logout:(id)sender; 15 | - (IBAction)supprt:(id)sender; 16 | - (IBAction)homepage:(id)sender; 17 | - (IBAction)buildAnApp:(id)sender; 18 | - (IBAction)documentation:(id)sender; 19 | - (IBAction)contribute:(id)sender; 20 | - (IBAction)reportBug:(id)sender; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /App/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | #import "SPKAppDelegate.h" 11 | #import "DDTTYLogger.h" 12 | #import "SPKDevLogFormatter.h" 13 | 14 | int main(int argc, char * argv[]) 15 | { 16 | @autoreleasepool { 17 | [[DDTTYLogger sharedInstance] setLogFormatter:[[SPKDevLogFormatter alloc] init]]; 18 | [DDLog addLogger:[DDTTYLogger sharedInstance]]; 19 | 20 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([SPKAppDelegate class])); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Controllers/SPKTimedOutViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKTimedOutViewController.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKTimedOutViewController.h" 9 | #import "SPKSpark.h" 10 | 11 | @interface SPKTimedOutViewController () 12 | 13 | @end 14 | 15 | @implementation SPKTimedOutViewController 16 | 17 | - (void)viewWillAppear:(BOOL)animated 18 | { 19 | [super viewWillAppear:animated]; 20 | 21 | self.tinkerButton.hidden = [SPKSpark sharedInstance].cores.count == 0; 22 | } 23 | 24 | - (UIStatusBarStyle)preferredStatusBarStyle 25 | { 26 | return UIStatusBarStyleLightContent; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Controllers/SPKCoreNamingViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKCoreNamingViewController.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKUIViewController.h" 9 | 10 | /* 11 | This class will name Core(s) as well as manage SmartConfig broadcasting 12 | */ 13 | @interface SPKCoreNamingViewController : SPKUIViewController 14 | 15 | @property (weak) IBOutlet UITextField *nameTextField; 16 | @property (weak) IBOutlet UILabel *coresPendingLabel; 17 | @property (weak) IBOutlet UILabel *infoLabel; 18 | @property (weak) IBOutlet UIButton *nameButton; 19 | @property (weak) IBOutlet UIImageView *spinnerImageView; 20 | 21 | - (IBAction)configureName:(id)sender; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Categories/NSString+HexData.m: -------------------------------------------------------------------------------- 1 | #import "NSString+HexData.h" 2 | 3 | @implementation NSString (HexData) 4 | 5 | - (NSData *)dataFromHex 6 | { 7 | NSUInteger len = [self length] / 2; 8 | unsigned char *buf = malloc(len); 9 | unsigned char *whole_byte = buf; 10 | char byte_chars[3] = {'\0','\0','\0'}; 11 | 12 | int i; 13 | for (i=0; i<[self length] / 2; i++) { 14 | byte_chars[0] = [self characterAtIndex:i*2]; 15 | byte_chars[1] = [self characterAtIndex:i*2+1]; 16 | *whole_byte = strtol(byte_chars, NULL, 16); 17 | whole_byte++; 18 | } 19 | 20 | NSData *data = [NSData dataWithBytes:buf length:len]; 21 | free(buf); 22 | 23 | return data; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Controllers/SPKLoginViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKLoginViewController.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKUIViewController.h" 9 | 10 | @interface SPKLoginViewController : SPKUIViewController 11 | 12 | @property (weak) IBOutlet UILabel *errorLabel; 13 | @property (weak) IBOutlet UITextField *userIdTextField; 14 | @property (weak) IBOutlet UITextField *passwordTextField; 15 | @property (weak) IBOutlet UIButton *loginButton; 16 | @property (weak) IBOutlet UIImageView *spinnerImageView; 17 | 18 | @property (weak) IBOutlet UIView *formView; 19 | @property (weak) IBOutlet UIImageView *logoImageView; 20 | 21 | - (IBAction)login:(id)sender; 22 | - (IBAction)forgot:(id)sender; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Tests/Spark_IOSTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // Spark_IOSTests.m 3 | // Spark IOSTests 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface Spark_IOSTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation Spark_IOSTests 15 | 16 | - (void)setUp 17 | { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown 23 | { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample 29 | { 30 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Models/SPKCore.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKCore.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | typedef NS_ENUM(uint8_t, SPKCoreState) { 11 | SPKCoreStateUnknown, 12 | SPKCoreStateHello, 13 | SPKCoreStateAttached, 14 | SPKCoreStateReady, 15 | SPKCoreStateAlreadyClaimed, 16 | SPKCoreStateFailed 17 | }; 18 | 19 | @interface SPKCore : NSObject 20 | 21 | @property (nonatomic, copy) NSData *coreId; 22 | @property (nonatomic, copy) NSString *name; 23 | @property (nonatomic, assign) SPKCoreState state; 24 | @property (nonatomic, readonly) UIColor *color; 25 | @property (nonatomic, assign) BOOL connected; 26 | 27 | @property (nonatomic, readonly) NSArray *pins; 28 | 29 | - (void)reset; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /Tests/Spark IOSTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.sparkdevices.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Categories/NSData+HexString.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+HexString.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "NSData+HexString.h" 9 | 10 | @implementation NSData (HexString) 11 | 12 | - (NSString *)hexString 13 | { 14 | const unsigned char *dataBuffer = (const unsigned char *)[self bytes]; 15 | 16 | if (!dataBuffer) { 17 | return [NSString string]; 18 | } 19 | 20 | NSUInteger dataLength = [self length]; 21 | NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)]; 22 | 23 | for (int i = 0; i < dataLength; ++i) { 24 | [hexString appendString:[NSString stringWithFormat:@"%02lx", (unsigned long)dataBuffer[i]]]; 25 | } 26 | 27 | return [NSString stringWithString:hexString]; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Controllers/SPKTinkerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKTinkerViewController.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "SPKCorePinView.h" 10 | #import "SPKPinFunctionView.h" 11 | 12 | /* 13 | This controller manages all aspects of Tinker including sub views via delegates. Any Tinker 14 | functionallity should following the same delegate pattern. 15 | */ 16 | @interface SPKTinkerViewController : UIViewController 17 | 18 | @property (weak) IBOutlet SPKPinFunctionView *pinFunctionView; 19 | @property (weak) IBOutlet UILabel *nameLabel; 20 | @property (weak) IBOutlet UIView *firstTimeView; 21 | @property (weak) IBOutlet UIImageView *tinkerLogoImageView; 22 | @property (weak) IBOutlet UIImageView *shadowImageView; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Views/SPKCoreCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKCoreCell.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "SPKCore.h" 10 | 11 | /* 12 | A cell for showing a core as seen in the basement view of cores 13 | */ 14 | @interface SPKCoreCell : UITableViewCell 15 | 16 | @property (nonatomic, strong) SPKCore *core; 17 | 18 | @property (nonatomic, assign) BOOL expanded; 19 | @property (nonatomic, assign) NSUInteger index; 20 | 21 | @property (weak) IBOutlet UILabel *nameLabel; 22 | @property (weak) IBOutlet UILabel *connectLabel; 23 | @property (weak) IBOutlet UIImageView *grayLineImageView; 24 | @property (weak) IBOutlet UIButton *editButton; 25 | @property (weak) IBOutlet UIButton *expandButton; 26 | @property (weak) IBOutlet UIImageView *spinnerImageView; 27 | 28 | - (void)expand; 29 | - (void)contract; 30 | - (void)spinSpinner:(BOOL)go; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Models/SPKSmartConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKSmartConfig.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | #define kSPKSmartConfigConfigCore @"kSPKSmartConfigConfigCore" 11 | #define kSPKSmartConfigHelloCore @"kSPKSmartConfigHelloCore" 12 | 13 | /* 14 | This is a custom implementation of TI's SmartConfig protocol. This implementation uses GCD and 15 | GCDAsyncSocket for better responsiveness and mananagement. 16 | 17 | */ 18 | @interface SPKSmartConfig : NSObject 19 | 20 | @property (nonatomic, copy) NSString *wifiPassword; 21 | @property (nonatomic, copy) NSString *aesKey; 22 | @property (nonatomic, readonly) BOOL isBroadcasting; 23 | 24 | - (void)configureWithPassword:(NSString *)wifiPassword aesKey:(NSString *)aesKey; 25 | - (void)startTransmittingSettings; 26 | - (void)stopTransmittingSettings; 27 | - (void)stopCoAPListening; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Controllers/SPKRegisterViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKRegisterViewController.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKUIViewController.h" 9 | 10 | @interface SPKRegisterViewController : SPKUIViewController 11 | 12 | @property (weak) IBOutlet UILabel *errorLabel; 13 | @property (weak) IBOutlet UITextField *userIdTextField; 14 | @property (weak) IBOutlet UITextField *passwordTextField; 15 | @property (weak) IBOutlet UIButton *registerButton; 16 | @property (weak) IBOutlet UIImageView *spinnerImageView; 17 | 18 | @property (weak) IBOutlet UIButton *termsButton; 19 | @property (weak) IBOutlet UIButton *privacyButton; 20 | 21 | @property (weak) IBOutlet UIView *formView; 22 | @property (weak) IBOutlet UIView *legalView; 23 | @property (weak) IBOutlet UIImageView *logoImageView; 24 | 25 | - (IBAction)register:(id)sender; 26 | - (IBAction)terms:(id)sender; 27 | - (IBAction)privacy:(id)sender; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /App/SPKSpark.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKSpark.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | #import "SPKUser.h" 11 | #import "SPKWebClient.h" 12 | #import "SPKSmartConfig.h" 13 | #import "SPKCore.h" 14 | 15 | /* 16 | This is the main class for the application. It should only used as a singleton 17 | and any class should feel free to access it. There is no initialization needs 18 | to be done on it. 19 | */ 20 | @interface SPKSpark : NSObject 21 | 22 | @property (nonatomic, readonly) SPKUser *user; 23 | @property (nonatomic, assign) BOOL attemptedLogin; 24 | @property (nonatomic, readonly) SPKWebClient *webClient; 25 | @property (nonatomic, readonly) SPKSmartConfig *smartConfig; 26 | @property (nonatomic, readonly) SPKCore *activeCore; 27 | 28 | + (SPKSpark *)sharedInstance; 29 | 30 | - (void)clearCores; 31 | - (NSArray *)cores; 32 | - (void)addCore:(SPKCore *)core; 33 | - (NSArray *)coresInState:(SPKCoreState)state; 34 | - (void)activateCore:(SPKCore *)core; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /Controllers/SPKNetworkSettingsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKNetworkSettingsViewController.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKUIViewController.h" 9 | #import "SPKSmartConfig.h" 10 | 11 | /* 12 | This class will initiate SmartConfig broadcasting and listening 13 | */ 14 | @interface SPKNetworkSettingsViewController : SPKUIViewController 15 | 16 | @property (weak) IBOutlet UITextField *ssidTextField; 17 | @property (weak) IBOutlet UITextField *passwordTextField; 18 | @property (weak) IBOutlet UITextField *keyTextField; 19 | @property (weak) IBOutlet UIButton *connectButton; 20 | @property (weak) IBOutlet UIButton *aesKeyButton; 21 | @property (weak) IBOutlet UILabel *messageLabel; 22 | @property (weak) IBOutlet UIImageView *keyBackgroundImageView; 23 | @property (weak) IBOutlet UIButton *logoutButton; 24 | @property (weak) IBOutlet UIButton *tinkerButton; 25 | @property (weak) IBOutlet UIImageView *spinnerImageView; 26 | 27 | - (IBAction)aesKeyToggle:(id)sender; 28 | - (IBAction)connect:(id)sender; 29 | - (IBAction)logout:(id)sender; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /App/Spark-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #import 17 | #import 18 | 19 | #import "DDLog.h" 20 | 21 | #define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0] 22 | #define isiPhone5 (([[UIScreen mainScreen] bounds].size.height == 568)?TRUE:FALSE) 23 | 24 | #ifdef DEBUG 25 | static const int ddLogLevel = LOG_LEVEL_VERBOSE; 26 | #define _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_ 27 | #else 28 | static const int ddLogLevel = LOG_LEVEL_INFO; 29 | #endif 30 | 31 | #import "SPKPrivateDefines.h" 32 | #endif 33 | -------------------------------------------------------------------------------- /Views/SPKCorePinView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKCorePinView.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "SPKCorePin.h" 10 | 11 | /* 12 | A rather complicated class to programatically draw a pin. It 13 | takes into account tall and short iPhones. It is all based on 14 | layers with each layer displaying a different type of interaction. 15 | */ 16 | @class SPKCorePinView; 17 | 18 | @protocol SPKCorePinViewDelegate 19 | 20 | - (void)pinViewTapped:(SPKCorePinView *)pinView inPin:(BOOL)inPin; 21 | - (void)pinViewAdjusted:(SPKCorePinView *)pinView newValue:(NSUInteger)newValue; 22 | - (void)pinViewHeld:(SPKCorePinView *)pinView; 23 | 24 | @end 25 | 26 | @interface SPKCorePinView : UIView 27 | 28 | @property (nonatomic, strong) SPKCorePin *pin; 29 | @property (nonatomic, readonly) BOOL active; 30 | @property (nonatomic, readonly) BOOL sliding; 31 | @property (weak) NSObject *delegate; 32 | 33 | - (void)refresh; 34 | - (void)deactivate; 35 | - (void)activate; 36 | - (void)hideDetails; 37 | - (void)showDetails; 38 | - (void)noslider; 39 | - (void)slider; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Views/SPKPinFunctionView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKPinFunctionView.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | #import "SPKCorePin.h" 10 | 11 | @protocol SPKPinFunctionDelegate 12 | 13 | - (void)pinFunctionSelected:(SPKCorePinFunction)function; 14 | 15 | @end 16 | 17 | /* 18 | A view to select a pins function. 19 | */ 20 | @interface SPKPinFunctionView : UIView 21 | 22 | @property (weak) IBOutlet UILabel *pinLabel; 23 | @property (weak) IBOutlet UIImageView *analogReadImageView; 24 | @property (weak) IBOutlet UIButton *analogReadHighButton; 25 | @property (weak) IBOutlet UIButton *analogReadButton; 26 | @property (weak) IBOutlet UIImageView *analogWriteImageView; 27 | @property (weak) IBOutlet UIButton *analogWriteButton; 28 | @property (weak) IBOutlet UIImageView *digitalReadImageView; 29 | @property (weak) IBOutlet UIButton *digitalReadHighButton; 30 | @property (weak) IBOutlet UIButton *digitalReadButton; 31 | @property (weak) IBOutlet UIImageView *digitalWriteImageView; 32 | @property (weak) IBOutlet UIButton *digitalWriteButton; 33 | 34 | @property (nonatomic, strong) SPKCorePin *pin; 35 | @property (nonatomic, weak) NSObject *delegate; 36 | 37 | - (IBAction)functionSelected:(id)sender; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Models/SPKTimer.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKTimer.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKTimer.h" 9 | 10 | @interface SPKTimer () 11 | 12 | @property (nonatomic, copy) dispatch_block_t block; 13 | @property (nonatomic, strong) dispatch_source_t source; 14 | 15 | @end 16 | 17 | @implementation SPKTimer 18 | 19 | + (SPKTimer *)repeatingTimerWithTimeInterval:(NSTimeInterval)seconds queue:(dispatch_queue_t)queue block:(dispatch_block_t)block 20 | { 21 | NSParameterAssert(seconds); 22 | NSParameterAssert(block); 23 | 24 | SPKTimer *timer = [[SPKTimer alloc] init]; 25 | timer.block = block; 26 | timer.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); 27 | uint64_t nsec = (uint64_t)(seconds * NSEC_PER_SEC); 28 | dispatch_source_set_timer(timer.source, dispatch_time(DISPATCH_TIME_NOW, nsec), nsec, 0); 29 | dispatch_source_set_event_handler(timer.source, block); 30 | dispatch_resume(timer.source); 31 | return timer; 32 | } 33 | 34 | - (void)invalidate 35 | { 36 | if (self.source) { 37 | dispatch_source_cancel(self.source); 38 | self.source = nil; 39 | } 40 | self.block = nil; 41 | } 42 | 43 | - (void)dealloc 44 | { 45 | [self invalidate]; 46 | } 47 | 48 | - (void)fire 49 | { 50 | self.block(); 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /Resources/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } -------------------------------------------------------------------------------- /Vendor/Lumberjack/DDASLLogger.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import "DDLog.h" 5 | 6 | /** 7 | * Welcome to Cocoa Lumberjack! 8 | * 9 | * The project page has a wealth of documentation if you have any questions. 10 | * https://github.com/robbiehanson/CocoaLumberjack 11 | * 12 | * If you're new to the project you may wish to read the "Getting Started" wiki. 13 | * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted 14 | * 15 | * 16 | * This class provides a logger for the Apple System Log facility. 17 | * 18 | * As described in the "Getting Started" page, 19 | * the traditional NSLog() function directs it's output to two places: 20 | * 21 | * - Apple System Log 22 | * - StdErr (if stderr is a TTY) so log statements show up in Xcode console 23 | * 24 | * To duplicate NSLog() functionality you can simply add this logger and a tty logger. 25 | * However, if you instead choose to use file logging (for faster performance), 26 | * you may choose to use a file logger and a tty logger. 27 | **/ 28 | 29 | @interface DDASLLogger : DDAbstractLogger 30 | { 31 | aslclient client; 32 | } 33 | 34 | + (DDASLLogger *)sharedInstance; 35 | 36 | // Inherited from DDAbstractLogger 37 | 38 | // - (id )logFormatter; 39 | // - (void)setLogFormatter:(id )formatter; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spark iOS Application 2 | 3 | This is the open source version of the iOS app from Spark Labs, Inc. for controlling the Spark Core. It provides key functionality of SmartConfig and Tinker. 4 | 5 | ## Building 6 | 7 | *USERS MUST DOWNLOAD THE SMART CONFIG LIBRARY FROM TEXAS INSTRUMENTS, AGREE TO THAT LICENSE, AND ADD IT TO THIS APP. MORE THOROUGH INSTRUCTIONS WILL BE PROVIDED IN THE FUTURE. FEEL FREE TO SUBMIT PULL REQUESTS WITH GOOD BUILD INSTRUCTIONS.* :smile: 8 | 9 | This project is designed to be build againt iOS 7 SDK and will not work with others. It does makes use of third-party 10 | libraries which are bundled. Those include GCDAsyncSocket and CocoaLumberjack. 11 | 12 | ## Design 13 | 14 | This application does not save any data locally except account information (which is saved in Keychain). All data is 15 | retrieved from the Spark Core API at launch. 16 | 17 | Key classes include 18 | 19 | ### Logic 20 | 21 | * SPKSpark - Singleton class that manages the list of Cores, active Core and SmartConfig 22 | * SPKWebClient - All API calls are made and processed here 23 | * SPKSmartConfig / SPKSmartConfigPayload - TI SmartConfig custom implementation and logic 24 | * SPKUser - Spark account management 25 | 26 | ### UI 27 | 28 | * Storyboard - All UI is implement via a Storyboard 29 | * SPKCorePinView - Programatic view implemented for all pin functionality / display 30 | -------------------------------------------------------------------------------- /App/SPKWebClient.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKWebClient.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "AFHTTPClient.h" 9 | #import "SPKUser.h" 10 | #import "SPKCorePin.h" 11 | 12 | #define kSPKWebClientAuthenticationError @"SPKWebClientAuthenticationError" 13 | #define kSPKWebClientConnectionError @"SPKWebClientConnectionError" 14 | #define kSPKWebClientReachabilityChange @"kSPKWebClientReachabilityChange" 15 | 16 | @interface SPKWebClient : AFHTTPClient 17 | 18 | - (id)initWithUser:(SPKUser *)user; 19 | 20 | - (void)login:(void (^)(NSString *))authToken failure:(void (^)(NSString *))message; 21 | - (void)register:(void (^)(NSString *))success failure:(void (^)(NSString *))failure; 22 | - (void)attach:(NSData *)coreId success:(void (^)(NSData *))success offline:(void (^)(void))offline alreadyClaimed:(void (^)(void))alreadyClaimed failure:(void (^)(NSString *message))failure; 23 | - (void)cores:(void (^)(NSArray *))cores failure:(void (^)(void))failure; 24 | - (void)signal:(NSData *)coreId on:(BOOL)on; 25 | - (void)name:(NSData *)coreId label:(NSString *)label success:(void (^)(void))success failure:(void (^)(void))failure; 26 | - (void)flashTinker:(NSData *)coreId success:(void (^)(void))success failure:(void (^)(void))failure; 27 | - (void)coreId:(NSData *)coreId pin:(NSString *)pin function:(SPKCorePinFunction)function value:(NSUInteger)value success:(void (^)(NSUInteger value))success failure:(void (^)(NSString *error))failure; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Resources/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "AppIcon29x29@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "40x40", 11 | "idiom" : "iphone", 12 | "filename" : "AppIcon40x40@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "60x60", 17 | "idiom" : "iphone", 18 | "filename" : "AppIcon60x60@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "ipad", 24 | "filename" : "AppIcon29x29.png", 25 | "scale" : "1x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "ipad", 30 | "filename" : "AppIcon29x29@2x-1.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "ipad", 36 | "filename" : "AppIcon40x40.png", 37 | "scale" : "1x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "ipad", 42 | "filename" : "AppIcon40x40@2x-1.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "76x76", 47 | "idiom" : "ipad", 48 | "filename" : "AppIcon76x76-1.png", 49 | "scale" : "1x" 50 | }, 51 | { 52 | "size" : "76x76", 53 | "idiom" : "ipad", 54 | "filename" : "AppIcon76x76@2x.png", 55 | "scale" : "2x" 56 | } 57 | ], 58 | "info" : { 59 | "version" : 1, 60 | "author" : "xcode" 61 | } 62 | } -------------------------------------------------------------------------------- /Models/SPKUser.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKUser.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKUser.h" 9 | #import "KeychainItemWrapper.h" 10 | 11 | #define KEYCHAIN_SERVICE @"Spark API" 12 | #define KEYCHAIN_IDENTIFIER @"Account Information" 13 | 14 | @interface SPKUser () 15 | 16 | @property (nonatomic, strong) KeychainItemWrapper *keychainWrapper; 17 | 18 | @end 19 | 20 | @implementation SPKUser 21 | 22 | - (id)init 23 | { 24 | if (self = [super init]) { 25 | _keychainWrapper = [[KeychainItemWrapper alloc] initWithAccount:KEYCHAIN_IDENTIFIER service:KEYCHAIN_SERVICE accessGroup:nil]; 26 | _userId = [self.keychainWrapper objectForKey:(__bridge id)(kSecAttrGeneric)]; 27 | _token = [self.keychainWrapper objectForKey:(__bridge id)(kSecValueData)]; 28 | } 29 | 30 | return self; 31 | } 32 | 33 | - (BOOL)found 34 | { 35 | return (self.userId != nil) && ([self.userId length]) && (self.token != nil) && ([self.token length]); 36 | } 37 | 38 | - (void)store 39 | { 40 | [self.keychainWrapper setObject:self.userId forKey:(__bridge id)(kSecAttrGeneric)]; 41 | [self.keychainWrapper setObject:self.token forKey:(__bridge id)(kSecValueData)]; 42 | } 43 | 44 | // Reset the values in the keychain item, or create a new item if it doesn't already exist 45 | - (void)clear 46 | { 47 | [self.keychainWrapper resetKeychainItem]; 48 | self.keychainWrapper = [[KeychainItemWrapper alloc] initWithAccount:KEYCHAIN_IDENTIFIER service:KEYCHAIN_SERVICE accessGroup:nil]; 49 | self.userId = nil; 50 | self.password = nil; 51 | self.token = nil; 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Controllers/SPKLoadingViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKLoadingViewController.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKLoadingViewController.h" 9 | #import "SPKSpark.h" 10 | 11 | @interface SPKLoadingViewController () 12 | 13 | @end 14 | 15 | @implementation SPKLoadingViewController 16 | 17 | - (void)viewDidAppear:(BOOL)animated 18 | { 19 | [super viewDidAppear:animated]; 20 | 21 | if (![[SPKSpark sharedInstance].user found]) { 22 | if ([[SPKSpark sharedInstance] attemptedLogin]) { 23 | [self performSegueWithIdentifier:@"login" sender:nil]; 24 | } else { 25 | [self performSegueWithIdentifier:@"register" sender:nil]; 26 | } 27 | } else { 28 | [[SPKSpark sharedInstance].webClient cores:^(NSArray *cores) { 29 | dispatch_async(dispatch_get_main_queue(), ^{ 30 | for (SPKCore *core in cores) { 31 | [[SPKSpark sharedInstance] addCore:core]; 32 | [[SPKSpark sharedInstance] activateCore:core]; 33 | 34 | } 35 | 36 | if ([SPKSpark sharedInstance].cores.count) { 37 | [self performSegueWithIdentifier:@"cores" sender:nil]; 38 | } else { 39 | [self performSegueWithIdentifier:@"settings" sender:nil]; 40 | } 41 | }); 42 | } failure:^{ 43 | DDLogError(@"Problem getting list of cores"); 44 | }]; 45 | } 46 | } 47 | 48 | - (UIStatusBarStyle)preferredStatusBarStyle 49 | { 50 | return UIStatusBarStyleLightContent; 51 | } 52 | 53 | - (void)showLogin 54 | { 55 | [self performSegueWithIdentifier:@"login" sender:nil]; 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /App/SPKDevLogFormatter.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKDevLogFormatter.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKDevLogFormatter.h" 9 | 10 | @interface SPKDevLogFormatter () 11 | 12 | @property (assign) uint8_t loggerCount; 13 | @property (nonatomic, strong) NSDateFormatter *threadUnsafeDateFormatter; 14 | 15 | @end 16 | 17 | @implementation SPKDevLogFormatter 18 | 19 | - (id)init 20 | { 21 | if ((self = [super init])) { 22 | _threadUnsafeDateFormatter = [[NSDateFormatter alloc] init]; 23 | [_threadUnsafeDateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; 24 | [_threadUnsafeDateFormatter setDateFormat:@"HH:mm:ss:SSS"]; 25 | } 26 | return self; 27 | } 28 | 29 | - (NSString *)formatLogMessage:(DDLogMessage *)logMessage 30 | { 31 | NSString *logLevel; 32 | switch (logMessage->logFlag) 33 | { 34 | case LOG_FLAG_ERROR : logLevel = @"E"; break; 35 | case LOG_FLAG_WARN : logLevel = @"W"; break; 36 | case LOG_FLAG_INFO : logLevel = @"I"; break; 37 | default : logLevel = @"V"; break; 38 | } 39 | 40 | NSString *dateAndTime = [self.threadUnsafeDateFormatter stringFromDate:(logMessage->timestamp)]; 41 | NSString *file = [[[NSString stringWithUTF8String:logMessage->file] componentsSeparatedByString:@"/"] lastObject]; 42 | 43 | return [NSString stringWithFormat:@"%@|%@|%@:%d|%s|%@", logLevel, dateAndTime, file, logMessage->lineNumber, logMessage->function, logMessage->logMsg]; 44 | } 45 | 46 | - (void)didAddToLogger:(id )logger 47 | { 48 | self.loggerCount++; 49 | NSAssert(self.loggerCount <= 1, @"This logger isn't thread-safe"); 50 | } 51 | 52 | - (void)willRemoveFromLogger:(id )logger 53 | { 54 | self.loggerCount--; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /Vendor/AFNetworking/AFNetworking.h: -------------------------------------------------------------------------------- 1 | // AFNetworking.h 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | #import 24 | #import 25 | 26 | #ifndef _AFNETWORKING_ 27 | #define _AFNETWORKING_ 28 | 29 | #import "AFURLConnectionOperation.h" 30 | 31 | #import "AFHTTPRequestOperation.h" 32 | #import "AFJSONRequestOperation.h" 33 | #import "AFXMLRequestOperation.h" 34 | #import "AFPropertyListRequestOperation.h" 35 | #import "AFHTTPClient.h" 36 | 37 | #import "AFImageRequestOperation.h" 38 | 39 | #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) 40 | #import "AFNetworkActivityIndicatorManager.h" 41 | #import "UIImageView+AFNetworking.h" 42 | #endif 43 | #endif /* _AFNETWORKING_ */ 44 | -------------------------------------------------------------------------------- /Controllers/SPKSettingsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKSettingsViewController.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKSettingsViewController.h" 9 | #import "SPKSpark.h" 10 | #import "SPKLoadingViewController.h" 11 | 12 | @interface SPKSettingsViewController () 13 | 14 | @end 15 | 16 | @implementation SPKSettingsViewController 17 | 18 | - (void)viewWillAppear:(BOOL)animated 19 | { 20 | [super viewWillAppear:animated]; 21 | 22 | self.userLabel.text = [SPKSpark sharedInstance].user.userId; 23 | } 24 | 25 | - (UIStatusBarStyle)preferredStatusBarStyle 26 | { 27 | return UIStatusBarStyleLightContent; 28 | } 29 | 30 | - (IBAction)logout:(id)sender 31 | { 32 | if ([SPKSpark sharedInstance].user) { 33 | [[SPKSpark sharedInstance].user clear]; 34 | [[SPKSpark sharedInstance] clearCores]; 35 | [self performSegueWithIdentifier:@"login" sender:sender]; 36 | } 37 | } 38 | 39 | - (IBAction)supprt:(id)sender 40 | { 41 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://www.spark.io/support"]]; 42 | } 43 | 44 | - (IBAction)homepage:(id)sender 45 | { 46 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://www.spark.io"]]; 47 | } 48 | 49 | - (IBAction)buildAnApp:(id)sender 50 | { 51 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://www.spark.io/build"]]; 52 | } 53 | 54 | - (IBAction)documentation:(id)sender 55 | { 56 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://docs.spark.io/"]]; 57 | } 58 | 59 | - (IBAction)contribute:(id)sender 60 | { 61 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://spark.github.io/"]]; 62 | } 63 | 64 | - (IBAction)reportBug:(id)sender 65 | { 66 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://www.github.com/spark/ios-app/issues"]]; 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /App/Spark-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Spark Core 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | io.spark.sparkcore 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | sparkcore 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | Main_iPhone 29 | UIMainStoryboardFile~ipad 30 | Main_iPad 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UIStatusBarStyle 36 | UIStatusBarStyleDefault 37 | UIStatusBarTintParameters 38 | 39 | UINavigationBar 40 | 41 | Style 42 | UIBarStyleDefault 43 | Translucent 44 | 45 | 46 | 47 | UISupportedInterfaceOrientations 48 | 49 | UIInterfaceOrientationPortrait 50 | 51 | UISupportedInterfaceOrientations~ipad 52 | 53 | UIInterfaceOrientationPortrait 54 | UIInterfaceOrientationPortraitUpsideDown 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /App/SPKAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKAppDelegate.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKAppDelegate.h" 9 | #import "SPKSpark.h" 10 | #import "SPKLoadingViewController.h" 11 | 12 | @implementation SPKAppDelegate 13 | 14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 15 | { 16 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(authenticationError:) name:kSPKWebClientAuthenticationError object:nil]; 17 | 18 | SPKUser *user = [SPKSpark sharedInstance].user; 19 | 20 | if ([user found]) { 21 | DDLogInfo(@"Found User: %@", user.userId); 22 | } else { 23 | DDLogInfo(@"No User Found"); 24 | } 25 | 26 | return YES; 27 | } 28 | 29 | - (void)applicationDidBecomeActive:(UIApplication *)application 30 | { 31 | NSNotification *notification = [NSNotification notificationWithName:kSPKWebClientReachabilityChange object:nil userInfo:@{ }]; 32 | [[NSNotificationCenter defaultCenter] postNotification:notification]; 33 | } 34 | 35 | // This is called when an authentication error occurs from the web client 36 | - (void)authenticationError:(NSNotification *)notification 37 | { 38 | [SPKSpark sharedInstance].user.token = nil; 39 | 40 | dispatch_async(dispatch_get_main_queue(), ^{ 41 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Spark Web" message:@"Authentication Error" delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil]; 42 | [alert show]; 43 | }); 44 | } 45 | 46 | // After acknowledging the authentication error, go to the login screen 47 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex 48 | { 49 | SPKLoadingViewController *vc = (SPKLoadingViewController *)self.window.rootViewController; 50 | if (vc.presentedViewController) { 51 | [vc dismissViewControllerAnimated:YES completion:nil]; 52 | } else { 53 | [vc showLogin]; 54 | } 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /Models/SPKCorePin.h: -------------------------------------------------------------------------------- 1 | // 2 | // SPKCorePin.h 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | typedef NS_ENUM(uint8_t, SPKCorePinSide) 11 | { 12 | SPKCorePinSideLeft, 13 | SPKCorePinSideRight 14 | }; 15 | 16 | typedef NS_OPTIONS(uint8_t, SPKCorePinFunction) 17 | { 18 | SPKCorePinFunctionNone = 0, 19 | SPKCorePinFunctionDigitalRead = 1 << 0, 20 | SPKCorePinFunctionDigitalWrite = 1 << 1, 21 | SPKCorePinFunctionAnalogRead = 1 << 2, 22 | SPKCorePinFunctionAnalogWrite = 1 << 3 23 | }; 24 | 25 | #define SPKCorePinFunctionNoneColor [UIColor clearColor] 26 | #define SPKCorePinFunctionDigitalReadColor [UIColor colorWithRed:0.0 green:0.67 blue:0.93 alpha:1.0] 27 | #define SPKCorePinFunctionDigitalWriteColor [UIColor colorWithRed:0.91 green:0.30 blue:0.24 alpha:1.0] 28 | #define SPKCorePinFunctionAnalogReadColor [UIColor colorWithRed:0.18 green:0.8 blue:0.44 alpha:1.0] 29 | #define SPKCorePinFunctionAnalogWriteColor [UIColor colorWithRed:0.95 green:0.77 blue:0.06 alpha:1.0] 30 | 31 | #define SPKCorePinFunctionAnalog(pin) ((pin.selectedFunction == SPKCorePinFunctionAnalogRead) || (pin.selectedFunction == SPKCorePinFunctionAnalogWrite)) 32 | #define SPKCorePinFunctionDigital(pin) ((pin.selectedFunction == SPKCorePinFunctionDigitalRead) || (pin.selectedFunction == SPKCorePinFunctionDigitalWrite)) 33 | #define SPKCorePinFunctionNothing(pin) (pin.selectedFunction == SPKCorePinFunctionNone) 34 | 35 | @interface SPKCorePin : NSObject 36 | 37 | @property (nonatomic, readonly) NSString *label; 38 | @property (nonatomic, readonly) SPKCorePinSide side; 39 | @property (nonatomic, readonly) NSUInteger row; 40 | @property (nonatomic, readonly) SPKCorePinFunction availableFunctions; 41 | @property (nonatomic, assign) SPKCorePinFunction selectedFunction; 42 | @property (nonatomic, readonly) BOOL valueSet; 43 | @property (nonatomic, readonly) NSUInteger value; 44 | 45 | - (id)initWithLabel:(NSString *)label side:(SPKCorePinSide)side row:(NSUInteger)row availableFunctions:(SPKCorePinFunction)availableFunctions; 46 | 47 | - (void)resetValue; 48 | - (void)adjustValue:(NSUInteger)newValue; 49 | - (UIColor *)selectedFunctionColor; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Models/SPKCorePin.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKCorePin.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKCorePin.h" 9 | 10 | @interface SPKCorePin () 11 | 12 | @property (nonatomic, assign) BOOL valueSet; 13 | @property (nonatomic, assign) NSUInteger value; 14 | 15 | @end 16 | 17 | @implementation SPKCorePin 18 | 19 | - (id)initWithLabel:(NSString *)label side:(SPKCorePinSide)side row:(NSUInteger)row availableFunctions:(SPKCorePinFunction)availableFunctions 20 | { 21 | if (self = [super init]) { 22 | _label = label; 23 | _side = side; 24 | _row = row; 25 | _availableFunctions = availableFunctions; 26 | _selectedFunction = SPKCorePinFunctionNone; 27 | 28 | 29 | // SPKCorePinFunction functions[] = { SPKCorePinFunctionNone, SPKCorePinFunctionAnalogRead, SPKCorePinFunctionAnalogWrite, SPKCorePinFunctionDigitalRead, SPKCorePinFunctionDigitalWrite }; 30 | // BOOL stop; 31 | // do { 32 | // SPKCorePinFunction randomFunction = functions[rand() % 5]; 33 | // if ((randomFunction & availableFunctions) == randomFunction) { 34 | // _selectedFunction = randomFunction; 35 | // stop = YES; 36 | // } 37 | // } while (!stop); 38 | // 39 | // if (SPKCorePinFunctionAnalog(self)) { 40 | // _value = rand() % 1000; 41 | // } else { 42 | // _value = rand() % 2 == 0; 43 | // } 44 | } 45 | 46 | return self; 47 | } 48 | 49 | - (void)resetValue 50 | { 51 | self.valueSet = NO; 52 | self.value = 0; 53 | } 54 | 55 | - (void)adjustValue:(NSUInteger)newValue 56 | { 57 | self.value = newValue; 58 | self.valueSet = YES; 59 | } 60 | 61 | - (UIColor *)selectedFunctionColor 62 | { 63 | switch (self.selectedFunction) { 64 | case SPKCorePinFunctionDigitalRead: 65 | return SPKCorePinFunctionDigitalReadColor; 66 | case SPKCorePinFunctionDigitalWrite: 67 | return SPKCorePinFunctionDigitalWriteColor; 68 | case SPKCorePinFunctionAnalogRead: 69 | return SPKCorePinFunctionAnalogReadColor; 70 | case SPKCorePinFunctionAnalogWrite: 71 | return SPKCorePinFunctionAnalogWriteColor; 72 | default: 73 | return SPKCorePinFunctionNoneColor; 74 | } 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /Vendor/Lumberjack/DDASLLogger.m: -------------------------------------------------------------------------------- 1 | #import "DDASLLogger.h" 2 | 3 | #import 4 | 5 | /** 6 | * Welcome to Cocoa Lumberjack! 7 | * 8 | * The project page has a wealth of documentation if you have any questions. 9 | * https://github.com/robbiehanson/CocoaLumberjack 10 | * 11 | * If you're new to the project you may wish to read the "Getting Started" wiki. 12 | * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted 13 | **/ 14 | 15 | #if ! __has_feature(objc_arc) 16 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 17 | #endif 18 | 19 | 20 | @implementation DDASLLogger 21 | 22 | static DDASLLogger *sharedInstance; 23 | 24 | /** 25 | * The runtime sends initialize to each class in a program exactly one time just before the class, 26 | * or any class that inherits from it, is sent its first message from within the program. (Thus the 27 | * method may never be invoked if the class is not used.) The runtime sends the initialize message to 28 | * classes in a thread-safe manner. Superclasses receive this message before their subclasses. 29 | * 30 | * This method may also be called directly (assumably by accident), hence the safety mechanism. 31 | **/ 32 | + (void)initialize 33 | { 34 | static BOOL initialized = NO; 35 | if (!initialized) 36 | { 37 | initialized = YES; 38 | 39 | sharedInstance = [[DDASLLogger alloc] init]; 40 | } 41 | } 42 | 43 | + (DDASLLogger *)sharedInstance 44 | { 45 | return sharedInstance; 46 | } 47 | 48 | - (id)init 49 | { 50 | if (sharedInstance != nil) 51 | { 52 | return nil; 53 | } 54 | 55 | if ((self = [super init])) 56 | { 57 | // A default asl client is provided for the main thread, 58 | // but background threads need to create their own client. 59 | 60 | client = asl_open(NULL, "com.apple.console", 0); 61 | } 62 | return self; 63 | } 64 | 65 | - (void)logMessage:(DDLogMessage *)logMessage 66 | { 67 | NSString *logMsg = logMessage->logMsg; 68 | 69 | if (formatter) 70 | { 71 | logMsg = [formatter formatLogMessage:logMessage]; 72 | } 73 | 74 | if (logMsg) 75 | { 76 | const char *msg = [logMsg UTF8String]; 77 | 78 | int aslLogLevel; 79 | switch (logMessage->logFlag) 80 | { 81 | // Note: By default ASL will filter anything above level 5 (Notice). 82 | // So our mappings shouldn't go above that level. 83 | 84 | case LOG_FLAG_ERROR : aslLogLevel = ASL_LEVEL_CRIT; break; 85 | case LOG_FLAG_WARN : aslLogLevel = ASL_LEVEL_ERR; break; 86 | case LOG_FLAG_INFO : aslLogLevel = ASL_LEVEL_WARNING; break; 87 | default : aslLogLevel = ASL_LEVEL_NOTICE; break; 88 | } 89 | 90 | asl_log(client, NULL, aslLogLevel, "%s", msg); 91 | } 92 | } 93 | 94 | - (NSString *)loggerName 95 | { 96 | return @"cocoa.lumberjack.aslLogger"; 97 | } 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /Views/SPKPinFunctionView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKPinFunctionView.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKPinFunctionView.h" 9 | 10 | #define selectedColor [UIColor colorWithRed:0 green:0 blue:0 alpha:0.2] 11 | #define unselectedColor [UIColor colorWithRed:0 green:0 blue:0 alpha:0.1] 12 | 13 | @implementation SPKPinFunctionView 14 | 15 | - (void)setPin:(SPKCorePin *)pin 16 | { 17 | _pin = pin; 18 | 19 | self.pinLabel.text = _pin.label; 20 | 21 | self.analogReadImageView.hidden = YES; 22 | self.analogReadButton.backgroundColor = unselectedColor; 23 | self.analogWriteImageView.hidden = YES; 24 | self.analogWriteButton.backgroundColor = unselectedColor; 25 | self.digitalReadImageView.hidden = YES; 26 | self.digitalReadButton.backgroundColor = unselectedColor; 27 | self.digitalWriteImageView.hidden = YES; 28 | self.digitalWriteButton.backgroundColor = unselectedColor; 29 | 30 | if ((pin.availableFunctions & SPKCorePinFunctionAnalogRead) == SPKCorePinFunctionAnalogRead) { 31 | self.analogReadButton.hidden = NO; 32 | } else { 33 | self.analogReadButton.hidden = YES; 34 | } 35 | 36 | if ((pin.availableFunctions & SPKCorePinFunctionAnalogWrite) == SPKCorePinFunctionAnalogWrite) { 37 | self.analogWriteButton.hidden = NO; 38 | } else { 39 | self.analogWriteButton.hidden = YES; 40 | } 41 | 42 | switch (_pin.selectedFunction) { 43 | case SPKCorePinFunctionAnalogRead: 44 | self.analogReadButton.backgroundColor = selectedColor; 45 | self.analogReadImageView.hidden = NO; 46 | break; 47 | 48 | case SPKCorePinFunctionAnalogWrite: 49 | self.analogWriteButton.backgroundColor = selectedColor; 50 | self.analogWriteImageView.hidden = NO; 51 | break; 52 | 53 | case SPKCorePinFunctionDigitalRead: 54 | self.digitalReadButton.backgroundColor = selectedColor; 55 | self.digitalReadImageView.hidden = NO; 56 | break; 57 | 58 | case SPKCorePinFunctionDigitalWrite: 59 | self.digitalWriteButton.backgroundColor = selectedColor; 60 | self.digitalWriteImageView.hidden = NO; 61 | break; 62 | 63 | default: 64 | break; 65 | } 66 | } 67 | 68 | - (IBAction)functionSelected:(id)sender 69 | { 70 | SPKCorePinFunction function = SPKCorePinFunctionNone; 71 | 72 | if (sender == self.analogReadButton || sender == self.analogReadHighButton) { 73 | function = SPKCorePinFunctionAnalogRead; 74 | } else if (sender == self.analogWriteButton) { 75 | function = SPKCorePinFunctionAnalogWrite; 76 | } else if (sender == self.digitalReadButton || sender == self.digitalReadHighButton) { 77 | function = SPKCorePinFunctionDigitalRead; 78 | } else if (sender == self.digitalWriteButton) { 79 | function = SPKCorePinFunctionDigitalWrite; 80 | } 81 | 82 | [self.delegate pinFunctionSelected:function]; 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /Views/SPKCoreCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKCoreCell.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKCoreCell.h" 9 | #import "SPKSpark.h" 10 | 11 | @interface SPKCoreCell () 12 | 13 | @property (nonatomic, strong) CAShapeLayer *leftLayer; 14 | @property (nonatomic, strong) CAShapeLayer *dotLayer; 15 | 16 | @end 17 | 18 | @implementation SPKCoreCell 19 | 20 | - (void)awakeFromNib 21 | { 22 | self.leftLayer = [CAShapeLayer layer]; 23 | self.leftLayer.fillColor = [[UIColor redColor] CGColor]; 24 | self.leftLayer.path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0.0, 5.0, 40.0)].CGPath; 25 | self.leftLayer.bounds = CGRectMake(0.0, 0.0, 5.0, 40.0); 26 | self.leftLayer.position = CGPointMake(2.5, 20.0); 27 | 28 | [self.layer addSublayer:self.leftLayer]; 29 | 30 | self.dotLayer = [CAShapeLayer layer]; 31 | self.dotLayer.fillColor = [[UIColor redColor] CGColor]; 32 | self.dotLayer.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 10.0, 10.0) cornerRadius:10.0/2.0].CGPath; 33 | self.dotLayer.bounds = CGRectMake(0.0, 0.0, 10.0, 10.0); 34 | self.dotLayer.cornerRadius = 10.0/2.0; 35 | self.dotLayer.position = CGPointMake(25.0, 21.0); 36 | 37 | [self.layer addSublayer:self.dotLayer]; 38 | 39 | self.spinnerImageView.hidden = YES; 40 | } 41 | 42 | - (void)setCore:(SPKCore *)core 43 | { 44 | _core = core; 45 | self.nameLabel.text = _core.name; 46 | self.leftLayer.fillColor = [_core.color CGColor]; 47 | self.dotLayer.fillColor = [_core.color CGColor]; 48 | self.connectLabel.text = [NSString stringWithFormat:@"Connected: %@", _core.connected ? @"Yes" : @"No"]; 49 | if ([SPKSpark sharedInstance].activeCore == _core) { 50 | self.leftLayer.hidden = NO; 51 | } else { 52 | self.leftLayer.hidden = YES; 53 | } 54 | } 55 | 56 | - (void)expand 57 | { 58 | [self.expandButton setImage:[UIImage imageNamed:@"20.03-down-arrow.png"] forState:UIControlStateNormal]; 59 | self.editButton.hidden = NO; 60 | self.grayLineImageView.hidden = YES; 61 | } 62 | 63 | - (void)contract 64 | { 65 | [self.expandButton setImage:[UIImage imageNamed:@"20.02-right-arrow.png"] forState:UIControlStateNormal]; 66 | self.editButton.hidden = YES; 67 | self.grayLineImageView.hidden = NO; 68 | } 69 | 70 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated 71 | { 72 | // [super setSelected:selected animated:animated]; 73 | 74 | // Configure the view for the selected state 75 | } 76 | 77 | - (void)spinSpinner:(BOOL)go 78 | { 79 | if (go) { 80 | self.spinnerImageView.hidden = NO; 81 | 82 | CABasicAnimation *rotation; 83 | rotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; 84 | rotation.fromValue = [NSNumber numberWithFloat:0]; 85 | rotation.toValue = [NSNumber numberWithFloat:(2*M_PI)]; 86 | rotation.duration = 1.1; // Speed 87 | rotation.repeatCount = HUGE_VALF; // Repeat forever. Can be a finite number. 88 | [self.spinnerImageView.layer addAnimation:rotation forKey:@"Spin"]; 89 | } else { 90 | self.spinnerImageView.hidden = YES; 91 | [self.spinnerImageView.layer removeAllAnimations]; 92 | } 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /Vendor/Apple/KeychainItemWrapper.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: KeychainItemWrapper.h 3 | Abstract: 4 | Objective-C wrapper for accessing a single keychain item. 5 | 6 | Version: 1.2 7 | 8 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 9 | Inc. ("Apple") in consideration of your agreement to the following 10 | terms, and your use, installation, modification or redistribution of 11 | this Apple software constitutes acceptance of these terms. If you do 12 | not agree with these terms, please do not use, install, modify or 13 | redistribute this Apple software. 14 | 15 | In consideration of your agreement to abide by the following terms, and 16 | subject to these terms, Apple grants you a personal, non-exclusive 17 | license, under Apple's copyrights in this original Apple software (the 18 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 19 | Software, with or without modifications, in source and/or binary forms; 20 | provided that if you redistribute the Apple Software in its entirety and 21 | without modifications, you must retain this notice and the following 22 | text and disclaimers in all such redistributions of the Apple Software. 23 | Neither the name, trademarks, service marks or logos of Apple Inc. may 24 | be used to endorse or promote products derived from the Apple Software 25 | without specific prior written permission from Apple. Except as 26 | expressly stated in this notice, no other rights or licenses, express or 27 | implied, are granted by Apple herein, including but not limited to any 28 | patent rights that may be infringed by your derivative works or by other 29 | works in which the Apple Software may be incorporated. 30 | 31 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 32 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 33 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 34 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 35 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 36 | 37 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 38 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 39 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 40 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 41 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 42 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 43 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 44 | POSSIBILITY OF SUCH DAMAGE. 45 | 46 | Copyright (C) 2010 Apple Inc. All Rights Reserved. 47 | 48 | */ 49 | 50 | #import 51 | 52 | /* 53 | The KeychainItemWrapper class is an abstraction layer for the iPhone Keychain communication. It is merely a 54 | simple wrapper to provide a distinct barrier between all the idiosyncracies involved with the Keychain 55 | CF/NS container objects. 56 | */ 57 | @interface KeychainItemWrapper : NSObject 58 | { 59 | NSMutableDictionary *keychainItemData; // The actual keychain item data backing store. 60 | NSMutableDictionary *genericPasswordQuery; // A placeholder for the generic keychain item query used to locate the item. 61 | } 62 | 63 | @property (nonatomic, retain) NSMutableDictionary *keychainItemData; 64 | @property (nonatomic, retain) NSMutableDictionary *genericPasswordQuery; 65 | 66 | // Designated initializer. 67 | - (id)initWithAccount:(NSString *)account service:(NSString *)service accessGroup:(NSString *) accessGroup; 68 | - (void)setObject:(id)inObject forKey:(id)key; 69 | - (id)objectForKey:(id)key; 70 | 71 | // Initializes and resets the default generic keychain item data. 72 | - (void)resetKeychainItem; 73 | 74 | @end -------------------------------------------------------------------------------- /Vendor/AFNetworking/AFPropertyListRequestOperation.h: -------------------------------------------------------------------------------- 1 | // AFPropertyListRequestOperation.h 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | #import 24 | #import "AFHTTPRequestOperation.h" 25 | 26 | /** 27 | `AFPropertyListRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading and deserializing objects with property list (plist) response data. 28 | 29 | ## Acceptable Content Types 30 | 31 | By default, `AFPropertyListRequestOperation` accepts the following MIME types: 32 | 33 | - `application/x-plist` 34 | */ 35 | @interface AFPropertyListRequestOperation : AFHTTPRequestOperation 36 | 37 | ///---------------------------- 38 | /// @name Getting Response Data 39 | ///---------------------------- 40 | 41 | /** 42 | An object deserialized from a plist constructed using the response data. 43 | */ 44 | @property (readonly, nonatomic) id responsePropertyList; 45 | 46 | ///-------------------------------------- 47 | /// @name Managing Property List Behavior 48 | ///-------------------------------------- 49 | 50 | /** 51 | One of the `NSPropertyListMutabilityOptions` options, specifying the mutability of objects deserialized from the property list. By default, this is `NSPropertyListImmutable`. 52 | */ 53 | @property (nonatomic, assign) NSPropertyListReadOptions propertyListReadOptions; 54 | 55 | /** 56 | Creates and returns an `AFPropertyListRequestOperation` object and sets the specified success and failure callbacks. 57 | 58 | @param urlRequest The request object to be loaded asynchronously during execution of the operation 59 | @param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the object deserialized from a plist constructed using the response data. 60 | @param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while deserializing the object from a property list. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network or parsing error that occurred. 61 | 62 | @return A new property list request operation 63 | */ 64 | + (instancetype)propertyListRequestOperationWithRequest:(NSURLRequest *)urlRequest 65 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id propertyList))success 66 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id propertyList))failure; 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /Controllers/SPKUIViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKUIViewController.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKUIViewController.h" 9 | 10 | @interface SPKUIViewController () 11 | 12 | @property (nonatomic, assign) CGFloat kbSizeHeight; 13 | 14 | @end 15 | 16 | @implementation SPKUIViewController 17 | 18 | - (void)viewDidLoad 19 | { 20 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissKeyboard)]; 21 | [self.view addGestureRecognizer:tap]; 22 | } 23 | 24 | - (void)viewWillAppear:(BOOL)animated 25 | { 26 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; 27 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; 28 | } 29 | 30 | - (void)viewWillDisappear:(BOOL)animated 31 | { 32 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; 33 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil]; 34 | } 35 | 36 | - (UIStatusBarStyle)preferredStatusBarStyle 37 | { 38 | return UIStatusBarStyleLightContent; 39 | } 40 | 41 | #pragma mark - Notifications 42 | 43 | - (void)keyboardWillShow:(NSNotification *)notification 44 | { 45 | self.kbSizeHeight = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size.height; 46 | self.kbSizeHeight -= [self keyboardHeightAdjust]; 47 | 48 | if (self.view.frame.origin.y >= 0) { 49 | [self setViewMovedUp:YES]; 50 | } else if (self.view.frame.origin.y < 0) { 51 | [self setViewMovedUp:NO]; 52 | } 53 | } 54 | 55 | - (void)keyboardWillHide:(NSNotification *)notification 56 | { 57 | if (self.view.frame.origin.y >= 0) { 58 | [self setViewMovedUp:YES]; 59 | } else if (self.view.frame.origin.y < 0) { 60 | [self setViewMovedUp:NO]; 61 | } 62 | } 63 | 64 | - (void)dismissKeyboard 65 | { 66 | 67 | } 68 | 69 | - (CGFloat)keyboardHeightAdjust 70 | { 71 | return 90.0; 72 | } 73 | 74 | #pragma mark - Methods 75 | 76 | //method to move the view up/down whenever the keyboard is shown/dismissed 77 | - (void)setViewMovedUp:(BOOL)movedUp 78 | { 79 | [UIView beginAnimations:nil context:NULL]; 80 | [UIView setAnimationDuration:0.3]; // if you want to slide up the view 81 | 82 | CGRect rect = self.view.frame; 83 | if (movedUp) { 84 | // 1. move the view's origin up so that the text field that will be hidden come above the keyboard 85 | // 2. increase the size of the view so that the area behind the keyboard is covered up. 86 | rect.origin.y -= self.kbSizeHeight; 87 | rect.size.height += self.kbSizeHeight; 88 | } else { 89 | // revert back to the normal state. 90 | rect.origin.y += self.kbSizeHeight; 91 | rect.size.height -= self.kbSizeHeight; 92 | } 93 | self.view.frame = rect; 94 | 95 | [UIView commitAnimations]; 96 | } 97 | 98 | - (BOOL)isValidEmail:(NSString *)checkString 99 | { 100 | BOOL stricterFilter = NO; // Discussion http://blog.logichigh.com/2010/09/02/validating-an-e-mail-address/ 101 | NSString *stricterFilterString = @"[A-Z0-9a-z\\._%+-]+@([A-Za-z0-9-]+\\.)+[A-Za-z]{2,4}"; 102 | NSString *laxString = @".+@([A-Za-z0-9]+\\.)+[A-Za-z]{2}[A-Za-z]*"; 103 | NSString *emailRegex = stricterFilter ? stricterFilterString : laxString; 104 | NSPredicate *emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", emailRegex]; 105 | return [emailTest evaluateWithObject:checkString]; 106 | } 107 | 108 | @end 109 | -------------------------------------------------------------------------------- /Vendor/AFNetworking/AFJSONRequestOperation.h: -------------------------------------------------------------------------------- 1 | // AFJSONRequestOperation.h 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | #import 24 | #import "AFHTTPRequestOperation.h" 25 | 26 | /** 27 | `AFJSONRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading and working with JSON response data. 28 | 29 | ## Acceptable Content Types 30 | 31 | By default, `AFJSONRequestOperation` accepts the following MIME types, which includes the official standard, `application/json`, as well as other commonly-used types: 32 | 33 | - `application/json` 34 | - `text/json` 35 | 36 | @warning JSON parsing will use the built-in `NSJSONSerialization` class. 37 | */ 38 | @interface AFJSONRequestOperation : AFHTTPRequestOperation 39 | 40 | ///---------------------------- 41 | /// @name Getting Response Data 42 | ///---------------------------- 43 | 44 | /** 45 | A JSON object constructed from the response data. If an error occurs while parsing, `nil` will be returned, and the `error` property will be set to the error. 46 | */ 47 | @property (readonly, nonatomic, strong) id responseJSON; 48 | 49 | /** 50 | Options for reading the response JSON data and creating the Foundation objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONReadingOptions". 51 | */ 52 | @property (nonatomic, assign) NSJSONReadingOptions JSONReadingOptions; 53 | 54 | ///---------------------------------- 55 | /// @name Creating Request Operations 56 | ///---------------------------------- 57 | 58 | /** 59 | Creates and returns an `AFJSONRequestOperation` object and sets the specified success and failure callbacks. 60 | 61 | @param urlRequest The request object to be loaded asynchronously during execution of the operation 62 | @param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the JSON object created from the response data of request. 63 | @param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data as JSON. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network or parsing error that occurred. 64 | 65 | @return A new JSON request operation 66 | */ 67 | + (instancetype)JSONRequestOperationWithRequest:(NSURLRequest *)urlRequest 68 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success 69 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON))failure; 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /Models/SPKSmartConfig.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKSmartConfig.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | #import "SPKSmartConfig.h" 11 | #import "GCDAsyncUdpSocket.h" 12 | #import "FirstTimeConfig.h" 13 | 14 | #include 15 | #include 16 | 17 | #define COAP_MULTICAST_HOST @"224.0.1.187" 18 | #define COAP_MULTICAST_PORT 5683 19 | #define COAP_MULTICAST_TIMEOUT (60*5) 20 | 21 | @interface SPKSmartConfig () 22 | 23 | @property (nonatomic, strong) FirstTimeConfig *firstTimeConfig; 24 | @property (nonatomic, strong) GCDAsyncUdpSocket *coapListenSocket; 25 | @property (nonatomic, strong) dispatch_queue_t coapListenQueue; 26 | 27 | @property (nonatomic, assign) BOOL isBroadcasting; 28 | @property (nonatomic, assign) BOOL isListening; 29 | 30 | @end 31 | 32 | @implementation SPKSmartConfig 33 | 34 | - (id)init 35 | { 36 | if (self = [super init]) { 37 | NSString *queueName = [NSString stringWithFormat:@"SPKSmartConfig-CoAPListen-%p", self]; 38 | _coapListenQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSUTF8StringEncoding], DISPATCH_QUEUE_SERIAL); 39 | _coapListenSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:_coapListenQueue]; 40 | } 41 | 42 | return self; 43 | } 44 | 45 | - (void)configureWithPassword:(NSString *)wifiPassword aesKey:(NSString *)aesKey 46 | { 47 | self.firstTimeConfig = [[FirstTimeConfig alloc] initWithKey:wifiPassword withEncryptionKey:[aesKey dataUsingEncoding:NSUTF8StringEncoding]]; 48 | } 49 | 50 | - (void)startTransmittingSettings 51 | { 52 | NSError *error; 53 | 54 | [self.coapListenSocket bindToPort:COAP_MULTICAST_PORT error:&error]; 55 | if (error) { 56 | DDLogError(@"Problem setting up multicast: %@", [error localizedDescription]); 57 | return; 58 | } 59 | 60 | [self.coapListenSocket joinMulticastGroup:COAP_MULTICAST_HOST error:&error]; 61 | if (error) { 62 | DDLogError(@"Problem setting up multicast: %@", [error localizedDescription]); 63 | return; 64 | } 65 | 66 | [self.coapListenSocket enableBroadcast:YES error:&error]; 67 | if (error) { 68 | DDLogError(@"Problem setting up multicast: %@", [error localizedDescription]); 69 | return; 70 | } 71 | 72 | [self.coapListenSocket beginReceiving:&error]; 73 | if (error) { 74 | DDLogError(@"Problem setting up multicast: %@", [error localizedDescription]); 75 | return; 76 | } 77 | 78 | [self.firstTimeConfig transmitSettings]; 79 | 80 | self.isBroadcasting = YES; 81 | self.isListening = YES; 82 | 83 | DDLogInfo(@"Starting SmartConfig..."); 84 | } 85 | 86 | - (void)stopTransmittingSettings 87 | { 88 | DDLogInfo(@"Stopping SmartConfig..."); 89 | self.isBroadcasting = NO; 90 | [self.firstTimeConfig stopTransmitting]; 91 | } 92 | 93 | - (void)stopCoAPListening 94 | { 95 | self.isListening = NO; 96 | [self.coapListenSocket close]; 97 | } 98 | 99 | #pragma mark - Async Socket Delegate 100 | 101 | - (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext 102 | { 103 | if (sock == self.coapListenSocket && self.isListening) { 104 | if (data.length >= 19) { 105 | uint8_t *bytes = (uint8_t *)[data bytes]; 106 | if ((bytes[0] == 0x50) && (bytes[1] == 0x02) && (bytes[4] == 0xB1) && (bytes[5] == 0x68) && (bytes[6] == 0xFF)) { 107 | NSData *coreId = [NSData dataWithBytes:bytes+7 length:12]; 108 | [[NSNotificationCenter defaultCenter] postNotificationName:kSPKSmartConfigHelloCore object:self userInfo:@{ @"coreId": coreId }]; 109 | return; 110 | } 111 | } 112 | 113 | DDLogVerbose(@"Got smartConfigHelloed but isn't valid - dropping: %@", data); 114 | } 115 | } 116 | 117 | @end 118 | -------------------------------------------------------------------------------- /Vendor/Lumberjack/DDAbstractDatabaseLogger.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "DDLog.h" 4 | 5 | /** 6 | * Welcome to Cocoa Lumberjack! 7 | * 8 | * The project page has a wealth of documentation if you have any questions. 9 | * https://github.com/robbiehanson/CocoaLumberjack 10 | * 11 | * If you're new to the project you may wish to read the "Getting Started" wiki. 12 | * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted 13 | * 14 | * 15 | * This class provides an abstract implementation of a database logger. 16 | * 17 | * That is, it provides the base implementation for a database logger to build atop of. 18 | * All that is needed for a concrete database logger is to extend this class 19 | * and override the methods in the implementation file that are prefixed with "db_". 20 | **/ 21 | 22 | @interface DDAbstractDatabaseLogger : DDAbstractLogger { 23 | @protected 24 | NSUInteger saveThreshold; 25 | NSTimeInterval saveInterval; 26 | NSTimeInterval maxAge; 27 | NSTimeInterval deleteInterval; 28 | BOOL deleteOnEverySave; 29 | 30 | BOOL saveTimerSuspended; 31 | NSUInteger unsavedCount; 32 | dispatch_time_t unsavedTime; 33 | dispatch_source_t saveTimer; 34 | dispatch_time_t lastDeleteTime; 35 | dispatch_source_t deleteTimer; 36 | } 37 | 38 | /** 39 | * Specifies how often to save the data to disk. 40 | * Since saving is an expensive operation (disk io) it is not done after every log statement. 41 | * These properties allow you to configure how/when the logger saves to disk. 42 | * 43 | * A save is done when either (whichever happens first): 44 | * 45 | * - The number of unsaved log entries reaches saveThreshold 46 | * - The amount of time since the oldest unsaved log entry was created reaches saveInterval 47 | * 48 | * You can optionally disable the saveThreshold by setting it to zero. 49 | * If you disable the saveThreshold you are entirely dependent on the saveInterval. 50 | * 51 | * You can optionally disable the saveInterval by setting it to zero (or a negative value). 52 | * If you disable the saveInterval you are entirely dependent on the saveThreshold. 53 | * 54 | * It's not wise to disable both saveThreshold and saveInterval. 55 | * 56 | * The default saveThreshold is 500. 57 | * The default saveInterval is 60 seconds. 58 | **/ 59 | @property (assign, readwrite) NSUInteger saveThreshold; 60 | @property (assign, readwrite) NSTimeInterval saveInterval; 61 | 62 | /** 63 | * It is likely you don't want the log entries to persist forever. 64 | * Doing so would allow the database to grow infinitely large over time. 65 | * 66 | * The maxAge property provides a way to specify how old a log statement can get 67 | * before it should get deleted from the database. 68 | * 69 | * The deleteInterval specifies how often to sweep for old log entries. 70 | * Since deleting is an expensive operation (disk io) is is done on a fixed interval. 71 | * 72 | * An alternative to the deleteInterval is the deleteOnEverySave option. 73 | * This specifies that old log entries should be deleted during every save operation. 74 | * 75 | * You can optionally disable the maxAge by setting it to zero (or a negative value). 76 | * If you disable the maxAge then old log statements are not deleted. 77 | * 78 | * You can optionally disable the deleteInterval by setting it to zero (or a negative value). 79 | * 80 | * If you disable both deleteInterval and deleteOnEverySave then old log statements are not deleted. 81 | * 82 | * It's not wise to enable both deleteInterval and deleteOnEverySave. 83 | * 84 | * The default maxAge is 7 days. 85 | * The default deleteInterval is 5 minutes. 86 | * The default deleteOnEverySave is NO. 87 | **/ 88 | @property (assign, readwrite) NSTimeInterval maxAge; 89 | @property (assign, readwrite) NSTimeInterval deleteInterval; 90 | @property (assign, readwrite) BOOL deleteOnEverySave; 91 | 92 | /** 93 | * Forces a save of any pending log entries (flushes log entries to disk). 94 | **/ 95 | - (void)savePendingLogEntries; 96 | 97 | /** 98 | * Removes any log entries that are older than maxAge. 99 | **/ 100 | - (void)deleteOldLogEntries; 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /Models/SPKCore.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKCore.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKCore.h" 9 | #import "SPKCorePin.h" 10 | 11 | static NSUInteger CORE_COLORS[] = { 12 | 0x1ABC9C, 13 | 0x3498DB, 14 | 0x2980B9, 15 | 0x9B59B6, 16 | 0x8E44AD, 17 | 0xF1C40F, 18 | 0xF39C12, 19 | 0xE67E22, 20 | 0xD35400, 21 | 0xE74C3C, 22 | 0x2ECC71, 23 | 0xC0392B, 24 | 0xECF0F1, 25 | 0x95A5A6, 26 | 0x7F8C8D, 27 | 0x16A085, 28 | 0x27AE60, 29 | }; 30 | 31 | static NSUInteger colorIndex = 0; 32 | 33 | static const char * const CORE_NAMES[] = { "aardvark", "bacon", "badger", "banjo", "bobcat", "boomer", "captain", "chicken", "cowboy", "cracker", "cranky", "crazy", "dentist", "doctor", "dozen", "easter", "ferret", "gerbil", "hacker", "hamster", "hindu", "hobo", "hoosier", "hunter", "jester", "jetpack", "kitty", "laser", "lawyer", "mighty", "monkey", "morphing", "mutant", "narwhal", "ninja", "normal", "penguin", "pirate", "pizza", "plumber", "power", "puppy", "ranger", "raptor", "robot", "scraper", "scrapple", "station", "tasty", "trochee", "turkey", "turtle", "vampire", "wombat", "zombie" }; 34 | 35 | static NSUInteger CORE_NAMES_COUNT = 55; 36 | 37 | #define ALL_FUNCTIONS (SPKCorePinFunctionDigitalRead|SPKCorePinFunctionDigitalWrite|SPKCorePinFunctionAnalogRead|SPKCorePinFunctionAnalogWrite) 38 | 39 | @interface SPKCore () 40 | 41 | @property (nonatomic, strong) UIColor *color; 42 | 43 | @end 44 | 45 | @implementation SPKCore 46 | 47 | - (id)init 48 | { 49 | if (self = [super init]) { 50 | _color = UIColorFromRGB(CORE_COLORS[colorIndex % 17]); 51 | colorIndex++; 52 | _name = [self generateName]; 53 | _state = SPKCoreStateUnknown; 54 | [self configurePins]; 55 | } 56 | 57 | return self; 58 | } 59 | 60 | - (void)reset 61 | { 62 | for (SPKCorePin *pin in self.pins) { 63 | pin.selectedFunction = SPKCorePinFunctionNone; 64 | [pin resetValue]; 65 | } 66 | } 67 | 68 | #pragma mark - Private Methods 69 | 70 | - (void)configurePins 71 | { 72 | SPKCorePin *a0 = [[SPKCorePin alloc] initWithLabel:@"A0" side:SPKCorePinSideLeft row:7 availableFunctions:ALL_FUNCTIONS]; 73 | SPKCorePin *a1 = [[SPKCorePin alloc] initWithLabel:@"A1" side:SPKCorePinSideLeft row:6 availableFunctions:ALL_FUNCTIONS]; 74 | SPKCorePin *a2 = [[SPKCorePin alloc] initWithLabel:@"A2" side:SPKCorePinSideLeft row:5 availableFunctions:ALL_FUNCTIONS]; 75 | SPKCorePin *a3 = [[SPKCorePin alloc] initWithLabel:@"A3" side:SPKCorePinSideLeft row:4 availableFunctions:SPKCorePinFunctionDigitalRead|SPKCorePinFunctionDigitalWrite|SPKCorePinFunctionAnalogRead]; 76 | SPKCorePin *a4 = [[SPKCorePin alloc] initWithLabel:@"A4" side:SPKCorePinSideLeft row:3 availableFunctions:SPKCorePinFunctionDigitalRead|SPKCorePinFunctionDigitalWrite|SPKCorePinFunctionAnalogRead]; 77 | SPKCorePin *a5 = [[SPKCorePin alloc] initWithLabel:@"A5" side:SPKCorePinSideLeft row:2 availableFunctions:ALL_FUNCTIONS]; 78 | SPKCorePin *a6 = [[SPKCorePin alloc] initWithLabel:@"A6" side:SPKCorePinSideLeft row:1 availableFunctions:ALL_FUNCTIONS]; 79 | SPKCorePin *a7 = [[SPKCorePin alloc] initWithLabel:@"A7" side:SPKCorePinSideLeft row:0 availableFunctions:ALL_FUNCTIONS]; 80 | 81 | SPKCorePin *d0 = [[SPKCorePin alloc] initWithLabel:@"D0" side:SPKCorePinSideRight row:7 availableFunctions:SPKCorePinFunctionDigitalRead|SPKCorePinFunctionDigitalWrite|SPKCorePinFunctionAnalogWrite]; 82 | SPKCorePin *d1 = [[SPKCorePin alloc] initWithLabel:@"D1" side:SPKCorePinSideRight row:6 availableFunctions:SPKCorePinFunctionDigitalRead|SPKCorePinFunctionDigitalWrite|SPKCorePinFunctionAnalogWrite]; 83 | SPKCorePin *d2 = [[SPKCorePin alloc] initWithLabel:@"D2" side:SPKCorePinSideRight row:5 availableFunctions:SPKCorePinFunctionDigitalRead|SPKCorePinFunctionDigitalWrite]; 84 | SPKCorePin *d3 = [[SPKCorePin alloc] initWithLabel:@"D3" side:SPKCorePinSideRight row:4 availableFunctions:SPKCorePinFunctionDigitalRead|SPKCorePinFunctionDigitalWrite]; 85 | SPKCorePin *d4 = [[SPKCorePin alloc] initWithLabel:@"D4" side:SPKCorePinSideRight row:3 availableFunctions:SPKCorePinFunctionDigitalRead|SPKCorePinFunctionDigitalWrite]; 86 | SPKCorePin *d5 = [[SPKCorePin alloc] initWithLabel:@"D5" side:SPKCorePinSideRight row:2 availableFunctions:SPKCorePinFunctionDigitalRead|SPKCorePinFunctionDigitalWrite]; 87 | SPKCorePin *d6 = [[SPKCorePin alloc] initWithLabel:@"D6" side:SPKCorePinSideRight row:1 availableFunctions:SPKCorePinFunctionDigitalRead|SPKCorePinFunctionDigitalWrite]; 88 | SPKCorePin *d7 = [[SPKCorePin alloc] initWithLabel:@"D7" side:SPKCorePinSideRight row:0 availableFunctions:SPKCorePinFunctionDigitalRead|SPKCorePinFunctionDigitalWrite]; 89 | 90 | _pins = @[a0, a1, a2, a3, a4, a5, a6, a7, d0, d1, d2, d3, d4, d5, d6, d7]; 91 | 92 | } 93 | 94 | - (NSString *)generateName 95 | { 96 | NSUInteger a = arc4random() % CORE_NAMES_COUNT; 97 | NSUInteger b = arc4random() % CORE_NAMES_COUNT; 98 | const char *first = CORE_NAMES[a]; 99 | const char *last = CORE_NAMES[b]; 100 | 101 | return [NSString stringWithFormat:@"%s_%s", first, last]; 102 | } 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /Controllers/SPKCoresViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKCoresViewController.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKCoresViewController.h" 9 | #import "SPKCoreCell.h" 10 | #import "SPKSpark.h" 11 | 12 | @interface SPKCoresViewController () 13 | 14 | @property (nonatomic, strong) NSMutableIndexSet *expandedIndexSet; 15 | @property (nonatomic, strong) SPKCoreCell *cellForNaming; 16 | 17 | @end 18 | 19 | @implementation SPKCoresViewController 20 | 21 | - (void)awakeFromNib 22 | { 23 | self.expandedIndexSet = [NSMutableIndexSet indexSet]; 24 | } 25 | 26 | - (UIStatusBarStyle)preferredStatusBarStyle 27 | { 28 | return UIStatusBarStyleLightContent; 29 | } 30 | 31 | - (IBAction)expandToggled:(id)sender 32 | { 33 | [self.tableView beginUpdates]; 34 | 35 | SPKCoreCell *cell = (SPKCoreCell *)[[[(UIButton *)sender superview] superview] superview]; 36 | cell.expanded = !cell.expanded; 37 | if (cell.expanded) { 38 | [self.expandedIndexSet addIndex:cell.index]; 39 | [cell expand]; 40 | } else { 41 | [cell contract]; 42 | [self.expandedIndexSet removeIndex:cell.index]; 43 | } 44 | [self.tableView endUpdates]; 45 | } 46 | 47 | - (IBAction)reflashTinker:(id)sender 48 | { 49 | SPKCoreCell *cell = (SPKCoreCell *)[[[(UIButton *)sender superview] superview] superview]; 50 | SPKCore *core = cell.core; 51 | 52 | [cell spinSpinner:YES]; 53 | 54 | [[SPKSpark sharedInstance].webClient flashTinker:core.coreId success:^{ 55 | dispatch_async(dispatch_get_main_queue(), ^{ 56 | [cell spinSpinner:NO]; 57 | [[[UIAlertView alloc] initWithTitle:@"Tinker" message:@"Core is being re-flashed" delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil] show]; 58 | }); 59 | } failure:^{ 60 | dispatch_async(dispatch_get_main_queue(), ^{ 61 | [cell spinSpinner:NO]; 62 | [[[UIAlertView alloc] initWithTitle:@"Tinker" message:@"Problem re-flashing Core" delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil] show]; 63 | }); 64 | }]; 65 | } 66 | 67 | - (IBAction)editName:(id)sender 68 | { 69 | self.cellForNaming = (SPKCoreCell *)[[[(UIButton *)sender superview] superview] superview]; 70 | 71 | UIAlertView *alertView = [[UIAlertView alloc] init]; 72 | [alertView setDelegate:self]; 73 | alertView.alertViewStyle=UIAlertViewStylePlainTextInput; 74 | [alertView setTitle:@"Rename Core"]; 75 | [alertView setMessage:@"Enter new Core name"]; 76 | 77 | [alertView addButtonWithTitle:@"Cancel"]; 78 | [alertView addButtonWithTitle:@"Ok"]; 79 | 80 | 81 | UITextField *nameTextField = [alertView textFieldAtIndex:0]; 82 | nameTextField.placeholder = self.cellForNaming.core.name; 83 | nameTextField.keyboardType = UIKeyboardTypeEmailAddress; 84 | [alertView show]; 85 | } 86 | 87 | - (IBAction)clearTinker:(id)sender 88 | { 89 | SPKCore *core = [SPKSpark sharedInstance].activeCore; 90 | [core reset]; 91 | [self performSegueWithIdentifier:@"tinker" sender:sender]; 92 | } 93 | 94 | #pragma mark - AlertView DataSource 95 | 96 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex 97 | { 98 | if (buttonIndex == 1) { // Ok 99 | NSString *name = [[alertView textFieldAtIndex:0] text]; 100 | self.cellForNaming.core.name = name; 101 | self.cellForNaming.nameLabel.text = name; 102 | [[SPKSpark sharedInstance].webClient name:self.cellForNaming.core.coreId label:name success:^{ 103 | // do nothing 104 | } failure:^{ 105 | dispatch_async(dispatch_get_main_queue(), ^{ 106 | [[[UIAlertView alloc] initWithTitle:@"Rename Core" message:@"There was a problem renaming the Core" delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil] show]; 107 | }); 108 | }]; 109 | } 110 | } 111 | 112 | #pragma mark - TableView DataSource 113 | 114 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 115 | { 116 | return [SPKSpark sharedInstance].cores.count; 117 | } 118 | 119 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 120 | { 121 | SPKCoreCell *cell = [tableView dequeueReusableCellWithIdentifier:@"core"]; 122 | cell.core = [SPKSpark sharedInstance].cores[indexPath.row]; 123 | cell.index = indexPath.row; 124 | if (cell.expanded) { 125 | [cell expand]; 126 | } else { 127 | [cell contract]; 128 | } 129 | 130 | return cell; 131 | } 132 | 133 | #pragma mark - TableView Delegate 134 | 135 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 136 | { 137 | SPKCoreCell *cell = (SPKCoreCell *)[tableView cellForRowAtIndexPath:indexPath]; 138 | SPKCore *core = cell.core; 139 | 140 | [[SPKSpark sharedInstance] activateCore:core]; 141 | [self performSegueWithIdentifier:@"tinker" sender:nil]; 142 | } 143 | 144 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 145 | { 146 | if ([self.expandedIndexSet containsIndex:indexPath.row]) { 147 | return 145.0; 148 | } else { 149 | return 42; 150 | } 151 | } 152 | 153 | @end 154 | -------------------------------------------------------------------------------- /Vendor/AFNetworking/AFXMLRequestOperation.h: -------------------------------------------------------------------------------- 1 | // AFXMLRequestOperation.h 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | #import 24 | #import "AFHTTPRequestOperation.h" 25 | 26 | #import 27 | 28 | /** 29 | `AFXMLRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading and working with XML response data. 30 | 31 | ## Acceptable Content Types 32 | 33 | By default, `AFXMLRequestOperation` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types: 34 | 35 | - `application/xml` 36 | - `text/xml` 37 | 38 | ## Use With AFHTTPClient 39 | 40 | When `AFXMLRequestOperation` is registered with `AFHTTPClient`, the response object in the success callback of `HTTPRequestOperationWithRequest:success:failure:` will be an instance of `NSXMLParser`. On platforms that support `NSXMLDocument`, you have the option to ignore the response object, and simply use the `responseXMLDocument` property of the operation argument of the callback. 41 | */ 42 | @interface AFXMLRequestOperation : AFHTTPRequestOperation 43 | 44 | ///---------------------------- 45 | /// @name Getting Response Data 46 | ///---------------------------- 47 | 48 | /** 49 | An `NSXMLParser` object constructed from the response data. 50 | */ 51 | @property (readonly, nonatomic, strong) NSXMLParser *responseXMLParser; 52 | 53 | #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED 54 | /** 55 | An `NSXMLDocument` object constructed from the response data. If an error occurs while parsing, `nil` will be returned, and the `error` property will be set to the error. 56 | */ 57 | @property (readonly, nonatomic, strong) NSXMLDocument *responseXMLDocument; 58 | #endif 59 | 60 | /** 61 | Creates and returns an `AFXMLRequestOperation` object and sets the specified success and failure callbacks. 62 | 63 | @param urlRequest The request object to be loaded asynchronously during execution of the operation 64 | @param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the XML parser constructed with the response data of request. 65 | @param failure A block object to be executed when the operation finishes unsuccessfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network error that occurred. 66 | 67 | @return A new XML request operation 68 | */ 69 | + (instancetype)XMLParserRequestOperationWithRequest:(NSURLRequest *)urlRequest 70 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser))success 71 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLParser *XMLParser))failure; 72 | 73 | 74 | #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED 75 | /** 76 | Creates and returns an `AFXMLRequestOperation` object and sets the specified success and failure callbacks. 77 | 78 | @param urlRequest The request object to be loaded asynchronously during execution of the operation 79 | @param success A block object to be executed when the operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the XML document created from the response data of request. 80 | @param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data as XML. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error describing the network or parsing error that occurred. 81 | 82 | @return A new XML request operation 83 | */ 84 | + (instancetype)XMLDocumentRequestOperationWithRequest:(NSURLRequest *)urlRequest 85 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLDocument *document))success 86 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLDocument *document))failure; 87 | #endif 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /Controllers/SPKLoginViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKLoginViewController.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKLoginViewController.h" 9 | #import "SPKSpark.h" 10 | #import 11 | 12 | @implementation SPKLoginViewController 13 | 14 | - (void)viewDidLoad 15 | { 16 | [super viewDidLoad]; 17 | 18 | self.userIdTextField.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 8, 20)]; 19 | self.userIdTextField.leftViewMode = UITextFieldViewModeAlways; 20 | 21 | self.passwordTextField.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 8, 20)]; 22 | self.passwordTextField.leftViewMode = UITextFieldViewModeAlways; 23 | } 24 | 25 | - (void)viewWillAppear:(BOOL)animated 26 | { 27 | [super viewWillAppear:animated]; 28 | 29 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionError:) name:kSPKWebClientConnectionError object:nil]; 30 | 31 | [self.loginButton setTitle:@"LOG IN" forState:UIControlStateNormal]; 32 | self.loginButton.enabled = NO; 33 | self.userIdTextField.enabled = YES; 34 | self.passwordTextField.enabled = YES; 35 | 36 | self.spinnerImageView.hidden = YES; 37 | } 38 | 39 | - (void)viewWillDisappear:(BOOL)animated 40 | { 41 | [super viewWillDisappear:animated]; 42 | 43 | [[NSNotificationCenter defaultCenter] removeObserver:self name:kSPKWebClientConnectionError object:nil]; 44 | } 45 | 46 | - (void)viewDidLayoutSubviews 47 | { 48 | if (!isiPhone5) { 49 | CGRect f = self.formView.frame; 50 | f.origin.y -= 50.0; 51 | self.formView.frame = f; 52 | 53 | f = self.logoImageView.frame; 54 | f.origin.x += (f.size.width - (f.size.width * 0.75)) / 2.0; 55 | f.size.height *= 0.75; 56 | f.size.width *= 0.75; 57 | self.logoImageView.frame = f; 58 | } 59 | } 60 | 61 | - (void)dismissKeyboard 62 | { 63 | if (self.userIdTextField.isFirstResponder) { 64 | [self.userIdTextField resignFirstResponder]; 65 | } else if (self.passwordTextField.isFirstResponder) { 66 | [self.passwordTextField resignFirstResponder]; 67 | } 68 | } 69 | 70 | #pragma mark - Notifications 71 | 72 | - (void)connectionError:(NSNotification *)notifications 73 | { 74 | dispatch_async(dispatch_get_main_queue(), ^{ 75 | self.userIdTextField.enabled = YES; 76 | self.passwordTextField.enabled = YES; 77 | [self.loginButton setTitle:@"LOG IN" forState:UIControlStateNormal]; 78 | }); 79 | } 80 | 81 | #pragma mark - TextField Delegate 82 | 83 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 84 | { 85 | BOOL r1 = NO; 86 | BOOL r2 = NO; 87 | 88 | NSString *s = self.userIdTextField.text; 89 | if (textField == self.userIdTextField) { 90 | s = [textField.text stringByReplacingCharactersInRange:range withString:string]; 91 | } 92 | r1 = [self isValidEmail:s]; 93 | 94 | s = self.passwordTextField.text; 95 | if (textField == self.passwordTextField) { 96 | s = [textField.text stringByReplacingCharactersInRange:range withString:string]; 97 | } 98 | r2 = s.length > 0; 99 | 100 | self.loginButton.enabled = r1 && r2; 101 | 102 | #if 0 103 | self.loginButton.enabled = YES; 104 | #endif 105 | 106 | return YES; 107 | } 108 | 109 | - (BOOL)textFieldShouldReturn:(UITextField *)textField; 110 | { 111 | if (textField == self.userIdTextField) { 112 | [self.passwordTextField becomeFirstResponder]; 113 | } else { 114 | [self.passwordTextField resignFirstResponder]; 115 | [self login:textField]; 116 | } 117 | return NO; 118 | } 119 | 120 | #pragma mark - Actions 121 | 122 | - (IBAction)forgot:(id)sender 123 | { 124 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://www.spark.io/forgot-password"]]; 125 | } 126 | 127 | - (IBAction)login:(id)sender 128 | { 129 | self.userIdTextField.enabled = NO; 130 | self.passwordTextField.enabled = NO; 131 | 132 | [self.loginButton setTitle:@"LOGGING IN..." forState:UIControlStateNormal]; 133 | 134 | SPKUser *user = [SPKSpark sharedInstance].user; 135 | user.userId = self.userIdTextField.text; 136 | user.password = self.passwordTextField.text; 137 | [SPKSpark sharedInstance].attemptedLogin = YES; 138 | 139 | [self spinSpinner:YES]; 140 | 141 | [[SPKSpark sharedInstance].webClient login:^(NSString *authToken) { 142 | user.token = authToken; 143 | [user store]; 144 | dispatch_async(dispatch_get_main_queue(), ^{ 145 | [self spinSpinner:NO]; 146 | self.errorLabel.text = @""; 147 | [self performSegueWithIdentifier:@"loading" sender:sender]; 148 | }); 149 | } failure:^(NSString *failure) { 150 | dispatch_async(dispatch_get_main_queue(), ^{ 151 | [self spinSpinner:NO]; 152 | 153 | self.userIdTextField.enabled = YES; 154 | self.passwordTextField.enabled = YES; 155 | 156 | self.errorLabel.text = @"Username and/or password incorrect"; 157 | 158 | [self.loginButton setTitle:@"LOG IN" forState:UIControlStateNormal]; 159 | }); 160 | }]; 161 | } 162 | 163 | #pragma mark - Private Methods 164 | 165 | - (void)spinSpinner:(BOOL)go 166 | { 167 | if (go) { 168 | self.spinnerImageView.hidden = NO; 169 | 170 | CABasicAnimation *rotation; 171 | rotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; 172 | rotation.fromValue = [NSNumber numberWithFloat:0]; 173 | rotation.toValue = [NSNumber numberWithFloat:(2*M_PI)]; 174 | rotation.duration = 1.1; // Speed 175 | rotation.repeatCount = HUGE_VALF; // Repeat forever. Can be a finite number. 176 | [self.spinnerImageView.layer addAnimation:rotation forKey:@"Spin"]; 177 | } else { 178 | self.spinnerImageView.hidden = YES; 179 | [self.spinnerImageView.layer removeAllAnimations]; 180 | } 181 | } 182 | 183 | @end 184 | -------------------------------------------------------------------------------- /App/SPKSpark.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKSpark.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKSpark.h" 9 | #import "SPKWebClient.h" 10 | #import "SPKCore.h" 11 | #import "NSData+HexString.h" 12 | 13 | @interface SPKSpark () 14 | 15 | @property (nonatomic, strong) dispatch_queue_t workQueue; 16 | @property (nonatomic, strong) dispatch_queue_t coreQueue; 17 | @property (nonatomic, strong) dispatch_queue_t retryQueue; 18 | @property (nonatomic, strong) NSMutableDictionary *coresInternal; 19 | @property (nonatomic, strong) SPKCore *activeCore; 20 | 21 | @end 22 | 23 | @implementation SPKSpark 24 | 25 | + (SPKSpark *)sharedInstance 26 | { 27 | static SPKSpark *_sharedInstance = nil; 28 | static dispatch_once_t onceToken; 29 | dispatch_once(&onceToken, ^{ 30 | _sharedInstance = [[SPKSpark alloc] init]; 31 | }); 32 | 33 | return _sharedInstance; 34 | } 35 | 36 | - (id)init 37 | { 38 | if (self = [super init]) { 39 | _user = [[SPKUser alloc] init]; 40 | _webClient = [[SPKWebClient alloc] initWithUser:_user]; 41 | _smartConfig = [[SPKSmartConfig alloc] init]; 42 | _coresInternal = [NSMutableDictionary dictionaryWithCapacity:10]; 43 | _coreQueue = dispatch_queue_create("Spark Core", DISPATCH_QUEUE_SERIAL); 44 | _workQueue = dispatch_queue_create("Spark Work", DISPATCH_QUEUE_CONCURRENT); 45 | _retryQueue = dispatch_queue_create("Spark Retry", DISPATCH_QUEUE_CONCURRENT); 46 | 47 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(coreConfig:) name:kSPKSmartConfigConfigCore object:self.smartConfig]; 48 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(coreHello:) name:kSPKSmartConfigHelloCore object:self.smartConfig]; 49 | } 50 | 51 | return self; 52 | } 53 | 54 | - (void)clearCores 55 | { 56 | dispatch_sync(self.coreQueue, ^{ 57 | [self.coresInternal removeAllObjects]; 58 | }); 59 | } 60 | 61 | - (NSArray *)cores 62 | { 63 | __block NSArray *filtedCores; 64 | 65 | dispatch_sync(self.coreQueue, ^{ 66 | filtedCores = [[SPKSpark sharedInstance].coresInternal.allValues filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"state != %d", SPKCoreStateAlreadyClaimed]]; 67 | }); 68 | 69 | return filtedCores; 70 | } 71 | 72 | - (void)addCore:(SPKCore *)core 73 | { 74 | dispatch_sync(self.coreQueue, ^{ 75 | if (![self.coresInternal.allKeys containsObject:core.coreId]) { 76 | self.coresInternal[core.coreId] = core; 77 | } 78 | }); 79 | } 80 | 81 | - (NSArray *)coresInState:(SPKCoreState)state 82 | { 83 | __block NSArray *filtedCores; 84 | 85 | dispatch_sync(self.coreQueue, ^{ 86 | filtedCores = [[SPKSpark sharedInstance].coresInternal.allValues filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"state == %d", state]]; 87 | }); 88 | 89 | return filtedCores; 90 | } 91 | 92 | - (void)activateCore:(SPKCore *)core 93 | { 94 | dispatch_sync(self.coreQueue, ^{ 95 | self.activeCore = core; 96 | }); 97 | } 98 | 99 | #pragma mark - Smart Config Delegate 100 | 101 | - (void)coreConfig:(NSNotification *)notification 102 | { 103 | // When the Smart Config mDNS signal is received, simply ignore it 104 | DDLogInfo(@"Core SmartConfig detected"); 105 | } 106 | 107 | - (void)coreHello:(NSNotification *)notification 108 | { 109 | 110 | NSData *coreId = notification.userInfo[@"coreId"]; 111 | DDLogInfo(@"Core Hello'd with CoreId: %@", [coreId hexString]); 112 | 113 | SPKCore *core = self.coresInternal[coreId]; 114 | 115 | if (!core) { 116 | core = [[SPKCore alloc] init]; 117 | core.coreId = coreId; 118 | [self addCore:core]; 119 | } 120 | 121 | if (core.state != SPKCoreStateHello) { 122 | core.state = SPKCoreStateHello; 123 | 124 | double delayInSeconds = 2.0; 125 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 126 | dispatch_after(popTime, self.workQueue, ^(void) { 127 | DDLogVerbose(@"Calling handleHello"); 128 | [self handleHello:core retryCount:0]; 129 | }); 130 | } 131 | } 132 | 133 | - (void)handleHello:(SPKCore *)core retryCount:(NSUInteger)retryCount 134 | { 135 | 136 | #if TARGET_IPHONE_SIMULATOR 137 | core.state = SPKCoreStateAttached; 138 | core.connected = YES; 139 | #else 140 | dispatch_sync(self.coreQueue, ^{ 141 | 142 | DDLogVerbose(@"Handling Core"); 143 | 144 | [[SPKSpark sharedInstance].webClient attach:core.coreId success:^(NSData *coreId) { 145 | core.connected = YES; 146 | core.state = SPKCoreStateAttached; 147 | 148 | self.activeCore = core; 149 | 150 | [[NSNotificationCenter defaultCenter] postNotificationName:kSPKSmartConfigHelloCore object:self userInfo:@{ @"coreId" : core.coreId }]; 151 | } offline:^ { 152 | if (retryCount < 3) { 153 | DDLogVerbose(@"Retrying Core"); 154 | double delayInSeconds = 5.0; 155 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 156 | dispatch_after(popTime, self.retryQueue, ^(void) { 157 | [self handleHello:core retryCount:retryCount+1]; 158 | }); 159 | } else { 160 | core.connected = NO; 161 | core.state = SPKCoreStateFailed; 162 | DDLogVerbose(@"Giving up trying to claim Core"); 163 | } 164 | } alreadyClaimed:^ { 165 | core.state = SPKCoreStateAlreadyClaimed; 166 | DDLogVerbose(@"Core already claimed, skipping"); 167 | } failure:^(NSString *message) { 168 | core.connected = NO; 169 | core.state = SPKCoreStateFailed; 170 | DDLogWarn(@"Problem attaching core: %@", message); 171 | }]; 172 | }); 173 | #endif 174 | } 175 | 176 | @end 177 | -------------------------------------------------------------------------------- /Controllers/SPKCoreNamingViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKCoreNamingViewController.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKCoreNamingViewController.h" 9 | #import "SPKCore.h" 10 | #import "SPKSpark.h" 11 | 12 | @interface SPKCoreNamingViewController () 13 | 14 | @property (nonatomic, strong) SPKCore *currentCore; 15 | 16 | @end 17 | 18 | @implementation SPKCoreNamingViewController 19 | 20 | - (void)viewDidLoad 21 | { 22 | [super viewDidLoad]; 23 | 24 | self.nameTextField.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 8, 20)]; 25 | self.nameTextField.leftViewMode = UITextFieldViewModeAlways; 26 | } 27 | 28 | - (void)viewWillAppear:(BOOL)animated 29 | { 30 | [super viewWillAppear:animated]; 31 | 32 | NSArray *cores = [[SPKSpark sharedInstance] coresInState:SPKCoreStateAttached]; 33 | 34 | self.currentCore = [cores firstObject]; 35 | 36 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(coreHello:) name:kSPKSmartConfigHelloCore object:[SPKSpark sharedInstance]]; 37 | 38 | [self updateTitle]; 39 | self.nameTextField.text = self.currentCore.name; 40 | self.infoLabel.text = @"Name the Core that's shouting rainbows at you."; 41 | 42 | self.spinnerImageView.hidden = YES; 43 | 44 | [[SPKSpark sharedInstance].webClient signal:self.currentCore.coreId on:YES]; 45 | 46 | #if TARGET_IPHONE_SIMULATOR 47 | uint8_t coreId[] = { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 }; 48 | NSNotification *notification = [[NSNotification alloc] initWithName:kSPKSmartConfigHelloCore object:[SPKSpark sharedInstance].smartConfig userInfo:@{ @"coreId": [NSData dataWithBytes:coreId length:12] }]; 49 | [[NSNotificationCenter defaultCenter] performSelectorInBackground:@selector(postNotification:) withObject:notification]; 50 | #endif 51 | 52 | } 53 | 54 | - (void)viewWillDisappear:(BOOL)animated 55 | { 56 | [super viewWillDisappear:animated]; 57 | 58 | [[NSNotificationCenter defaultCenter] removeObserver:self name:kSPKSmartConfigHelloCore object:[SPKSpark sharedInstance]]; 59 | if ([SPKSpark sharedInstance].smartConfig.isBroadcasting) { 60 | [[SPKSpark sharedInstance].smartConfig stopTransmittingSettings]; 61 | [[SPKSpark sharedInstance].smartConfig stopCoAPListening]; 62 | } 63 | } 64 | 65 | - (CGFloat)keyboardHeightAdjust 66 | { 67 | return 145.0; 68 | } 69 | 70 | - (void)dismissKeyboard 71 | { 72 | if (self.nameTextField.isFirstResponder) { 73 | [self.nameTextField resignFirstResponder]; 74 | } 75 | } 76 | 77 | #pragma mark - TextField Delegate 78 | 79 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 80 | { 81 | NSUInteger length = [textField.text length]; 82 | self.nameButton.enabled = (length > 1 || ![string isEqualToString:@""]); 83 | 84 | return YES; 85 | } 86 | 87 | #pragma mark - Smart Config Delegate 88 | 89 | - (void)coreHello:(NSNotification *)notification 90 | { 91 | dispatch_async(dispatch_get_main_queue(), ^{ 92 | [self updateTitle]; 93 | }); 94 | } 95 | 96 | #pragma mark - Actions 97 | 98 | - (IBAction)configureName:(id)sender 99 | { 100 | NSString *name = self.nameTextField.text; 101 | 102 | [self spinSpinner:YES]; 103 | 104 | #if !TARGET_IPHONE_SIMULATOR 105 | [[SPKSpark sharedInstance].webClient name:self.currentCore.coreId label:name success:^ { 106 | #endif 107 | self.currentCore.name = name; 108 | self.currentCore.state = SPKCoreStateReady; 109 | self.currentCore.connected = YES; 110 | [[SPKSpark sharedInstance].webClient signal:self.currentCore.coreId on:NO]; 111 | dispatch_async(dispatch_get_main_queue(), ^{ 112 | [self spinSpinner:NO]; 113 | NSArray *cores = [[SPKSpark sharedInstance] coresInState:SPKCoreStateAttached]; 114 | if (cores.count) { 115 | self.currentCore = [cores firstObject]; 116 | self.nameTextField.text = self.currentCore.name; 117 | self.infoLabel.text = @"Core was named. Name the next one that's shouting rainbows at you."; 118 | [[SPKSpark sharedInstance].webClient signal:self.currentCore.coreId on:YES]; 119 | } else { 120 | [self performSegueWithIdentifier:@"tinker" sender:nil]; 121 | } 122 | }); 123 | #if !TARGET_IPHONE_SIMULATOR 124 | } failure:^{ 125 | self.currentCore.connected = NO; 126 | self.currentCore.name = @"no-name-core"; 127 | dispatch_async(dispatch_get_main_queue(), ^{ 128 | [self spinSpinner:NO]; 129 | self.infoLabel.text = @"Failed to name core."; 130 | }); 131 | DDLogError(@"Failed to name core"); 132 | }]; 133 | #endif 134 | } 135 | 136 | #pragma mark - Private Methods 137 | 138 | - (void)updateTitle 139 | { 140 | NSUInteger count = [[[SPKSpark sharedInstance] coresInState:SPKCoreStateAttached] count]; 141 | if (count == 1) { 142 | self.coresPendingLabel.text = @"We found a Core! Let's name it."; 143 | } else if (count > 1) { 144 | self.coresPendingLabel.text = [NSString stringWithFormat:@"We found %u Cores! Let's name them.", count]; 145 | } else { 146 | self.coresPendingLabel.text = @"No new Cores found."; 147 | [self dismissViewControllerAnimated:YES completion:nil]; 148 | } 149 | } 150 | 151 | - (void)spinSpinner:(BOOL)go 152 | { 153 | if (go) { 154 | self.spinnerImageView.hidden = NO; 155 | 156 | CABasicAnimation *rotation; 157 | rotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; 158 | rotation.fromValue = [NSNumber numberWithFloat:0]; 159 | rotation.toValue = [NSNumber numberWithFloat:(2*M_PI)]; 160 | rotation.duration = 1.1; // Speed 161 | rotation.repeatCount = HUGE_VALF; // Repeat forever. Can be a finite number. 162 | [self.spinnerImageView.layer addAnimation:rotation forKey:@"Spin"]; 163 | } else { 164 | self.spinnerImageView.hidden = YES; 165 | [self.spinnerImageView.layer removeAllAnimations]; 166 | } 167 | } 168 | 169 | @end 170 | -------------------------------------------------------------------------------- /Vendor/AFNetworking/AFPropertyListRequestOperation.m: -------------------------------------------------------------------------------- 1 | // AFPropertyListRequestOperation.m 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | #import "AFPropertyListRequestOperation.h" 24 | 25 | static dispatch_queue_t property_list_request_operation_processing_queue() { 26 | static dispatch_queue_t af_property_list_request_operation_processing_queue; 27 | static dispatch_once_t onceToken; 28 | dispatch_once(&onceToken, ^{ 29 | af_property_list_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.property-list-request.processing", DISPATCH_QUEUE_CONCURRENT); 30 | }); 31 | 32 | return af_property_list_request_operation_processing_queue; 33 | } 34 | 35 | @interface AFPropertyListRequestOperation () 36 | @property (readwrite, nonatomic) id responsePropertyList; 37 | @property (readwrite, nonatomic, assign) NSPropertyListFormat propertyListFormat; 38 | @property (readwrite, nonatomic) NSError *propertyListError; 39 | @end 40 | 41 | @implementation AFPropertyListRequestOperation 42 | @synthesize responsePropertyList = _responsePropertyList; 43 | @synthesize propertyListReadOptions = _propertyListReadOptions; 44 | @synthesize propertyListFormat = _propertyListFormat; 45 | @synthesize propertyListError = _propertyListError; 46 | 47 | + (instancetype)propertyListRequestOperationWithRequest:(NSURLRequest *)request 48 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id propertyList))success 49 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id propertyList))failure 50 | { 51 | AFPropertyListRequestOperation *requestOperation = [(AFPropertyListRequestOperation *)[self alloc] initWithRequest:request]; 52 | [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { 53 | if (success) { 54 | success(operation.request, operation.response, responseObject); 55 | } 56 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 57 | if (failure) { 58 | failure(operation.request, operation.response, error, [(AFPropertyListRequestOperation *)operation responsePropertyList]); 59 | } 60 | }]; 61 | 62 | return requestOperation; 63 | } 64 | 65 | - (id)initWithRequest:(NSURLRequest *)urlRequest { 66 | self = [super initWithRequest:urlRequest]; 67 | if (!self) { 68 | return nil; 69 | } 70 | 71 | self.propertyListReadOptions = NSPropertyListImmutable; 72 | 73 | return self; 74 | } 75 | 76 | 77 | - (id)responsePropertyList { 78 | if (!_responsePropertyList && [self.responseData length] > 0 && [self isFinished]) { 79 | NSPropertyListFormat format; 80 | NSError *error = nil; 81 | self.responsePropertyList = [NSPropertyListSerialization propertyListWithData:self.responseData options:self.propertyListReadOptions format:&format error:&error]; 82 | self.propertyListFormat = format; 83 | self.propertyListError = error; 84 | } 85 | 86 | return _responsePropertyList; 87 | } 88 | 89 | - (NSError *)error { 90 | if (_propertyListError) { 91 | return _propertyListError; 92 | } else { 93 | return [super error]; 94 | } 95 | } 96 | 97 | #pragma mark - AFHTTPRequestOperation 98 | 99 | + (NSSet *)acceptableContentTypes { 100 | return [NSSet setWithObjects:@"application/x-plist", nil]; 101 | } 102 | 103 | + (BOOL)canProcessRequest:(NSURLRequest *)request { 104 | return [[[request URL] pathExtension] isEqualToString:@"plist"] || [super canProcessRequest:request]; 105 | } 106 | 107 | - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success 108 | failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure 109 | { 110 | #pragma clang diagnostic push 111 | #pragma clang diagnostic ignored "-Warc-retain-cycles" 112 | #pragma clang diagnostic ignored "-Wgnu" 113 | self.completionBlock = ^ { 114 | if (self.error) { 115 | if (failure) { 116 | dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{ 117 | failure(self, self.error); 118 | }); 119 | } 120 | } else { 121 | dispatch_async(property_list_request_operation_processing_queue(), ^(void) { 122 | id propertyList = self.responsePropertyList; 123 | 124 | if (self.propertyListError) { 125 | if (failure) { 126 | dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{ 127 | failure(self, self.error); 128 | }); 129 | } 130 | } else { 131 | if (success) { 132 | dispatch_async(self.successCallbackQueue ?: dispatch_get_main_queue(), ^{ 133 | success(self, propertyList); 134 | }); 135 | } 136 | } 137 | }); 138 | } 139 | }; 140 | #pragma clang diagnostic pop 141 | } 142 | 143 | @end 144 | -------------------------------------------------------------------------------- /Vendor/AFNetworking/AFImageRequestOperation.h: -------------------------------------------------------------------------------- 1 | // AFImageRequestOperation.h 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | #import 24 | #import "AFHTTPRequestOperation.h" 25 | 26 | #import 27 | 28 | #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) 29 | #import 30 | #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED) 31 | #import 32 | #endif 33 | 34 | /** 35 | `AFImageRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading and processing images. 36 | 37 | ## Acceptable Content Types 38 | 39 | By default, `AFImageRequestOperation` accepts the following MIME types, which correspond to the image formats supported by UIImage or NSImage: 40 | 41 | - `image/tiff` 42 | - `image/jpeg` 43 | - `image/gif` 44 | - `image/png` 45 | - `image/ico` 46 | - `image/x-icon` 47 | - `image/bmp` 48 | - `image/x-bmp` 49 | - `image/x-xbitmap` 50 | - `image/x-win-bitmap` 51 | */ 52 | @interface AFImageRequestOperation : AFHTTPRequestOperation 53 | 54 | /** 55 | An image constructed from the response data. If an error occurs during the request, `nil` will be returned, and the `error` property will be set to the error. 56 | */ 57 | #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) 58 | @property (readonly, nonatomic, strong) UIImage *responseImage; 59 | #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED) 60 | @property (readonly, nonatomic, strong) NSImage *responseImage; 61 | #endif 62 | 63 | #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) 64 | /** 65 | The scale factor used when interpreting the image data to construct `responseImage`. Specifying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the size property. This is set to the value of scale of the main screen by default, which automatically scales images for retina displays, for instance. 66 | */ 67 | @property (nonatomic, assign) CGFloat imageScale; 68 | 69 | /** 70 | Whether to automatically inflate response image data for compressed formats (such as PNG or JPEG). Enabling this can significantly improve drawing performance on iOS when used with `setCompletionBlockWithSuccess:failure:`, as it allows a bitmap representation to be constructed in the background rather than on the main thread. `YES` by default. 71 | */ 72 | @property (nonatomic, assign) BOOL automaticallyInflatesResponseImage; 73 | #endif 74 | 75 | /** 76 | Creates and returns an `AFImageRequestOperation` object and sets the specified success callback. 77 | 78 | @param urlRequest The request object to be loaded asynchronously during execution of the operation. 79 | @param success A block object to be executed when the request finishes successfully. This block has no return value and takes a single argument, the image created from the response data of the request. 80 | 81 | @return A new image request operation 82 | */ 83 | #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) 84 | + (instancetype)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest 85 | success:(void (^)(UIImage *image))success; 86 | #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED) 87 | + (instancetype)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest 88 | success:(void (^)(NSImage *image))success; 89 | #endif 90 | 91 | /** 92 | Creates and returns an `AFImageRequestOperation` object and sets the specified success callback. 93 | 94 | @param urlRequest The request object to be loaded asynchronously during execution of the operation. 95 | @param imageProcessingBlock A block object to be executed after the image request finishes successfully, but before the image is returned in the `success` block. This block takes a single argument, the image loaded from the response body, and returns the processed image. 96 | @param success A block object to be executed when the request finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `image/png`). This block has no return value and takes three arguments: the request object of the operation, the response for the request, and the image created from the response data. 97 | @param failure A block object to be executed when the request finishes unsuccessfully. This block has no return value and takes three arguments: the request object of the operation, the response for the request, and the error associated with the cause for the unsuccessful operation. 98 | 99 | @return A new image request operation 100 | */ 101 | #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) 102 | + (instancetype)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest 103 | imageProcessingBlock:(UIImage *(^)(UIImage *image))imageProcessingBlock 104 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success 105 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure; 106 | #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED) 107 | + (instancetype)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest 108 | imageProcessingBlock:(NSImage *(^)(NSImage *image))imageProcessingBlock 109 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSImage *image))success 110 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure; 111 | #endif 112 | 113 | @end 114 | -------------------------------------------------------------------------------- /Vendor/Lumberjack/DDTTYLogger.h: -------------------------------------------------------------------------------- 1 | #import 2 | #if TARGET_OS_IPHONE 3 | #import 4 | #else 5 | #import 6 | #endif 7 | 8 | #import "DDLog.h" 9 | 10 | /** 11 | * Welcome to Cocoa Lumberjack! 12 | * 13 | * The project page has a wealth of documentation if you have any questions. 14 | * https://github.com/robbiehanson/CocoaLumberjack 15 | * 16 | * If you're new to the project you may wish to read the "Getting Started" wiki. 17 | * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted 18 | * 19 | * 20 | * This class provides a logger for Terminal output or Xcode console output, 21 | * depending on where you are running your code. 22 | * 23 | * As described in the "Getting Started" page, 24 | * the traditional NSLog() function directs it's output to two places: 25 | * 26 | * - Apple System Log (so it shows up in Console.app) 27 | * - StdErr (if stderr is a TTY, so log statements show up in Xcode console) 28 | * 29 | * To duplicate NSLog() functionality you can simply add this logger and an asl logger. 30 | * However, if you instead choose to use file logging (for faster performance), 31 | * you may choose to use only a file logger and a tty logger. 32 | **/ 33 | 34 | @interface DDTTYLogger : DDAbstractLogger 35 | { 36 | NSCalendar *calendar; 37 | NSUInteger calendarUnitFlags; 38 | 39 | NSString *appName; 40 | char *app; 41 | size_t appLen; 42 | 43 | NSString *processID; 44 | char *pid; 45 | size_t pidLen; 46 | 47 | BOOL colorsEnabled; 48 | NSMutableArray *colorProfilesArray; 49 | NSMutableDictionary *colorProfilesDict; 50 | } 51 | 52 | + (DDTTYLogger *)sharedInstance; 53 | 54 | /* Inherited from the DDLogger protocol: 55 | * 56 | * Formatters may optionally be added to any logger. 57 | * 58 | * If no formatter is set, the logger simply logs the message as it is given in logMessage, 59 | * or it may use its own built in formatting style. 60 | * 61 | * More information about formatters can be found here: 62 | * https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomFormatters 63 | * 64 | * The actual implementation of these methods is inherited from DDAbstractLogger. 65 | 66 | - (id )logFormatter; 67 | - (void)setLogFormatter:(id )formatter; 68 | 69 | */ 70 | 71 | /** 72 | * Want to use different colors for different log levels? 73 | * Enable this property. 74 | * 75 | * If you run the application via the Terminal (not Xcode), 76 | * the logger will map colors to xterm-256color or xterm-color (if available). 77 | * 78 | * Xcode does NOT natively support colors in the Xcode debugging console. 79 | * You'll need to install the XcodeColors plugin to see colors in the Xcode console. 80 | * https://github.com/robbiehanson/XcodeColors 81 | * 82 | * The default value if NO. 83 | **/ 84 | @property (readwrite, assign) BOOL colorsEnabled; 85 | 86 | /** 87 | * The default color set (foregroundColor, backgroundColor) is: 88 | * 89 | * - LOG_FLAG_ERROR = (red, nil) 90 | * - LOG_FLAG_WARN = (orange, nil) 91 | * 92 | * You can customize the colors however you see fit. 93 | * Please note that you are passing a flag, NOT a level. 94 | * 95 | * GOOD : [ttyLogger setForegroundColor:pink backgroundColor:nil forFlag:LOG_FLAG_INFO]; // <- Good :) 96 | * BAD : [ttyLogger setForegroundColor:pink backgroundColor:nil forFlag:LOG_LEVEL_INFO]; // <- BAD! :( 97 | * 98 | * LOG_FLAG_INFO = 0...00100 99 | * LOG_LEVEL_INFO = 0...00111 <- Would match LOG_FLAG_INFO and LOG_FLAG_WARN and LOG_FLAG_ERROR 100 | * 101 | * If you run the application within Xcode, then the XcodeColors plugin is required. 102 | * 103 | * If you run the application from a shell, then DDTTYLogger will automatically map the given color to 104 | * the closest available color. (xterm-256color or xterm-color which have 256 and 16 supported colors respectively.) 105 | * 106 | * This method invokes setForegroundColor:backgroundColor:forFlag:context: and passes the default context (0). 107 | **/ 108 | #if TARGET_OS_IPHONE 109 | - (void)setForegroundColor:(UIColor *)txtColor backgroundColor:(UIColor *)bgColor forFlag:(int)mask; 110 | #else 111 | - (void)setForegroundColor:(NSColor *)txtColor backgroundColor:(NSColor *)bgColor forFlag:(int)mask; 112 | #endif 113 | 114 | /** 115 | * Just like setForegroundColor:backgroundColor:flag, but allows you to specify a particular logging context. 116 | * 117 | * A logging context is often used to identify log messages coming from a 3rd party framework, 118 | * although logging context's can be used for many different functions. 119 | * 120 | * Logging context's are explained in further detail here: 121 | * https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomContext 122 | **/ 123 | #if TARGET_OS_IPHONE 124 | - (void)setForegroundColor:(UIColor *)txtColor backgroundColor:(UIColor *)bgColor forFlag:(int)mask context:(int)ctxt; 125 | #else 126 | - (void)setForegroundColor:(NSColor *)txtColor backgroundColor:(NSColor *)bgColor forFlag:(int)mask context:(int)ctxt; 127 | #endif 128 | 129 | /** 130 | * Similar to the methods above, but allows you to map DDLogMessage->tag to a particular color profile. 131 | * For example, you could do something like this: 132 | * 133 | * static NSString *const PurpleTag = @"PurpleTag"; 134 | * 135 | * #define DDLogPurple(frmt, ...) LOG_OBJC_TAG_MACRO(NO, 0, 0, 0, PurpleTag, frmt, ##__VA_ARGS__) 136 | * 137 | * And then in your applicationDidFinishLaunching, or wherever you configure Lumberjack: 138 | * 139 | * #if TARGET_OS_IPHONE 140 | * UIColor *purple = [UIColor colorWithRed:(64/255.0) green:(0/255.0) blue:(128/255.0) alpha:1.0]; 141 | * #else 142 | * NSColor *purple = [NSColor colorWithCalibratedRed:(64/255.0) green:(0/255.0) blue:(128/255.0) alpha:1.0]; 143 | * 144 | * [[DDTTYLogger sharedInstance] setForegroundColor:purple backgroundColor:nil forTag:PurpleTag]; 145 | * [DDLog addLogger:[DDTTYLogger sharedInstance]]; 146 | * 147 | * This would essentially give you a straight NSLog replacement that prints in purple: 148 | * 149 | * DDLogPurple(@"I'm a purple log message!"); 150 | **/ 151 | #if TARGET_OS_IPHONE 152 | - (void)setForegroundColor:(UIColor *)txtColor backgroundColor:(UIColor *)bgColor forTag:(id )tag; 153 | #else 154 | - (void)setForegroundColor:(NSColor *)txtColor backgroundColor:(NSColor *)bgColor forTag:(id )tag; 155 | #endif 156 | 157 | /** 158 | * Clearing color profiles. 159 | **/ 160 | - (void)clearColorsForFlag:(int)mask; 161 | - (void)clearColorsForFlag:(int)mask context:(int)context; 162 | - (void)clearColorsForTag:(id )tag; 163 | - (void)clearColorsForAllFlags; 164 | - (void)clearColorsForAllTags; 165 | - (void)clearAllColors; 166 | 167 | @end 168 | -------------------------------------------------------------------------------- /Vendor/AFNetworking/AFHTTPRequestOperation.h: -------------------------------------------------------------------------------- 1 | // AFHTTPRequestOperation.h 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | #import 24 | #import "AFURLConnectionOperation.h" 25 | 26 | /** 27 | `AFHTTPRequestOperation` is a subclass of `AFURLConnectionOperation` for requests using the HTTP or HTTPS protocols. It encapsulates the concept of acceptable status codes and content types, which determine the success or failure of a request. 28 | */ 29 | @interface AFHTTPRequestOperation : AFURLConnectionOperation 30 | 31 | ///---------------------------------------------- 32 | /// @name Getting HTTP URL Connection Information 33 | ///---------------------------------------------- 34 | 35 | /** 36 | The last HTTP response received by the operation's connection. 37 | */ 38 | @property (readonly, nonatomic, strong) NSHTTPURLResponse *response; 39 | 40 | ///---------------------------------------------------------- 41 | /// @name Managing And Checking For Acceptable HTTP Responses 42 | ///---------------------------------------------------------- 43 | 44 | /** 45 | A Boolean value that corresponds to whether the status code of the response is within the specified set of acceptable status codes. Returns `YES` if `acceptableStatusCodes` is `nil`. 46 | */ 47 | @property (nonatomic, readonly) BOOL hasAcceptableStatusCode; 48 | 49 | /** 50 | A Boolean value that corresponds to whether the MIME type of the response is among the specified set of acceptable content types. Returns `YES` if `acceptableContentTypes` is `nil`. 51 | */ 52 | @property (nonatomic, readonly) BOOL hasAcceptableContentType; 53 | 54 | /** 55 | The callback dispatch queue on success. If `NULL` (default), the main queue is used. 56 | */ 57 | @property (nonatomic, assign) dispatch_queue_t successCallbackQueue; 58 | 59 | /** 60 | The callback dispatch queue on failure. If `NULL` (default), the main queue is used. 61 | */ 62 | @property (nonatomic, assign) dispatch_queue_t failureCallbackQueue; 63 | 64 | ///------------------------------------------------------------ 65 | /// @name Managing Acceptable HTTP Status Codes & Content Types 66 | ///------------------------------------------------------------ 67 | 68 | /** 69 | Returns an `NSIndexSet` object containing the ranges of acceptable HTTP status codes. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 70 | 71 | By default, this is the range 200 to 299, inclusive. 72 | */ 73 | + (NSIndexSet *)acceptableStatusCodes; 74 | 75 | /** 76 | Adds status codes to the set of acceptable HTTP status codes returned by `+acceptableStatusCodes` in subsequent calls by this class and its descendants. 77 | 78 | @param statusCodes The status codes to be added to the set of acceptable HTTP status codes 79 | */ 80 | + (void)addAcceptableStatusCodes:(NSIndexSet *)statusCodes; 81 | 82 | /** 83 | Returns an `NSSet` object containing the acceptable MIME types. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17 84 | 85 | By default, this is `nil`. 86 | */ 87 | + (NSSet *)acceptableContentTypes; 88 | 89 | /** 90 | Adds content types to the set of acceptable MIME types returned by `+acceptableContentTypes` in subsequent calls by this class and its descendants. 91 | 92 | @param contentTypes The content types to be added to the set of acceptable MIME types 93 | */ 94 | + (void)addAcceptableContentTypes:(NSSet *)contentTypes; 95 | 96 | 97 | ///----------------------------------------------------- 98 | /// @name Determining Whether A Request Can Be Processed 99 | ///----------------------------------------------------- 100 | 101 | /** 102 | A Boolean value determining whether or not the class can process the specified request. For example, `AFJSONRequestOperation` may check to make sure the content type was `application/json` or the URL path extension was `.json`. 103 | 104 | @param urlRequest The request that is determined to be supported or not supported for this class. 105 | */ 106 | + (BOOL)canProcessRequest:(NSURLRequest *)urlRequest; 107 | 108 | ///----------------------------------------------------------- 109 | /// @name Setting Completion Block Success / Failure Callbacks 110 | ///----------------------------------------------------------- 111 | 112 | /** 113 | Sets the `completionBlock` property with a block that executes either the specified success or failure block, depending on the state of the request on completion. If `error` returns a value, which can be caused by an unacceptable status code or content type, then `failure` is executed. Otherwise, `success` is executed. 114 | 115 | This method should be overridden in subclasses in order to specify the response object passed into the success block. 116 | 117 | @param success The block to be executed on the completion of a successful request. This block has no return value and takes two arguments: the receiver operation and the object constructed from the response data of the request. 118 | @param failure The block to be executed on the completion of an unsuccessful request. This block has no return value and takes two arguments: the receiver operation and the error that occurred during the request. 119 | */ 120 | - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success 121 | failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; 122 | 123 | @end 124 | 125 | ///---------------- 126 | /// @name Functions 127 | ///---------------- 128 | 129 | /** 130 | Returns a set of MIME types detected in an HTTP `Accept` or `Content-Type` header. 131 | */ 132 | extern NSSet * AFContentTypesFromHTTPHeader(NSString *string); 133 | 134 | -------------------------------------------------------------------------------- /Vendor/AFNetworking/AFJSONRequestOperation.m: -------------------------------------------------------------------------------- 1 | // AFJSONRequestOperation.m 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | #import "AFJSONRequestOperation.h" 24 | 25 | static dispatch_queue_t json_request_operation_processing_queue() { 26 | static dispatch_queue_t af_json_request_operation_processing_queue; 27 | static dispatch_once_t onceToken; 28 | dispatch_once(&onceToken, ^{ 29 | af_json_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.json-request.processing", DISPATCH_QUEUE_CONCURRENT); 30 | }); 31 | 32 | return af_json_request_operation_processing_queue; 33 | } 34 | 35 | @interface AFJSONRequestOperation () 36 | @property (readwrite, nonatomic, strong) id responseJSON; 37 | @property (readwrite, nonatomic, strong) NSError *JSONError; 38 | @property (readwrite, nonatomic, strong) NSRecursiveLock *lock; 39 | @end 40 | 41 | @implementation AFJSONRequestOperation 42 | @synthesize responseJSON = _responseJSON; 43 | @synthesize JSONReadingOptions = _JSONReadingOptions; 44 | @synthesize JSONError = _JSONError; 45 | @dynamic lock; 46 | 47 | + (instancetype)JSONRequestOperationWithRequest:(NSURLRequest *)urlRequest 48 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success 49 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON))failure 50 | { 51 | AFJSONRequestOperation *requestOperation = [(AFJSONRequestOperation *)[self alloc] initWithRequest:urlRequest]; 52 | [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { 53 | if (success) { 54 | success(operation.request, operation.response, responseObject); 55 | } 56 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 57 | if (failure) { 58 | failure(operation.request, operation.response, error, [(AFJSONRequestOperation *)operation responseJSON]); 59 | } 60 | }]; 61 | 62 | return requestOperation; 63 | } 64 | 65 | 66 | - (id)responseJSON { 67 | [self.lock lock]; 68 | if (!_responseJSON && [self.responseData length] > 0 && [self isFinished] && !self.JSONError) { 69 | NSError *error = nil; 70 | 71 | // Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization. 72 | // See https://github.com/rails/rails/issues/1742 73 | if (self.responseString && ![self.responseString isEqualToString:@" "]) { 74 | // Workaround for a bug in NSJSONSerialization when Unicode character escape codes are used instead of the actual character 75 | // See http://stackoverflow.com/a/12843465/157142 76 | NSData *data = [self.responseString dataUsingEncoding:NSUTF8StringEncoding]; 77 | 78 | if (data) { 79 | self.responseJSON = [NSJSONSerialization JSONObjectWithData:data options:self.JSONReadingOptions error:&error]; 80 | } else { 81 | NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; 82 | [userInfo setValue:@"Operation responseData failed decoding as a UTF-8 string" forKey:NSLocalizedDescriptionKey]; 83 | [userInfo setValue:[NSString stringWithFormat:@"Could not decode string: %@", self.responseString] forKey:NSLocalizedFailureReasonErrorKey]; 84 | error = [[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo]; 85 | } 86 | } 87 | 88 | self.JSONError = error; 89 | } 90 | [self.lock unlock]; 91 | 92 | return _responseJSON; 93 | } 94 | 95 | - (NSError *)error { 96 | if (_JSONError) { 97 | return _JSONError; 98 | } else { 99 | return [super error]; 100 | } 101 | } 102 | 103 | #pragma mark - AFHTTPRequestOperation 104 | 105 | + (NSSet *)acceptableContentTypes { 106 | return [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil]; 107 | } 108 | 109 | + (BOOL)canProcessRequest:(NSURLRequest *)request { 110 | return [[[request URL] pathExtension] isEqualToString:@"json"] || [super canProcessRequest:request]; 111 | } 112 | 113 | - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success 114 | failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure 115 | { 116 | #pragma clang diagnostic push 117 | #pragma clang diagnostic ignored "-Warc-retain-cycles" 118 | #pragma clang diagnostic ignored "-Wgnu" 119 | 120 | self.completionBlock = ^ { 121 | if (self.error) { 122 | if (failure) { 123 | dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{ 124 | failure(self, self.error); 125 | }); 126 | } 127 | } else { 128 | dispatch_async(json_request_operation_processing_queue(), ^{ 129 | id JSON = self.responseJSON; 130 | 131 | if (self.error) { 132 | if (failure) { 133 | dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{ 134 | failure(self, self.error); 135 | }); 136 | } 137 | } else { 138 | if (success) { 139 | dispatch_async(self.successCallbackQueue ?: dispatch_get_main_queue(), ^{ 140 | success(self, JSON); 141 | }); 142 | } 143 | } 144 | }); 145 | } 146 | }; 147 | #pragma clang diagnostic pop 148 | } 149 | 150 | @end 151 | -------------------------------------------------------------------------------- /Vendor/AFNetworking/AFXMLRequestOperation.m: -------------------------------------------------------------------------------- 1 | // AFXMLRequestOperation.m 2 | // 3 | // Copyright (c) 2011 Gowalla (http://gowalla.com/) 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | #import "AFXMLRequestOperation.h" 24 | 25 | #include 26 | 27 | static dispatch_queue_t xml_request_operation_processing_queue() { 28 | static dispatch_queue_t af_xml_request_operation_processing_queue; 29 | static dispatch_once_t onceToken; 30 | dispatch_once(&onceToken, ^{ 31 | af_xml_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.xml-request.processing", DISPATCH_QUEUE_CONCURRENT); 32 | }); 33 | 34 | return af_xml_request_operation_processing_queue; 35 | } 36 | 37 | @interface AFXMLRequestOperation () 38 | @property (readwrite, nonatomic, strong) NSXMLParser *responseXMLParser; 39 | #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED 40 | @property (readwrite, nonatomic, strong) NSXMLDocument *responseXMLDocument; 41 | #endif 42 | @property (readwrite, nonatomic, strong) NSError *XMLError; 43 | @end 44 | 45 | @implementation AFXMLRequestOperation 46 | @synthesize responseXMLParser = _responseXMLParser; 47 | #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED 48 | @synthesize responseXMLDocument = _responseXMLDocument; 49 | #endif 50 | @synthesize XMLError = _XMLError; 51 | 52 | + (instancetype)XMLParserRequestOperationWithRequest:(NSURLRequest *)urlRequest 53 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser))success 54 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLParser *XMLParser))failure 55 | { 56 | AFXMLRequestOperation *requestOperation = [(AFXMLRequestOperation *)[self alloc] initWithRequest:urlRequest]; 57 | [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { 58 | if (success) { 59 | success(operation.request, operation.response, responseObject); 60 | } 61 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 62 | if (failure) { 63 | failure(operation.request, operation.response, error, [(AFXMLRequestOperation *)operation responseXMLParser]); 64 | } 65 | }]; 66 | 67 | return requestOperation; 68 | } 69 | 70 | #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED 71 | + (instancetype)XMLDocumentRequestOperationWithRequest:(NSURLRequest *)urlRequest 72 | success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLDocument *document))success 73 | failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLDocument *document))failure 74 | { 75 | AFXMLRequestOperation *requestOperation = [[self alloc] initWithRequest:urlRequest]; 76 | [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, __unused id responseObject) { 77 | if (success) { 78 | NSXMLDocument *XMLDocument = [(AFXMLRequestOperation *)operation responseXMLDocument]; 79 | success(operation.request, operation.response, XMLDocument); 80 | } 81 | } failure:^(AFHTTPRequestOperation *operation, NSError *error) { 82 | if (failure) { 83 | NSXMLDocument *XMLDocument = [(AFXMLRequestOperation *)operation responseXMLDocument]; 84 | failure(operation.request, operation.response, error, XMLDocument); 85 | } 86 | }]; 87 | 88 | return requestOperation; 89 | } 90 | #endif 91 | 92 | 93 | - (NSXMLParser *)responseXMLParser { 94 | if (!_responseXMLParser && [self.responseData length] > 0 && [self isFinished]) { 95 | self.responseXMLParser = [[NSXMLParser alloc] initWithData:self.responseData]; 96 | } 97 | 98 | return _responseXMLParser; 99 | } 100 | 101 | #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED 102 | - (NSXMLDocument *)responseXMLDocument { 103 | if (!_responseXMLDocument && [self.responseData length] > 0 && [self isFinished]) { 104 | NSError *error = nil; 105 | self.responseXMLDocument = [[NSXMLDocument alloc] initWithData:self.responseData options:0 error:&error]; 106 | self.XMLError = error; 107 | } 108 | 109 | return _responseXMLDocument; 110 | } 111 | #endif 112 | 113 | - (NSError *)error { 114 | if (_XMLError) { 115 | return _XMLError; 116 | } else { 117 | return [super error]; 118 | } 119 | } 120 | 121 | #pragma mark - NSOperation 122 | 123 | - (void)cancel { 124 | [super cancel]; 125 | 126 | self.responseXMLParser.delegate = nil; 127 | } 128 | 129 | #pragma mark - AFHTTPRequestOperation 130 | 131 | + (NSSet *)acceptableContentTypes { 132 | return [NSSet setWithObjects:@"application/xml", @"text/xml", nil]; 133 | } 134 | 135 | + (BOOL)canProcessRequest:(NSURLRequest *)request { 136 | return [[[request URL] pathExtension] isEqualToString:@"xml"] || [super canProcessRequest:request]; 137 | } 138 | 139 | - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success 140 | failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure 141 | { 142 | #pragma clang diagnostic push 143 | #pragma clang diagnostic ignored "-Warc-retain-cycles" 144 | #pragma clang diagnostic ignored "-Wgnu" 145 | self.completionBlock = ^ { 146 | dispatch_async(xml_request_operation_processing_queue(), ^(void) { 147 | NSXMLParser *XMLParser = self.responseXMLParser; 148 | 149 | if (self.error) { 150 | if (failure) { 151 | dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{ 152 | failure(self, self.error); 153 | }); 154 | } 155 | } else { 156 | if (success) { 157 | dispatch_async(self.successCallbackQueue ?: dispatch_get_main_queue(), ^{ 158 | success(self, XMLParser); 159 | }); 160 | } 161 | } 162 | }); 163 | }; 164 | #pragma clang diagnostic pop 165 | } 166 | 167 | @end 168 | -------------------------------------------------------------------------------- /Controllers/SPKRegisterViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKRegisterViewController.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKRegisterViewController.h" 9 | #import "SPKSpark.h" 10 | #import "SPKWebClient.h" 11 | 12 | @implementation SPKRegisterViewController 13 | 14 | - (void)viewDidLoad 15 | { 16 | [super viewDidLoad]; 17 | 18 | self.userIdTextField.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 8, 20)]; 19 | self.userIdTextField.leftViewMode = UITextFieldViewModeAlways; 20 | 21 | self.passwordTextField.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 8, 20)]; 22 | self.passwordTextField.leftViewMode = UITextFieldViewModeAlways; 23 | 24 | NSMutableAttributedString *s = [[NSMutableAttributedString alloc] initWithString:@"Terms of Service"]; 25 | [s addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0, [s length])]; 26 | [s addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, [s length])]; 27 | [self.termsButton setAttributedTitle:s forState:UIControlStateNormal]; 28 | 29 | s = [[NSMutableAttributedString alloc] initWithString:@"Privacy Policy"]; 30 | [s addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0, [s length])]; 31 | [s addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, [s length])]; 32 | [self.privacyButton setAttributedTitle:s forState:UIControlStateNormal]; 33 | } 34 | 35 | - (void)viewWillAppear:(BOOL)animated 36 | { 37 | [super viewWillAppear:animated]; 38 | 39 | self.userIdTextField.enabled = YES; 40 | self.passwordTextField.enabled = YES; 41 | [self.registerButton setTitle:@"SIGN UP" forState:UIControlStateNormal]; 42 | self.registerButton.enabled = NO; 43 | self.spinnerImageView.hidden = YES; 44 | 45 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionError:) name:kSPKWebClientConnectionError object:nil]; 46 | } 47 | 48 | - (void)viewWillDisappear:(BOOL)animated 49 | { 50 | [super viewWillDisappear:animated]; 51 | 52 | [[NSNotificationCenter defaultCenter] removeObserver:self name:kSPKWebClientConnectionError object:nil]; 53 | } 54 | 55 | - (void)viewDidAppear:(BOOL)animated 56 | { 57 | [super viewDidAppear:animated]; 58 | } 59 | 60 | - (void)dismissKeyboard 61 | { 62 | if (self.userIdTextField.isFirstResponder) { 63 | [self.userIdTextField resignFirstResponder]; 64 | } else if (self.passwordTextField.isFirstResponder) { 65 | [self.passwordTextField resignFirstResponder]; 66 | } 67 | } 68 | 69 | - (void)viewDidLayoutSubviews 70 | { 71 | [super viewDidLayoutSubviews]; 72 | 73 | if (!isiPhone5) { 74 | CGRect f = self.formView.frame; 75 | f.origin.y -= 50.0; 76 | self.formView.frame = f; 77 | 78 | f = self.legalView.frame; 79 | f.origin.y -= 86.0; 80 | self.legalView.frame = f; 81 | 82 | f = self.logoImageView.frame; 83 | f.origin.x += (f.size.width - (f.size.width * 0.75)) / 2.0; 84 | f.size.height *= 0.75; 85 | f.size.width *= 0.75; 86 | self.logoImageView.frame = f; 87 | } 88 | } 89 | 90 | #pragma mark - Notifications 91 | 92 | - (void)connectionError:(NSNotification *)notifications 93 | { 94 | dispatch_async(dispatch_get_main_queue(), ^{ 95 | self.userIdTextField.enabled = YES; 96 | self.passwordTextField.enabled = YES; 97 | [self.registerButton setTitle:@"SIGN UP" forState:UIControlStateNormal]; 98 | }); 99 | } 100 | 101 | #pragma mark - TextField Delegate 102 | 103 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 104 | { 105 | BOOL r1 = NO; 106 | BOOL r2 = NO; 107 | 108 | NSString *s = self.userIdTextField.text; 109 | if (textField == self.userIdTextField) { 110 | s = [textField.text stringByReplacingCharactersInRange:range withString:string]; 111 | } 112 | r1 = [self isValidEmail:s]; 113 | 114 | s = self.passwordTextField.text; 115 | if (textField == self.passwordTextField) { 116 | s = [textField.text stringByReplacingCharactersInRange:range withString:string]; 117 | } 118 | r2 = s.length > 0; 119 | 120 | self.registerButton.enabled = r1 && r2; 121 | 122 | return YES; 123 | } 124 | 125 | - (BOOL)textFieldShouldReturn:(UITextField *)textField; 126 | { 127 | if (textField == self.userIdTextField) { 128 | [self.passwordTextField becomeFirstResponder]; 129 | } else { 130 | [self.passwordTextField resignFirstResponder]; 131 | [self register:textField]; 132 | } 133 | return NO; 134 | } 135 | 136 | #pragma mark - Actions 137 | 138 | - (IBAction)terms:(id)sender 139 | { 140 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://www.spark.io/tos"]]; 141 | } 142 | 143 | - (IBAction)privacy:(id)sender 144 | { 145 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://www.spark.io/privacy"]]; 146 | } 147 | 148 | - (IBAction)register:(id)sender 149 | { 150 | self.userIdTextField.enabled = NO; 151 | self.passwordTextField.enabled = NO; 152 | 153 | [self.registerButton setTitle:@"SIGNING UP..." forState:UIControlStateNormal]; 154 | 155 | SPKUser *user = [SPKSpark sharedInstance].user; 156 | user.userId = self.userIdTextField.text; 157 | user.password = self.passwordTextField.text; 158 | [SPKSpark sharedInstance].attemptedLogin = NO; 159 | 160 | [self spinSpinner:YES]; 161 | 162 | [[SPKSpark sharedInstance].webClient register:^(NSString *authToken) { 163 | user.token = authToken; 164 | user.firstTime = YES; 165 | [user store]; 166 | dispatch_async(dispatch_get_main_queue(), ^{ 167 | [self spinSpinner:NO]; 168 | self.errorLabel.text = @""; 169 | [self performSegueWithIdentifier:@"loading" sender:sender]; 170 | }); 171 | } failure:^(NSString *failure) { 172 | dispatch_async(dispatch_get_main_queue(), ^{ 173 | [self spinSpinner:NO]; 174 | self.userIdTextField.enabled = YES; 175 | self.passwordTextField.enabled = YES; 176 | [self.registerButton setTitle:@"SIGN UP" forState:UIControlStateNormal]; 177 | 178 | if (!failure) { 179 | self.errorLabel.text = @"Email and/or password invalid"; 180 | } else { 181 | self.errorLabel.text = failure; 182 | } 183 | 184 | }); 185 | }]; 186 | } 187 | 188 | #pragma mark - Private Methods 189 | 190 | - (void)spinSpinner:(BOOL)go 191 | { 192 | if (go) { 193 | self.spinnerImageView.hidden = NO; 194 | 195 | CABasicAnimation *rotation; 196 | rotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; 197 | rotation.fromValue = [NSNumber numberWithFloat:0]; 198 | rotation.toValue = [NSNumber numberWithFloat:(2*M_PI)]; 199 | rotation.duration = 1.1; // Speed 200 | rotation.repeatCount = HUGE_VALF; // Repeat forever. Can be a finite number. 201 | [self.spinnerImageView.layer addAnimation:rotation forKey:@"Spin"]; 202 | } else { 203 | self.spinnerImageView.hidden = YES; 204 | [self.spinnerImageView.layer removeAllAnimations]; 205 | } 206 | } 207 | 208 | @end 209 | -------------------------------------------------------------------------------- /Controllers/SPKTinkerViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKTinkerViewController.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKTinkerViewController.h" 9 | #import "SPKCore.h" 10 | #import "SPKCorePin.h" 11 | #import "SPKSpark.h" 12 | 13 | @interface SPKTinkerViewController () 14 | 15 | @property (nonatomic, strong) NSMutableDictionary *pinViews; 16 | 17 | @end 18 | 19 | @implementation SPKTinkerViewController 20 | 21 | - (void)viewDidLoad 22 | { 23 | self.pinViews = [NSMutableDictionary dictionaryWithCapacity:16]; 24 | 25 | self.pinFunctionView.delegate = self; 26 | 27 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissFirstTime)]; 28 | [self.firstTimeView addGestureRecognizer:tap]; 29 | 30 | SPKCore *activeCore = [SPKSpark sharedInstance].activeCore; 31 | 32 | for (SPKCorePin *pin in activeCore.pins) { 33 | SPKCorePinView *v = [[SPKCorePinView alloc] init]; 34 | v.pin = pin; 35 | v.delegate = self; 36 | self.pinViews[pin.label] = v; 37 | [self.view insertSubview:v belowSubview:self.nameLabel]; 38 | } 39 | 40 | if (!isiPhone5) { 41 | self.shadowImageView.hidden = YES; 42 | self.nameLabel.textColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.3]; 43 | } 44 | } 45 | 46 | - (void)viewWillAppear:(BOOL)animated 47 | { 48 | [super viewWillAppear:animated]; 49 | 50 | SPKCore *activeCore = [SPKSpark sharedInstance].activeCore; 51 | 52 | for (SPKCorePin *pin in activeCore.pins) { 53 | SPKCorePinView *v = self.pinViews[pin.label]; 54 | v.pin = pin; 55 | } 56 | 57 | self.nameLabel.text = activeCore.name; 58 | 59 | self.firstTimeView.hidden = ![SPKSpark sharedInstance].user.firstTime; 60 | self.tinkerLogoImageView.hidden = NO; 61 | self.nameLabel.hidden = NO; 62 | } 63 | 64 | - (void)viewDidLayoutSubviews 65 | { 66 | [super viewDidLayoutSubviews]; 67 | 68 | if (!isiPhone5) { 69 | CGRect f = self.nameLabel.frame; 70 | f.origin.y = 340.0; 71 | self.nameLabel.frame = f; 72 | 73 | f = self.firstTimeView.frame; 74 | f.origin.y += 1.0; 75 | self.firstTimeView.frame = f; 76 | 77 | f = self.tinkerLogoImageView.frame; 78 | f.origin.y -= 30.0; 79 | self.tinkerLogoImageView.frame = f; 80 | } 81 | } 82 | 83 | - (UIStatusBarStyle)preferredStatusBarStyle 84 | { 85 | return UIStatusBarStyleLightContent; 86 | } 87 | 88 | #pragma mark - Pin Function Delegate 89 | 90 | - (void)pinFunctionSelected:(SPKCorePinFunction)function 91 | { 92 | SPKCorePin *pin = self.pinFunctionView.pin; 93 | pin.selectedFunction = function; 94 | self.pinFunctionView.pin = pin; 95 | 96 | SPKCorePinView *pinView = self.pinViews[pin.label]; 97 | [pinView.pin resetValue]; 98 | [pinView refresh]; 99 | } 100 | 101 | #pragma mark - Core Pin View Delegate 102 | 103 | - (void)pinViewAdjusted:(SPKCorePinView *)pinView newValue:(NSUInteger)newValue 104 | { 105 | [pinView.pin adjustValue:newValue]; 106 | 107 | [pinView noslider]; 108 | [pinView refresh]; 109 | [pinView activate]; 110 | [self pinCallHome:pinView]; 111 | for (SPKCorePinView *pv in self.pinViews.allValues) { 112 | [pv showDetails]; 113 | } 114 | } 115 | 116 | - (void)pinViewHeld:(SPKCorePinView *)pinView 117 | { 118 | if (![self slidingAnalogWritePinView] && !pinView.active) { 119 | [self showFunctionView:pinView]; 120 | } 121 | } 122 | 123 | - (void)pinViewTapped:(SPKCorePinView *)pinView inPin:(BOOL)inPin 124 | { 125 | if (!self.pinFunctionView.hidden) { 126 | self.pinFunctionView.hidden = YES; 127 | for (SPKCorePinView *pv in self.pinViews.allValues) { 128 | pv.alpha = 1.0; 129 | } 130 | self.tinkerLogoImageView.hidden = NO; 131 | self.nameLabel.hidden = NO; 132 | } else if (!pinView.active) { 133 | SPKCorePinView *slidingAnalogWritePinView = [self slidingAnalogWritePinView]; 134 | 135 | if (!slidingAnalogWritePinView && pinView.pin.selectedFunction == SPKCorePinFunctionAnalogWrite) { 136 | for (SPKCorePinView *pinView in self.pinViews.allValues) { 137 | [pinView hideDetails]; 138 | } 139 | 140 | self.tinkerLogoImageView.hidden = YES; 141 | if (!isiPhone5) { 142 | self.nameLabel.hidden = YES; 143 | } 144 | [self.view bringSubviewToFront:pinView]; 145 | [pinView slider]; 146 | } else if (!slidingAnalogWritePinView && inPin && pinView.pin.selectedFunction == SPKCorePinFunctionNone) { 147 | [self showFunctionView:pinView]; 148 | } else if (!slidingAnalogWritePinView && pinView.pin.selectedFunction == SPKCorePinFunctionDigitalWrite) { 149 | if (!pinView.pin.valueSet) { 150 | [pinView.pin adjustValue:1]; 151 | } else { 152 | [pinView.pin adjustValue:!pinView.pin.value]; 153 | } 154 | 155 | [pinView refresh]; 156 | [pinView activate]; 157 | [self pinCallHome:pinView]; 158 | } else if (!slidingAnalogWritePinView && inPin) { 159 | if (pinView.pin.selectedFunction == SPKCorePinFunctionAnalogRead || pinView.pin.selectedFunction == SPKCorePinFunctionDigitalRead) { 160 | [pinView showDetails]; 161 | [self.view bringSubviewToFront:pinView]; 162 | [pinView activate]; 163 | [self pinCallHome:pinView]; 164 | } 165 | } else if (slidingAnalogWritePinView && pinView != slidingAnalogWritePinView) { 166 | [slidingAnalogWritePinView noslider]; 167 | [slidingAnalogWritePinView refresh]; 168 | [slidingAnalogWritePinView activate]; 169 | [self pinCallHome:slidingAnalogWritePinView]; 170 | for (SPKCorePinView *pinView in self.pinViews.allValues) { 171 | [pinView showDetails]; 172 | } 173 | } 174 | } 175 | } 176 | 177 | #pragma mark - Private Methods 178 | 179 | - (void)dismissFirstTime 180 | { 181 | self.firstTimeView.hidden = YES; 182 | [SPKSpark sharedInstance].user.firstTime = NO; 183 | } 184 | 185 | - (void)showFunctionView:(SPKCorePinView *)pinView 186 | { 187 | if (self.pinFunctionView.hidden) { 188 | self.tinkerLogoImageView.hidden = YES; 189 | if (!isiPhone5) { 190 | self.nameLabel.hidden = YES; 191 | } 192 | self.pinFunctionView.pin = pinView.pin; 193 | self.pinFunctionView.hidden = NO; 194 | for (SPKCorePinView *pv in self.pinViews.allValues) { 195 | if (pv != pinView) { 196 | pv.alpha = 0.1; 197 | } 198 | } 199 | [self.view bringSubviewToFront:self.pinFunctionView]; 200 | } 201 | } 202 | 203 | - (SPKCorePinView *)slidingAnalogWritePinView 204 | { 205 | for (SPKCorePinView *pv in self.pinViews.allValues) { 206 | if (pv.pin.selectedFunction == SPKCorePinFunctionAnalogWrite && pv.sliding) { 207 | return pv; 208 | } 209 | } 210 | 211 | return nil; 212 | } 213 | 214 | - (void)pinCallHome:(SPKCorePinView *)pinView 215 | { 216 | [[SPKSpark sharedInstance].webClient coreId:[SPKSpark sharedInstance].activeCore.coreId pin:pinView.pin.label function:pinView.pin.selectedFunction value:pinView.pin.value success:^(NSUInteger value) { 217 | [SPKSpark sharedInstance].activeCore.connected = YES; 218 | dispatch_async(dispatch_get_main_queue(), ^{ 219 | if (pinView.pin.selectedFunction == SPKCorePinFunctionDigitalWrite || pinView.pin.selectedFunction == SPKCorePinFunctionAnalogWrite) { 220 | if (value == -1) { 221 | [[[UIAlertView alloc] initWithTitle:@"Core Pin" message:@"There was a problem writing to this pin." delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil] show]; 222 | [pinView.pin resetValue]; 223 | } 224 | } else { 225 | [pinView.pin adjustValue:value]; 226 | } 227 | 228 | [CATransaction begin]; 229 | [CATransaction setDisableActions:YES]; 230 | [pinView deactivate]; 231 | self.tinkerLogoImageView.hidden = NO; 232 | self.nameLabel.hidden = NO; 233 | [pinView refresh]; 234 | [CATransaction commit]; 235 | }); 236 | } failure:^(NSString *errorMessage) { 237 | [SPKSpark sharedInstance].activeCore.connected = NO; 238 | dispatch_async(dispatch_get_main_queue(), ^{ 239 | [[[UIAlertView alloc] initWithTitle:@"Core Pin" message:errorMessage delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil] show]; 240 | [pinView.pin resetValue]; 241 | [CATransaction begin]; 242 | [CATransaction setDisableActions:YES]; 243 | [pinView deactivate]; 244 | self.tinkerLogoImageView.hidden = NO; 245 | self.nameLabel.hidden = NO; 246 | [pinView refresh]; 247 | [CATransaction commit]; 248 | }); 249 | }]; 250 | } 251 | 252 | @end 253 | -------------------------------------------------------------------------------- /Controllers/SPKNetworkSettingsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SPKNetworkSettingsViewController.m 3 | // Spark IOS 4 | // 5 | // Copyright (c) 2013 Spark Devices. All rights reserved. 6 | // 7 | 8 | #import "SPKNetworkSettingsViewController.h" 9 | #import "SPKSpark.h" 10 | #import "NSData+HexString.h" 11 | #import "SPKCore.h" 12 | #import "SPKTimer.h" 13 | #import "FirstTimeConfig.h" 14 | 15 | @interface SPKNetworkSettingsViewController () 16 | 17 | @property (nonatomic, strong) SPKSmartConfig *smartConfig; 18 | @property (nonatomic, strong) dispatch_queue_t timerQueue; 19 | @property (nonatomic, strong) SPKTimer *timer; 20 | 21 | @end 22 | 23 | @implementation SPKNetworkSettingsViewController 24 | 25 | - (void)viewDidLoad 26 | { 27 | [super viewDidLoad]; 28 | 29 | self.smartConfig = [SPKSpark sharedInstance].smartConfig; 30 | self.timerQueue = dispatch_queue_create("networkSettingsTimer", DISPATCH_QUEUE_SERIAL); 31 | 32 | self.ssidTextField.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 8, 20)]; 33 | self.ssidTextField.leftViewMode = UITextFieldViewModeAlways; 34 | 35 | self.passwordTextField.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 8, 20)]; 36 | self.passwordTextField.leftViewMode = UITextFieldViewModeAlways; 37 | 38 | self.keyTextField.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 8, 20)]; 39 | self.keyTextField.leftViewMode = UITextFieldViewModeAlways; 40 | } 41 | 42 | - (void)viewWillAppear:(BOOL)animated 43 | { 44 | [super viewWillAppear:animated]; 45 | 46 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(coreHello:) name:kSPKSmartConfigHelloCore object:[SPKSpark sharedInstance]]; 47 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wifiChanged:) name:kSPKWebClientReachabilityChange object:nil]; 48 | 49 | self.messageLabel.text = @""; 50 | self.passwordTextField.text = @""; 51 | self.keyTextField.text = @""; 52 | self.ssidTextField.text = @""; 53 | 54 | if ([[[SPKSpark sharedInstance] coresInState:SPKCoreStateReady] count]) { 55 | self.logoutButton.hidden = YES; 56 | self.tinkerButton.hidden = NO; 57 | } else { 58 | self.logoutButton.hidden = NO; 59 | self.tinkerButton.hidden = YES; 60 | } 61 | 62 | self.spinnerImageView.hidden = YES; 63 | } 64 | 65 | - (void)viewDidAppear:(BOOL)animated 66 | { 67 | [super viewDidAppear:animated]; 68 | [self handleWifi]; 69 | } 70 | 71 | - (void)viewWillDisappear:(BOOL)animated 72 | { 73 | [super viewWillDisappear:animated]; 74 | 75 | [self.timer invalidate]; 76 | [[NSNotificationCenter defaultCenter] removeObserver:self name:kSPKSmartConfigHelloCore object:[SPKSpark sharedInstance]]; 77 | [[NSNotificationCenter defaultCenter] removeObserver:self name:kSPKWebClientReachabilityChange object:nil]; 78 | } 79 | 80 | - (CGFloat)keyboardHeightAdjust 81 | { 82 | if (isiPhone5) { 83 | return 145.0; 84 | } else { 85 | return 85.0; 86 | } 87 | } 88 | 89 | - (void)dismissKeyboard 90 | { 91 | if (self.ssidTextField.isFirstResponder) { 92 | [self.ssidTextField resignFirstResponder]; 93 | } else if (self.passwordTextField.isFirstResponder) { 94 | [self.passwordTextField resignFirstResponder]; 95 | } else if (self.keyTextField.isFirstResponder) { 96 | [self.keyTextField resignFirstResponder]; 97 | } 98 | } 99 | 100 | - (void)viewDidLayoutSubviews 101 | { 102 | if (!isiPhone5) { 103 | CGRect f = self.logoutButton.frame; 104 | f.origin.y -= 76; 105 | self.logoutButton.frame = f; 106 | 107 | f = self.tinkerButton.frame; 108 | f.origin.y -= 76; 109 | self.tinkerButton.frame = f; 110 | } 111 | } 112 | 113 | #pragma mark - TextField Delegate 114 | 115 | - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 116 | { 117 | if (textField == self.keyTextField) { 118 | NSUInteger length = [textField.text length]; 119 | self.connectButton.enabled = (length >= 15 && ![string isEqualToString:@""]); 120 | return [string isEqualToString:@""] || length <= 15; 121 | } else { 122 | return YES; 123 | } 124 | } 125 | 126 | #pragma mark - Actions 127 | 128 | - (IBAction)connect:(id)sender 129 | { 130 | if (self.smartConfig.isBroadcasting) { 131 | [self spinSpinner:NO]; 132 | if ([[[SPKSpark sharedInstance] coresInState:SPKCoreStateReady] count]) { 133 | self.logoutButton.hidden = YES; 134 | self.tinkerButton.hidden = NO; 135 | } else { 136 | self.logoutButton.hidden = NO; 137 | self.tinkerButton.hidden = YES; 138 | } 139 | 140 | [self.timer invalidate]; 141 | [self.smartConfig stopTransmittingSettings]; 142 | [self.smartConfig stopCoAPListening]; 143 | [self.connectButton setTitle:@"CONNECT" forState:UIControlStateNormal]; 144 | [self.connectButton setBackgroundImage:[UIImage imageNamed:@"connect-btn"] forState:UIControlStateNormal]; 145 | self.ssidTextField.enabled = YES; 146 | self.passwordTextField.enabled = YES; 147 | self.keyTextField.enabled = YES; 148 | self.messageLabel.text = @""; 149 | } else { 150 | self.logoutButton.hidden = YES; 151 | self.tinkerButton.hidden = YES; 152 | [self spinSpinner:YES]; 153 | 154 | NSUInteger duration = 90.0; 155 | #if TARGET_IPHONE_SIMULATOR 156 | duration = 20.0; 157 | #endif 158 | self.timer = [SPKTimer repeatingTimerWithTimeInterval:duration queue:self.timerQueue block:^{ 159 | [self timedOut]; 160 | }]; 161 | 162 | [self.connectButton setTitle:@"STOP" forState:UIControlStateNormal]; 163 | [self.connectButton setBackgroundImage:[UIImage imageNamed:@"not-found-btn"] forState:UIControlStateNormal]; 164 | self.ssidTextField.enabled = NO; 165 | self.passwordTextField.enabled = NO; 166 | self.keyTextField.enabled = NO; 167 | NSString *ipAddress = [FirstTimeConfig getGatewayAddress]; 168 | DDLogInfo(@"Using router address: %@", ipAddress); 169 | NSString *aesKey = self.aesKeyButton.selected ? self.keyTextField.text : @"sparkdevices2013"; 170 | [self.smartConfig configureWithPassword:self.passwordTextField.text aesKey:aesKey]; 171 | [self.smartConfig startTransmittingSettings]; 172 | 173 | #if TARGET_IPHONE_SIMULATOR 174 | uint8_t coreId[] = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }; 175 | NSNotification *notification = [[NSNotification alloc] initWithName:kSPKSmartConfigHelloCore object:[SPKSpark sharedInstance].smartConfig userInfo:@{ @"coreId": [NSData dataWithBytes:coreId length:12] }]; 176 | [[NSNotificationCenter defaultCenter] performSelectorInBackground:@selector(postNotification:) withObject:notification]; 177 | #endif 178 | } 179 | } 180 | 181 | - (IBAction)aesKeyToggle:(id)sender 182 | { 183 | UIButton *button = sender; 184 | button.selected = !button.selected; 185 | self.keyTextField.hidden = !button.selected; 186 | self.keyBackgroundImageView.hidden = !button.selected; 187 | if (button.selected) { 188 | self.connectButton.enabled = (self.keyTextField.text.length == 16); 189 | } else { 190 | self.connectButton.enabled = YES; 191 | } 192 | } 193 | 194 | - (IBAction)logout:(id)sender 195 | { 196 | [[SPKSpark sharedInstance].user clear]; 197 | [[SPKSpark sharedInstance] clearCores]; 198 | [self spinSpinner:NO]; 199 | [self performSegueWithIdentifier:@"login" sender:sender]; 200 | } 201 | 202 | #pragma mark - Notifications 203 | 204 | - (void)wifiChanged:(NSNotification *)notification 205 | { 206 | dispatch_async(dispatch_get_main_queue(), ^{ 207 | [self handleWifi]; 208 | }); 209 | } 210 | 211 | - (void)coreHello:(NSNotification *)notification 212 | { 213 | dispatch_async(dispatch_get_main_queue(), ^{ 214 | [self spinSpinner:NO]; 215 | [self performSegueWithIdentifier:@"connect" sender:nil]; 216 | }); 217 | } 218 | 219 | #pragma mark - Private Methods 220 | 221 | - (void)timedOut 222 | { 223 | [self.timer invalidate]; 224 | if ([SPKSpark sharedInstance].smartConfig.isBroadcasting) { 225 | [[SPKSpark sharedInstance].smartConfig stopTransmittingSettings]; 226 | [[SPKSpark sharedInstance].smartConfig stopCoAPListening]; 227 | } 228 | dispatch_async(dispatch_get_main_queue(), ^{ 229 | [self spinSpinner:NO]; 230 | [self performSegueWithIdentifier:@"notfound" sender:nil]; 231 | }); 232 | } 233 | 234 | - (void)handleWifi 235 | { 236 | #if TARGET_IPHONE_SIMULATOR 237 | self.ssidTextField.text = @"Simulator"; 238 | #else 239 | if ([FirstTimeConfig getSSID]) { 240 | self.ssidTextField.text = [FirstTimeConfig getSSID]; 241 | self.connectButton.enabled = YES; 242 | } else { 243 | [self spinSpinner:NO]; 244 | self.ssidTextField.text = @"No Wifi"; 245 | self.connectButton.enabled = NO; 246 | [[[UIAlertView alloc] initWithTitle:@"Smart Config Error" message:@"You must be connected to a Wi-Fi network to connect your Core." delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil] show]; 247 | } 248 | #endif 249 | } 250 | 251 | - (void)spinSpinner:(BOOL)go 252 | { 253 | if (go) { 254 | self.spinnerImageView.hidden = NO; 255 | 256 | CABasicAnimation *rotation; 257 | rotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; 258 | rotation.fromValue = [NSNumber numberWithFloat:0]; 259 | rotation.toValue = [NSNumber numberWithFloat:(2*M_PI)]; 260 | rotation.duration = 1.1; // Speed 261 | rotation.repeatCount = HUGE_VALF; // Repeat forever. Can be a finite number. 262 | [self.spinnerImageView.layer addAnimation:rotation forKey:@"Spin"]; 263 | } else { 264 | self.spinnerImageView.hidden = YES; 265 | [self.spinnerImageView.layer removeAllAnimations]; 266 | } 267 | } 268 | 269 | @end 270 | -------------------------------------------------------------------------------- /Interfaces/Base.lproj/Main_iPad.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | --------------------------------------------------------------------------------