├── .gitignore ├── How-to FE-C over BLE v1_0_0.pdf ├── BleTrainerControl ├── Assets.xcassets │ ├── Contents.json │ ├── AppIcon.appiconset │ │ ├── logo29.png │ │ ├── logo40.png │ │ ├── logo58.png │ │ ├── logo76.png │ │ ├── logo80.png │ │ ├── logo87.png │ │ ├── logo120-1.png │ │ ├── logo120.png │ │ ├── logo152.png │ │ ├── logo180.png │ │ ├── logo58-1.png │ │ ├── logo80-1.png │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ ├── ipad_l_@1x.png │ │ ├── ipad_l_@2x.png │ │ ├── ipad_p_@1x.png │ │ ├── ipad_p_@2x.png │ │ ├── iphone_p_4.7.png │ │ ├── iphone_p_5.5.png │ │ ├── iphone_p_@2x.png │ │ ├── iphone_p_retina4.png │ │ └── Contents.json ├── BleTrainerControl.xcdatamodeld │ ├── .xccurrentversion │ └── BleTrainerControl.xcdatamodel │ │ └── contents ├── main.m ├── Utils.h ├── SpinDownCalibrationViewController.h ├── TargetPowerViewController.h ├── BasicResistanceViewController.h ├── AppDelegate.h ├── TrackResistanceViewController.h ├── WindResistanceViewController.h ├── ViewController.h ├── Info.plist ├── ConnectionViewController.h ├── Base.lproj │ └── LaunchScreen.storyboard ├── BTLEManager.h ├── BTLEConstants.h ├── TargetPowerViewController.m ├── BasicResistanceViewController.m ├── WindResistanceViewController.m ├── TrackResistanceViewController.m ├── Utils.m ├── TargetPowerViewController.xib ├── BasicResistanceViewController.xib ├── ViewController.xib ├── BTLETrainerManager.h ├── AppDelegate.m ├── TrackResistanceViewController.xib ├── WindResistanceViewController.xib ├── ViewController.m ├── ConnectionViewController_iPhone.xib ├── ConnectionViewController.xib ├── BTLEManager.m ├── SpinDownCalibrationViewController.m ├── SpinDownCalibrationViewController.xib └── ConnectionViewController.m ├── Firmware └── Firmware update instructions for the Tacx smart series trainers 1_1.pdf ├── BleTrainerControl.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── wminol.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── wminol.xcuserdatad │ └── xcschemes │ ├── xcschememanagement.plist │ └── BleTrainerControl.xcscheme └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /How-to FE-C over BLE v1_0_0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/How-to FE-C over BLE v1_0_0.pdf -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo29.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo40.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo58.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo76.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo80.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo87.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo120-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo120-1.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo120.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo152.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo180.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo58-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo58-1.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo80-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/AppIcon.appiconset/logo80-1.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/ipad_l_@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/ipad_l_@1x.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/ipad_l_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/ipad_l_@2x.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/ipad_p_@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/ipad_p_@1x.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/ipad_p_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/ipad_p_@2x.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/iphone_p_4.7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/iphone_p_4.7.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/iphone_p_5.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/iphone_p_5.5.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/iphone_p_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/iphone_p_@2x.png -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/iphone_p_retina4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/iphone_p_retina4.png -------------------------------------------------------------------------------- /Firmware/Firmware update instructions for the Tacx smart series trainers 1_1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/Firmware/Firmware update instructions for the Tacx smart series trainers 1_1.pdf -------------------------------------------------------------------------------- /BleTrainerControl.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BleTrainerControl.xcodeproj/project.xcworkspace/xcuserdata/wminol.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jedla22/BleTrainerControl/HEAD/BleTrainerControl.xcodeproj/project.xcworkspace/xcuserdata/wminol.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /BleTrainerControl/BleTrainerControl.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | BleTrainerControl.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /BleTrainerControl/BleTrainerControl.xcdatamodeld/BleTrainerControl.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /BleTrainerControl/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 23/09/2015. 6 | // Copyright © 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BleTrainerControl/Utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.h 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 27/08/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface Utils : NSObject 13 | 14 | -(NSString *)getHexaStringFromData:(NSData *)data; 15 | -(NSString *)getBinaryFromHexa:(NSString *)hexa; 16 | -(NSInteger)getDecimalFromBinary:(NSString *)binary; 17 | -(NSInteger)getDecimalFromHexa:(NSString *)hexa; 18 | -(NSString *)getHexaFromBinary:(NSString *)binary; 19 | -(NSString *)XORBetweenBinary:(NSString *)binary1 andBinary:(NSString *)binary2; 20 | 21 | -(UIImage *)imageWithColor:(UIColor *)color; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /BleTrainerControl/SpinDownCalibrationViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SpinDownCalibrationViewController.h 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 04/09/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class AppDelegate; 12 | 13 | @interface SpinDownCalibrationViewController : UIViewController 14 | { 15 | AppDelegate *appDelegate; 16 | 17 | NSTimer *getDataTimer; 18 | 19 | IBOutlet UIButton *spinDownButton; 20 | 21 | IBOutlet UITableView *calibrationTableView; 22 | } 23 | 24 | -(IBAction)onStartSpinDownCalibrationButton:(id)sender; 25 | -(IBAction)onCloseButton:(id)sender; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /BleTrainerControl/TargetPowerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TargetPowerViewController.h 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 03/09/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class AppDelegate; 12 | 13 | @interface TargetPowerViewController : UIViewController 14 | { 15 | AppDelegate *appDelegate; 16 | 17 | IBOutlet UILabel *targetPowerLabel; 18 | IBOutlet UISlider *targetPowerSlider; 19 | IBOutlet UILabel *targetPowerValueLabel; 20 | 21 | IBOutlet UIButton *sendButton; 22 | } 23 | 24 | -(IBAction)onSendButton:(id)sender; 25 | -(IBAction)onCloseButton:(id)sender; 26 | 27 | -(IBAction)onSliderValueChanged:(id)sender; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /BleTrainerControl/BasicResistanceViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BasicResistanceViewController.h 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 03/09/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class AppDelegate; 12 | 13 | @interface BasicResistanceViewController : UIViewController 14 | { 15 | AppDelegate *appDelegate; 16 | 17 | IBOutlet UILabel *totalResistanceLabel; 18 | IBOutlet UISlider *totalResistanceSlider; 19 | IBOutlet UILabel *totalResistanceValueLabel; 20 | 21 | IBOutlet UIButton *sendButton; 22 | } 23 | 24 | -(IBAction)onSendButton:(id)sender; 25 | -(IBAction)onCloseButton:(id)sender; 26 | 27 | -(IBAction)onSliderValueChanged:(id)sender; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /BleTrainerControl.xcodeproj/xcuserdata/wminol.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | BleTrainerControl.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C787007E1BB2D68C007BC635 16 | 17 | primary 18 | 19 | 20 | C787009A1BB2D68C007BC635 21 | 22 | primary 23 | 24 | 25 | C78700A51BB2D68D007BC635 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /BleTrainerControl/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 23/09/2015. 6 | // Copyright © 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "ViewController.h" 13 | 14 | #import "BTLETrainerManager.h" 15 | 16 | @interface AppDelegate : UIResponder 17 | { 18 | BTLETrainerManager *btleTrainerManager; 19 | } 20 | 21 | @property (nonatomic, retain) BTLETrainerManager *btleTrainerManager; 22 | 23 | @property (strong, nonatomic) UIWindow *window; 24 | 25 | @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; 26 | @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; 27 | @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; 28 | 29 | - (void)saveContext; 30 | - (NSURL *)applicationDocumentsDirectory; 31 | 32 | 33 | @end 34 | 35 | -------------------------------------------------------------------------------- /BleTrainerControl/TrackResistanceViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TrackResistanceViewController.h 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 03/09/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class AppDelegate; 12 | 13 | @interface TrackResistanceViewController : UIViewController 14 | { 15 | AppDelegate *appDelegate; 16 | 17 | IBOutlet UILabel *gradeLabel; 18 | IBOutlet UISlider *gradeSlider; 19 | IBOutlet UILabel *gradeValueLabel; 20 | 21 | IBOutlet UILabel *coefficentOfRollingResistanceLabel; 22 | IBOutlet UISlider *coefficentOfRollingResistanceSlider; 23 | IBOutlet UILabel *coefficentOfRollingResistanceValueLabel; 24 | 25 | IBOutlet UIButton *sendButton; 26 | 27 | IBOutlet UISwitch *maxRangeSwitch; 28 | } 29 | 30 | -(IBAction)onSendButton:(id)sender; 31 | -(IBAction)onCloseButton:(id)sender; 32 | 33 | -(IBAction)onSliderValueChanged:(id)sender; 34 | 35 | -(IBAction)onMaxRangeSwitchValueChanged:(id)sender; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /BleTrainerControl/WindResistanceViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // WindResistanceViewController.h 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 03/09/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class AppDelegate; 12 | 13 | @interface WindResistanceViewController : UIViewController 14 | { 15 | AppDelegate *appDelegate; 16 | 17 | IBOutlet UILabel *windResistanceCoefficientLabel; 18 | IBOutlet UISlider *windResistanceCoefficientSlider; 19 | IBOutlet UILabel *windResistanceCoefficientValueLabel; 20 | 21 | IBOutlet UILabel *windSpeedLabel; 22 | IBOutlet UISlider *windSpeedSlider; 23 | IBOutlet UILabel *windSpeedValueLabel; 24 | 25 | IBOutlet UILabel *draftingFactorLabel; 26 | IBOutlet UISlider *draftingFactorSlider; 27 | IBOutlet UILabel *draftingFactorValueLabel; 28 | 29 | IBOutlet UIButton *sendButton; 30 | } 31 | 32 | -(IBAction)onSendButton:(id)sender; 33 | -(IBAction)onCloseButton:(id)sender; 34 | 35 | -(IBAction)onSliderValueChanged:(id)sender; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /BleTrainerControl/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 27/08/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "ConnectionViewController.h" 12 | 13 | typedef enum 14 | { 15 | DiscoveryStateStopped, 16 | DiscoveryStateStarted 17 | } DiscoveryState; 18 | 19 | @class AppDelegate; 20 | 21 | @interface ViewController : UIViewController 22 | { 23 | AppDelegate *appDelegate; 24 | 25 | NSUserDefaults *userDefaults; 26 | 27 | DiscoveryState discoveryState; 28 | 29 | IBOutlet UIButton *discoveryButton; 30 | IBOutlet UITableView *discoveryTableView; 31 | 32 | IBOutlet UISwitch *searchAllSwitch; 33 | 34 | NSTimer *discoveryTimer; 35 | 36 | NSMutableArray *discoveredDeviceArray; 37 | 38 | IBOutlet UILabel *versionLabel; 39 | } 40 | 41 | -(IBAction)onStartDiscovery:(id)sender; 42 | 43 | -(IBAction)onSearchAllValueChanged:(id)sender; 44 | 45 | @end 46 | 47 | -------------------------------------------------------------------------------- /BleTrainerControl/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 4 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchImageFile 26 | Default.png 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /BleTrainerControl/ConnectionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ConnectionViewController.h 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 28/08/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import 12 | 13 | #import "BasicResistanceViewController.h" 14 | #import "TargetPowerViewController.h" 15 | #import "WindResistanceViewController.h" 16 | #import "TrackResistanceViewController.h" 17 | 18 | #import "SpinDownCalibrationViewController.h" 19 | 20 | @class AppDelegate; 21 | 22 | @interface ConnectionViewController : UIViewController 23 | { 24 | AppDelegate *appDelegate; 25 | 26 | NSTimer *getDataTimer; 27 | 28 | CBPeripheral *peripheralSelected; 29 | 30 | IBOutlet UITableView *dataPagesTableView; 31 | 32 | IBOutlet UILabel *statusValueLabel; 33 | 34 | BOOL isDisplayingModalView; 35 | 36 | IBOutlet UIButton *basicResistanceButton; 37 | IBOutlet UIButton *targetPowerButton; 38 | IBOutlet UIButton *windResistanceButton; 39 | IBOutlet UIButton *trackResistanceButton; 40 | 41 | IBOutlet UIButton *calibrationButton; 42 | 43 | IBOutlet UIButton *requestPage54Button; 44 | IBOutlet UIButton *requestPage55Button; 45 | } 46 | 47 | @property (nonatomic, retain) CBPeripheral *peripheralSelected; 48 | 49 | 50 | -(IBAction)onBasicResistance:(id)sender; 51 | -(IBAction)onTargetPower:(id)sender; 52 | -(IBAction)onWindResistance:(id)sender; 53 | -(IBAction)onTrackResistance:(id)sender; 54 | 55 | -(IBAction)onCalibration:(id)sender; 56 | 57 | 58 | -(IBAction)requestPage54:(id)sender; 59 | -(IBAction)requestPage55:(id)sender; 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /BleTrainerControl/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "logo58.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "logo87.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "40x40", 17 | "idiom" : "iphone", 18 | "filename" : "logo80.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "40x40", 23 | "idiom" : "iphone", 24 | "filename" : "logo120.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "60x60", 29 | "idiom" : "iphone", 30 | "filename" : "logo120-1.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "logo180.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "29x29", 41 | "idiom" : "ipad", 42 | "filename" : "logo29.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "29x29", 47 | "idiom" : "ipad", 48 | "filename" : "logo58-1.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "40x40", 53 | "idiom" : "ipad", 54 | "filename" : "logo40.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "40x40", 59 | "idiom" : "ipad", 60 | "filename" : "logo80-1.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "76x76", 65 | "idiom" : "ipad", 66 | "filename" : "logo76.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "76x76", 71 | "idiom" : "ipad", 72 | "filename" : "logo152.png", 73 | "scale" : "2x" 74 | } 75 | ], 76 | "info" : { 77 | "version" : 1, 78 | "author" : "xcode" 79 | } 80 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BleTrainerControl 2 | ## What is BleTrainerControl 3 | This repo is a complete demo project to interact with Tacx trainers over Bluetooth Smart (Neo, Vortex Smart,...). 4 | The idea is to send and receive ANT payloads (FE-C profile) over a BLE tunnel. 5 | 6 | ## Screens 7 | ### Discovery screen 8 | This screen allows you to search for Bluetooth Smart devices around that corresponds to the 2 services: 9 | 6E40FEC1-B5A3-F393-E0A9-E50E24DCCA9E 10 | 669AA305-0C08-969E-E211-86AD5062675F 11 | You can also search all Bluetooth smart devices around by checking the lower right option "Search all devices" 12 | 13 | ### Main screen 14 | FE-C pages displayed: 15 | * Page 16: FE capabilities 16 | * Page 17: General settings 17 | * Page 25: Specific trainer 18 | * Page 54: FE capabilities (you need to request this page with the button on top) 19 | * Page 55: User configuration (you need to request this page with the button on top) 20 | * Page 71: Command status 21 | * Page 80: Manufacturer's identification 22 | * Page 81: Product information 23 | 24 | 4 buttons to set one of the mode with its options: 25 | * Basic resistance, opening a slider from 0 to 100% 26 | * Target power, opening a slider from 0 Watts to 1000 Watts 27 | * Wind resistance, opening 3 sliders to set the coefficient of wind resistance, the wind speed and the drafting factor 28 | * Track resistance, or simulation mode, to set the grade and coefficient of rolling resistance 29 | 30 | 1 button to open calibration 31 | 32 | ### Calibration screen 33 | After initiating the process with the "SPIN-DOWN" button, pedal up to the Trainer expected speed (32km/h on a Vortex as of Oct 2015). The Page 2 (Calibration in progress) will show you what is going on. 34 | Then stop pedalling until the wheel comes to a halt and expect the Page 1 Calibration Success information 35 | 36 | 37 | ## About 38 | Written by Kinomap for Tacx under GNU GPL 2.0 licence. 39 | To learn more about the FE-C ANT+ profile: 40 | https://www.thisisant.com/developer/resources/downloads 41 | -------------------------------------------------------------------------------- /BleTrainerControl/BTLEManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // BTLEManager.h 3 | // KinomapTrainer 4 | // 5 | // Created by William Minol on 28/10/2014. 6 | // Copyright (c) 2014 Excellance. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "BTLEConstants.h" 13 | 14 | @protocol BTLEManagerDelegate; 15 | 16 | @interface BTLEManager : NSObject 17 | { 18 | id btleManagerDelegate; 19 | 20 | CBCentralManager *centralManager; 21 | NSInteger scanningState; 22 | NSMutableArray *discoveredPeripheralArray; 23 | 24 | CBPeripheral *peripheralSelected; 25 | 26 | BOOL needToBeDisconnected; 27 | 28 | //NSMutableArray *characteristicsArrayForPeripheral; 29 | } 30 | 31 | @property (nonatomic, assign) id btleManagerDelegate; 32 | 33 | @property (nonatomic, retain) CBCentralManager *centralManager; 34 | @property (nonatomic) NSInteger scanningState; 35 | @property (nonatomic, retain) NSMutableArray *discoveredPeripheralArray; 36 | @property (nonatomic, retain) CBPeripheral *peripheralSelected; 37 | 38 | -(void)startScanning; 39 | -(void)startScanningWithServicesUUIDArray:(NSArray *)servicesUUIDArray; 40 | -(void)stopScanning; 41 | 42 | -(void)addPeripheralToArray:(CBPeripheral *)peripheral; 43 | 44 | -(void)retrievePeripheralAndConnectWithUUID:(NSString *)peripheralUUID; 45 | -(void)disconnectPeripheral; 46 | 47 | -(void)writeValue:(NSData *)data toCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)writeType; 48 | -(void)writeValue:(NSData *)data toCharacteristicUUIDString:(NSString *)characteristicUUIDString; 49 | 50 | @end 51 | 52 | 53 | @protocol BTLEManagerDelegate 54 | 55 | @optional 56 | -(void)peripheralIsConnected:(BOOL)isConnected; 57 | -(void)characteristicIsDiscovered:(CBCharacteristic *)characteristic; 58 | -(void)dataReceivedFromPeripheral:(CBPeripheral *)peripheral withCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error; 59 | @end -------------------------------------------------------------------------------- /BleTrainerControl/BTLEConstants.h: -------------------------------------------------------------------------------- 1 | // 2 | // BTLEConstants.h 3 | // KinomapTrainer 4 | // 5 | // Created by William Minol on 28/10/2014. 6 | // Copyright (c) 2014 Excellance. All rights reserved. 7 | // 8 | 9 | #ifndef KinomapTrainer_BTLEConstants_h 10 | #define KinomapTrainer_BTLEConstants_h 11 | 12 | //Scanning state 13 | #define BTLE_SCANNING_STATE_STOPPED 0 14 | #define BTLE_SCANNING_STATE_STARTED 1 15 | 16 | 17 | 18 | 19 | //////////////////////////////////////////////// 20 | //////BTLE SERVICE AND CHARACTERISTICS//////// 21 | //////////////////////////////////////////// 22 | 23 | //BTLE services and characteristics 24 | #define SERVICE_CYCLING_POWER @"1818" 25 | #define CHARACTERISTIC_CYCLING_POWER_MEASUREMENT @"2A63" 26 | #define CHARACTERISTIC_CYCLING_POWER_FEATURE @"2A65" 27 | #define CHARACTERISTIC_CYCLING_POWER_CONTROL_POINT @"2A66" 28 | #define CHARACTERISTIC_SENSOR_LOCATION @"2A5D" 29 | 30 | #define SERVICE_DEVICE_INFORMATION @"180A" 31 | #define CHARACTERISTIC_MODEL_NUMBER_STRING @"2A24" 32 | #define CHARACTERISTIC_FIRMWARE_REVISION_STRING @"2A26" 33 | #define CHARACTERISTIC_HARDWARE_REVISTION_STRING @"2A27" 34 | #define CHARACTERISTIC_SOFTWARE_REVISTION_STRING @"2A28" 35 | #define CHARACTERISTIC_MANUFACTURER_NAME_STRING @"2A29" 36 | #define CHARACTERISTIC_PNP_ID @"2A50" 37 | 38 | //////////////////////////////////////////// 39 | //////BTLE SERVICE AND CHARACTERISTICS//////// 40 | //////////////////////////////////////////////// 41 | 42 | 43 | 44 | /////////////////////////////////// 45 | ////////////TAXC FE-C/////////// 46 | ///////////////////////////// 47 | 48 | //Tacx FE-C service and characteristics 49 | #define TACX_FEC_PRIMARY_SERVICE @"6E40FEC1-B5A3-F393-E0A9-E50E24DCCA9E" 50 | //Pour détecter le Vortex 51 | #define TACX_VORTEX_PRIMARY_SERVICE @"669AA305-0C08-969E-E211-86AD5062675F" 52 | 53 | 54 | #define TACX_FEC_READ_CHARACTERISTIC @"6E40FEC2-B5A3-F393-E0A9-E50E24DCCA9E" 55 | #define TACX_FEC_WRITE_CHARACTERISTIC @"6E40FEC3-B5A3-F393-E0A9-E50E24DCCA9E" 56 | 57 | ///////////////////////////// 58 | ////////////TAXC FE-C/////////// 59 | /////////////////////////////////// 60 | 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /BleTrainerControl/TargetPowerViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TargetPowerViewController.m 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 03/09/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import "TargetPowerViewController.h" 10 | 11 | #import "AppDelegate.h" 12 | 13 | @interface TargetPowerViewController () 14 | 15 | @end 16 | 17 | @implementation TargetPowerViewController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | // Do any additional setup after loading the view from its nib. 22 | 23 | appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; 24 | 25 | [targetPowerSlider setMinimumValue:0.0]; 26 | [targetPowerSlider setMaximumValue:1000.0]; 27 | 28 | //Default values 29 | [targetPowerSlider setValue:0.0]; 30 | 31 | //Display values 32 | [self onSliderValueChanged:targetPowerSlider]; 33 | 34 | [self.navigationItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStylePlain target:self action:@selector(onCloseButton:)] autorelease]]; 35 | 36 | Utils *utils = [[[Utils alloc] init] autorelease]; 37 | NSArray *buttonsArray = [NSArray arrayWithObjects:sendButton, nil]; 38 | for(UIButton *btn in buttonsArray) 39 | { 40 | [btn setBackgroundImage:[utils imageWithColor:[UIColor lightGrayColor]] forState:UIControlStateHighlighted]; 41 | [btn setBackgroundImage:[utils imageWithColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]] forState:UIControlStateNormal]; 42 | } 43 | } 44 | 45 | - (void)didReceiveMemoryWarning { 46 | [super didReceiveMemoryWarning]; 47 | // Dispose of any resources that can be recreated. 48 | } 49 | 50 | -(IBAction)onCloseButton:(id)sender 51 | { 52 | [super dismissViewControllerAnimated:TRUE completion:nil]; 53 | } 54 | 55 | -(IBAction)onSendButton:(id)sender 56 | { 57 | //Send target power for target power mode 58 | float targetPowerValue = targetPowerSlider.value; 59 | [appDelegate.btleTrainerManager sendTargetPower:targetPowerValue]; 60 | } 61 | 62 | 63 | -(IBAction)onSliderValueChanged:(id)sender 64 | { 65 | [targetPowerValueLabel setText:[NSString stringWithFormat:@"%0.0f W", targetPowerSlider.value]]; 66 | } 67 | 68 | /* 69 | #pragma mark - Navigation 70 | 71 | // In a storyboard-based application, you will often want to do a little preparation before navigation 72 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 73 | // Get the new view controller using [segue destinationViewController]. 74 | // Pass the selected object to the new view controller. 75 | } 76 | */ 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /BleTrainerControl/BasicResistanceViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BasicResistanceViewController.m 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 03/09/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import "BasicResistanceViewController.h" 10 | 11 | #import "AppDelegate.h" 12 | 13 | @interface BasicResistanceViewController () 14 | 15 | @end 16 | 17 | @implementation BasicResistanceViewController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | // Do any additional setup after loading the view from its nib. 22 | 23 | appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; 24 | 25 | [totalResistanceSlider setMinimumValue:0.0]; 26 | [totalResistanceSlider setMaximumValue:100.0]; 27 | 28 | //Default values 29 | [totalResistanceSlider setValue:0.0]; 30 | 31 | //Display values 32 | [self onSliderValueChanged:totalResistanceSlider]; 33 | 34 | [self.navigationItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStylePlain target:self action:@selector(onCloseButton:)] autorelease]]; 35 | 36 | Utils *utils = [[[Utils alloc] init] autorelease]; 37 | NSArray *buttonsArray = [NSArray arrayWithObjects:sendButton, nil]; 38 | for(UIButton *btn in buttonsArray) 39 | { 40 | [btn setBackgroundImage:[utils imageWithColor:[UIColor lightGrayColor]] forState:UIControlStateHighlighted]; 41 | [btn setBackgroundImage:[utils imageWithColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]] forState:UIControlStateNormal]; 42 | } 43 | } 44 | 45 | 46 | - (void)didReceiveMemoryWarning { 47 | [super didReceiveMemoryWarning]; 48 | // Dispose of any resources that can be recreated. 49 | } 50 | 51 | -(IBAction)onCloseButton:(id)sender 52 | { 53 | [super dismissViewControllerAnimated:TRUE completion:nil]; 54 | } 55 | 56 | -(IBAction)onSendButton:(id)sender 57 | { 58 | //Send total resistance for basic resistance mode 59 | float totalResistanceValue = totalResistanceSlider.value; 60 | [appDelegate.btleTrainerManager sendBasicResistance:totalResistanceValue]; 61 | } 62 | 63 | 64 | -(IBAction)onSliderValueChanged:(id)sender 65 | { 66 | [totalResistanceValueLabel setText:[NSString stringWithFormat:@"%0.0f %%", totalResistanceSlider.value]]; 67 | } 68 | 69 | 70 | /* 71 | #pragma mark - Navigation 72 | 73 | // In a storyboard-based application, you will often want to do a little preparation before navigation 74 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 75 | // Get the new view controller using [segue destinationViewController]. 76 | // Pass the selected object to the new view controller. 77 | } 78 | */ 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /BleTrainerControl/WindResistanceViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // WindResistanceViewController.m 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 03/09/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import "WindResistanceViewController.h" 10 | 11 | #import "AppDelegate.h" 12 | 13 | @interface WindResistanceViewController () 14 | 15 | @end 16 | 17 | @implementation WindResistanceViewController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | // Do any additional setup after loading the view from its nib. 22 | 23 | appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; 24 | 25 | [windResistanceCoefficientSlider setMinimumValue:0.0]; 26 | [windResistanceCoefficientSlider setMaximumValue:1.86]; 27 | 28 | [windSpeedSlider setMinimumValue:-127.0]; 29 | [windSpeedSlider setMaximumValue:127.0]; 30 | 31 | [draftingFactorSlider setMinimumValue:0.0]; 32 | [draftingFactorSlider setMaximumValue:1.0]; 33 | 34 | //Default values 35 | [windResistanceCoefficientSlider setValue:0.6]; 36 | [windSpeedSlider setValue:0.0]; 37 | [draftingFactorSlider setValue:0.0]; 38 | 39 | //Display values 40 | [self onSliderValueChanged:windResistanceCoefficientSlider]; 41 | [self onSliderValueChanged:windSpeedSlider]; 42 | [self onSliderValueChanged:draftingFactorSlider]; 43 | 44 | [self.navigationItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStylePlain target:self action:@selector(onCloseButton:)] autorelease]]; 45 | 46 | Utils *utils = [[[Utils alloc] init] autorelease]; 47 | NSArray *buttonsArray = [NSArray arrayWithObjects:sendButton, nil]; 48 | for(UIButton *btn in buttonsArray) 49 | { 50 | [btn setBackgroundImage:[utils imageWithColor:[UIColor lightGrayColor]] forState:UIControlStateHighlighted]; 51 | [btn setBackgroundImage:[utils imageWithColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]] forState:UIControlStateNormal]; 52 | } 53 | } 54 | 55 | - (void)didReceiveMemoryWarning { 56 | [super didReceiveMemoryWarning]; 57 | // Dispose of any resources that can be recreated. 58 | } 59 | 60 | -(IBAction)onCloseButton:(id)sender 61 | { 62 | [super dismissViewControllerAnimated:TRUE completion:nil]; 63 | } 64 | 65 | -(IBAction)onSendButton:(id)sender 66 | { 67 | //Send for wind resistanc mode 68 | float windResistanceCoefficientValue = windResistanceCoefficientSlider.value; 69 | float windSpeedValue = windSpeedSlider.value; 70 | float draftingFactorValue = draftingFactorSlider.value; 71 | 72 | [appDelegate.btleTrainerManager sendWindResistanceCoefficient:windResistanceCoefficientValue windSpeed:windSpeedValue draftingFactor:draftingFactorValue]; 73 | } 74 | 75 | 76 | -(IBAction)onSliderValueChanged:(id)sender 77 | { 78 | if([sender isEqual:windResistanceCoefficientSlider]) 79 | [windResistanceCoefficientValueLabel setText:[NSString stringWithFormat:@"%0.2f kg/m", windResistanceCoefficientSlider.value]]; 80 | 81 | if([sender isEqual:windSpeedSlider]) 82 | [windSpeedValueLabel setText:[NSString stringWithFormat:@"%0.0f km/h", windSpeedSlider.value]]; 83 | 84 | if([sender isEqual:draftingFactorSlider]) 85 | [draftingFactorValueLabel setText:[NSString stringWithFormat:@"%0.2f", draftingFactorSlider.value]]; 86 | } 87 | 88 | /* 89 | #pragma mark - Navigation 90 | 91 | // In a storyboard-based application, you will often want to do a little preparation before navigation 92 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 93 | // Get the new view controller using [segue destinationViewController]. 94 | // Pass the selected object to the new view controller. 95 | } 96 | */ 97 | 98 | @end 99 | -------------------------------------------------------------------------------- /BleTrainerControl/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "extent" : "full-screen", 5 | "idiom" : "iphone", 6 | "subtype" : "736h", 7 | "filename" : "iphone_p_5.5.png", 8 | "minimum-system-version" : "8.0", 9 | "orientation" : "portrait", 10 | "scale" : "3x" 11 | }, 12 | { 13 | "orientation" : "landscape", 14 | "idiom" : "iphone", 15 | "extent" : "full-screen", 16 | "minimum-system-version" : "8.0", 17 | "subtype" : "736h", 18 | "scale" : "3x" 19 | }, 20 | { 21 | "extent" : "full-screen", 22 | "idiom" : "iphone", 23 | "subtype" : "667h", 24 | "filename" : "iphone_p_4.7.png", 25 | "minimum-system-version" : "8.0", 26 | "orientation" : "portrait", 27 | "scale" : "2x" 28 | }, 29 | { 30 | "orientation" : "portrait", 31 | "idiom" : "iphone", 32 | "filename" : "iphone_p_@2x.png", 33 | "extent" : "full-screen", 34 | "minimum-system-version" : "7.0", 35 | "scale" : "2x" 36 | }, 37 | { 38 | "extent" : "full-screen", 39 | "idiom" : "iphone", 40 | "subtype" : "retina4", 41 | "filename" : "iphone_p_retina4.png", 42 | "minimum-system-version" : "7.0", 43 | "orientation" : "portrait", 44 | "scale" : "2x" 45 | }, 46 | { 47 | "orientation" : "portrait", 48 | "idiom" : "ipad", 49 | "filename" : "ipad_p_@1x.png", 50 | "extent" : "full-screen", 51 | "minimum-system-version" : "7.0", 52 | "scale" : "1x" 53 | }, 54 | { 55 | "orientation" : "landscape", 56 | "idiom" : "ipad", 57 | "filename" : "ipad_l_@1x.png", 58 | "extent" : "full-screen", 59 | "minimum-system-version" : "7.0", 60 | "scale" : "1x" 61 | }, 62 | { 63 | "orientation" : "portrait", 64 | "idiom" : "ipad", 65 | "filename" : "ipad_p_@2x.png", 66 | "extent" : "full-screen", 67 | "minimum-system-version" : "7.0", 68 | "scale" : "2x" 69 | }, 70 | { 71 | "orientation" : "landscape", 72 | "idiom" : "ipad", 73 | "filename" : "ipad_l_@2x.png", 74 | "extent" : "full-screen", 75 | "minimum-system-version" : "7.0", 76 | "scale" : "2x" 77 | }, 78 | { 79 | "orientation" : "portrait", 80 | "idiom" : "iphone", 81 | "extent" : "full-screen", 82 | "scale" : "1x" 83 | }, 84 | { 85 | "orientation" : "portrait", 86 | "idiom" : "iphone", 87 | "extent" : "full-screen", 88 | "scale" : "2x" 89 | }, 90 | { 91 | "orientation" : "portrait", 92 | "idiom" : "iphone", 93 | "extent" : "full-screen", 94 | "subtype" : "retina4", 95 | "scale" : "2x" 96 | }, 97 | { 98 | "orientation" : "portrait", 99 | "idiom" : "ipad", 100 | "extent" : "to-status-bar", 101 | "scale" : "1x" 102 | }, 103 | { 104 | "orientation" : "portrait", 105 | "idiom" : "ipad", 106 | "extent" : "full-screen", 107 | "scale" : "1x" 108 | }, 109 | { 110 | "orientation" : "landscape", 111 | "idiom" : "ipad", 112 | "extent" : "to-status-bar", 113 | "scale" : "1x" 114 | }, 115 | { 116 | "orientation" : "landscape", 117 | "idiom" : "ipad", 118 | "extent" : "full-screen", 119 | "scale" : "1x" 120 | }, 121 | { 122 | "orientation" : "portrait", 123 | "idiom" : "ipad", 124 | "extent" : "to-status-bar", 125 | "scale" : "2x" 126 | }, 127 | { 128 | "orientation" : "portrait", 129 | "idiom" : "ipad", 130 | "extent" : "full-screen", 131 | "scale" : "2x" 132 | }, 133 | { 134 | "orientation" : "landscape", 135 | "idiom" : "ipad", 136 | "extent" : "to-status-bar", 137 | "scale" : "2x" 138 | }, 139 | { 140 | "orientation" : "landscape", 141 | "idiom" : "ipad", 142 | "extent" : "full-screen", 143 | "scale" : "2x" 144 | } 145 | ], 146 | "info" : { 147 | "version" : 1, 148 | "author" : "xcode" 149 | } 150 | } -------------------------------------------------------------------------------- /BleTrainerControl/TrackResistanceViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TrackResistanceViewController.m 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 03/09/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import "TrackResistanceViewController.h" 10 | 11 | #import "AppDelegate.h" 12 | 13 | @interface TrackResistanceViewController () 14 | 15 | @end 16 | 17 | @implementation TrackResistanceViewController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | // Do any additional setup after loading the view from its nib. 22 | 23 | appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; 24 | 25 | //[gradeSlider setMinimumValue:-200.0]; 26 | //[gradeSlider setMaximumValue:200.0]; 27 | 28 | [maxRangeSwitch setOn:[[NSUserDefaults standardUserDefaults] boolForKey:@"maxRangeTrackResistance"]]; 29 | 30 | [self onMaxRangeSwitchValueChanged:maxRangeSwitch]; 31 | 32 | [coefficentOfRollingResistanceSlider setMinimumValue:0.0]; 33 | [coefficentOfRollingResistanceSlider setMaximumValue:0.0127]; 34 | 35 | 36 | //Default values 37 | [gradeSlider setValue:0.0]; 38 | [coefficentOfRollingResistanceSlider setValue:0.0033]; 39 | 40 | //Display values 41 | [self onSliderValueChanged:gradeSlider]; 42 | [self onSliderValueChanged:coefficentOfRollingResistanceSlider]; 43 | 44 | 45 | [self.navigationItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStylePlain target:self action:@selector(onCloseButton:)] autorelease]]; 46 | 47 | Utils *utils = [[[Utils alloc] init] autorelease]; 48 | NSArray *buttonsArray = [NSArray arrayWithObjects:sendButton, nil]; 49 | for(UIButton *btn in buttonsArray) 50 | { 51 | [btn setBackgroundImage:[utils imageWithColor:[UIColor lightGrayColor]] forState:UIControlStateHighlighted]; 52 | [btn setBackgroundImage:[utils imageWithColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]] forState:UIControlStateNormal]; 53 | } 54 | } 55 | 56 | - (void)didReceiveMemoryWarning { 57 | [super didReceiveMemoryWarning]; 58 | // Dispose of any resources that can be recreated. 59 | } 60 | 61 | -(IBAction)onCloseButton:(id)sender 62 | { 63 | [super dismissViewControllerAnimated:TRUE completion:nil]; 64 | } 65 | 66 | -(IBAction)onSendButton:(id)sender 67 | { 68 | //Send grade for track resistance mode 69 | float gradeValue = gradeSlider.value; 70 | float coefficentOfRollingResistanceValue = coefficentOfRollingResistanceSlider.value; 71 | 72 | [appDelegate.btleTrainerManager sendTrackResistanceWithGrade:gradeValue rollingResistanceCoefficient:coefficentOfRollingResistanceValue]; 73 | } 74 | 75 | 76 | -(IBAction)onSliderValueChanged:(id)sender 77 | { 78 | if([sender isEqual:gradeSlider]) 79 | [gradeValueLabel setText:[NSString stringWithFormat:@"%0.2f %%", gradeSlider.value]]; 80 | 81 | if([sender isEqual:coefficentOfRollingResistanceSlider]) 82 | [coefficentOfRollingResistanceValueLabel setText:[NSString stringWithFormat:@"%0.4f", coefficentOfRollingResistanceSlider.value]]; 83 | } 84 | 85 | 86 | -(IBAction)onMaxRangeSwitchValueChanged:(id)sender 87 | { 88 | if(maxRangeSwitch.on == TRUE) 89 | { 90 | [gradeSlider setMinimumValue:-200.0]; 91 | [gradeSlider setMaximumValue:200.0]; 92 | } 93 | else 94 | { 95 | [gradeSlider setMinimumValue:-20.0]; 96 | [gradeSlider setMaximumValue:20.0]; 97 | } 98 | 99 | [[NSUserDefaults standardUserDefaults] setBool:maxRangeSwitch.on forKey:@"maxRangeTrackResistance"]; 100 | [[NSUserDefaults standardUserDefaults] synchronize]; 101 | } 102 | 103 | 104 | /* 105 | #pragma mark - Navigation 106 | 107 | // In a storyboard-based application, you will often want to do a little preparation before navigation 108 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 109 | // Get the new view controller using [segue destinationViewController]. 110 | // Pass the selected object to the new view controller. 111 | } 112 | */ 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /BleTrainerControl.xcodeproj/xcuserdata/wminol.xcuserdatad/xcschemes/BleTrainerControl.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /BleTrainerControl/Utils.m: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.m 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 27/08/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import "Utils.h" 10 | 11 | @implementation Utils 12 | 13 | -(NSString *)getHexaStringFromData:(NSData *)data 14 | { 15 | const unsigned char *dataBuffer = (const unsigned char *)[data bytes]; 16 | if (!dataBuffer) 17 | { 18 | return @""; 19 | } 20 | else 21 | { 22 | NSUInteger dataLength = [data length]; 23 | NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)]; 24 | 25 | for (int i = 0; i < dataLength; ++i) 26 | [hexString appendString:[NSString stringWithFormat:@"%02lx", (unsigned long)dataBuffer[i]]]; 27 | 28 | return [NSString stringWithString:hexString]; 29 | } 30 | } 31 | 32 | 33 | 34 | -(NSString *)getBinaryFromHexa:(NSString *)hexa 35 | { 36 | NSMutableString *binary = [[NSMutableString alloc] initWithString:@""]; 37 | 38 | NSMutableArray *arrayNumbersToTest = [[NSMutableArray alloc] init]; 39 | 40 | NSInteger number = 1; 41 | //4 bits by characters 42 | for (NSInteger i = 0; i < [hexa length] * 4; i++) 43 | { 44 | if(i==0) 45 | number = 1; 46 | else 47 | number *= 2; 48 | 49 | [arrayNumbersToTest addObject:[NSString stringWithFormat:@"%li", (long)number]]; 50 | } 51 | 52 | arrayNumbersToTest = [[[arrayNumbersToTest reverseObjectEnumerator] allObjects] mutableCopy]; 53 | 54 | unsigned int decimal = 0; 55 | NSScanner *scanner = [NSScanner scannerWithString:hexa]; 56 | [scanner scanHexInt:&decimal]; 57 | 58 | for (NSString *nbString in arrayNumbersToTest) 59 | { 60 | NSInteger numberToTest = [nbString integerValue]; 61 | 62 | if(decimal >= numberToTest) 63 | { 64 | decimal -= numberToTest; 65 | [binary appendString:@"1"]; 66 | } 67 | else 68 | [binary appendString:@"0"]; 69 | } 70 | 71 | return binary; 72 | } 73 | 74 | -(NSInteger)getDecimalFromBinary:(NSString *)binary 75 | { 76 | NSString *hexa = [self getHexaFromBinary:binary]; 77 | 78 | NSInteger decimal = 0; 79 | unsigned int decimalInt = 0; 80 | NSScanner *scanner = [NSScanner scannerWithString:hexa]; 81 | [scanner scanHexInt:&decimalInt]; 82 | decimal = decimalInt; 83 | 84 | return decimal; 85 | } 86 | 87 | -(NSInteger)getDecimalFromHexa:(NSString *)hexa 88 | { 89 | NSInteger decimal = 0; 90 | unsigned int decimalInt = 0; 91 | NSScanner *scanner = [NSScanner scannerWithString:hexa]; 92 | [scanner scanHexInt:&decimalInt]; 93 | decimal = decimalInt; 94 | 95 | return decimal; 96 | } 97 | 98 | -(NSString *)getHexaFromBinary:(NSString *)binary 99 | { 100 | NSMutableString *hexa = [[NSMutableString alloc] initWithString:@""]; 101 | 102 | NSMutableArray *arrayNumbersToTest = [[NSMutableArray alloc] init]; 103 | 104 | NSInteger number = 1; 105 | for (NSInteger i=0; i<[binary length]; i++) 106 | { 107 | if(i==0) 108 | number = 1; 109 | else 110 | number *= 2; 111 | 112 | [arrayNumbersToTest addObject:[NSString stringWithFormat:@"%li", (long)number]]; 113 | } 114 | 115 | arrayNumbersToTest = [[[arrayNumbersToTest reverseObjectEnumerator] allObjects] mutableCopy]; 116 | 117 | NSInteger decimal = 0; 118 | for (NSInteger i = 0; i < [binary length]; i++) 119 | { 120 | if ([[binary substringWithRange:NSMakeRange(i, 1)] isEqualToString:@"1"]) 121 | decimal += [[arrayNumbersToTest objectAtIndex:i] integerValue]; 122 | } 123 | 124 | hexa = [[NSString stringWithFormat:@"%02lX", (long)decimal] mutableCopy]; 125 | 126 | return hexa; 127 | } 128 | 129 | -(NSString *)XORBetweenBinary:(NSString *)binary1 andBinary:(NSString *)binary2 130 | { 131 | NSMutableString *result = [[NSMutableString alloc] initWithString:@""]; 132 | 133 | for (NSInteger i = 0; i < [binary1 length]; i++) 134 | { 135 | if ([[binary1 substringWithRange:NSMakeRange(i, 1)] isEqual:[binary2 substringWithRange:NSMakeRange(i, 1)]] == TRUE) 136 | [result appendString:@"0"]; 137 | else 138 | [result appendString:@"1"]; 139 | } 140 | 141 | return result; 142 | } 143 | 144 | 145 | -(UIImage *)imageWithColor:(UIColor *)color 146 | { 147 | CGRect rect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); 148 | UIGraphicsBeginImageContext(rect.size); 149 | CGContextRef context = UIGraphicsGetCurrentContext(); 150 | 151 | CGContextSetFillColorWithColor(context, [color CGColor]); 152 | CGContextFillRect(context, rect); 153 | 154 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 155 | UIGraphicsEndImageContext(); 156 | 157 | return image; 158 | } 159 | 160 | 161 | @end 162 | -------------------------------------------------------------------------------- /BleTrainerControl/TargetPowerViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 49 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /BleTrainerControl/BasicResistanceViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 49 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /BleTrainerControl/ViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 55 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /BleTrainerControl/BTLETrainerManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // BTLETrainerManager.h 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 27/08/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "BTLEManager.h" 12 | 13 | #import "Utils.h" 14 | 15 | //Calibration status 16 | #define CALIBRATION_STATUS_NOT_REQUESTED 0 17 | #define CALIBRATION_STATUS_PENDING 1 18 | 19 | //Calibration conditions 20 | #define TEMPERATURE_CONDITION_NOT_APPLICABLE 0 21 | #define TEMPERATURE_CONDITION_CURRENT_TEMPERATURE_TOO_LOW 1 22 | #define TEMPERATURE_CONDITION_TEMPERATURE_OK 10 23 | #define TEMPERATURE_CONDITION_CURRENT_TEMPERATURE_TOO_HIGH 11 24 | 25 | #define SPEED_CONDITION_NOT_APPLICABLE 0 26 | #define SPEED_CONDITION_CURRENT_SPEED_TOO_LOW 1 27 | #define SPEED_CONDITION_SPEED_OK 10 28 | #define SPEED_CONDITION_RESERVED 11 29 | 30 | //Calibration response 31 | #define CALIBRATION_RESPONSE_FAILURE_NOT_ATTEMPTED 0 32 | #define CALIBRATION_RESPONSE_SUCCESS 1 33 | 34 | @interface BTLETrainerManager : NSObject 35 | { 36 | BTLEManager *manager; 37 | 38 | Utils *utils; 39 | 40 | BOOL calibrationStarted; 41 | 42 | //Page 1 43 | NSInteger zeroOffsetCalibrationResponse; 44 | NSInteger spinDownCalibrationResponse; 45 | float temperatureResponseDegC; 46 | NSInteger zeroOffsetResponse; 47 | float spinDownTimeResponseSeconds; 48 | 49 | //Page 2 50 | NSInteger zeroOffsetCalibrationStatus; 51 | NSInteger spinDownCalibrationStatus; 52 | NSInteger temperatureCondition; 53 | NSInteger speedCondition; 54 | float currentTemperatureDegC; 55 | float targetSpeedKmH; 56 | float targetSpinDownTimeSeconds; 57 | 58 | //Page 16 59 | float elapsedTimeSeconds; 60 | NSInteger distanceTraveledMeters; 61 | float speedKmH; 62 | NSInteger heartRateBPM; 63 | NSInteger equipmentType; 64 | NSString *equipmentTypeString; 65 | BOOL virtualSpeed; 66 | 67 | //Page 17 68 | float cycleLengthM; 69 | float inclinePercent; 70 | float resistanceLevelPercent; 71 | 72 | //Page 25 73 | NSInteger updateEventCount; 74 | NSInteger cadenceRPM; 75 | NSInteger accumulatedPowerW; 76 | NSInteger powerW; 77 | 78 | //Page 48 79 | float totalResistancePercent; 80 | 81 | //Page 49 82 | float targetPowerW; 83 | 84 | //Page 50 85 | float windResistanceCoefficientKgM; 86 | float windSpeedKmH; 87 | float draftingFactor; 88 | 89 | //Page 51 90 | float gradePercent; 91 | float rollingResistanceCoefficient; 92 | 93 | //Page 54 94 | NSInteger maximumResistanceN; 95 | NSString *supportedMode; 96 | 97 | //Page 55 98 | float userWeightKg; 99 | NSInteger bicycleWheelDiameterOffsetMm; 100 | float bicycleWeightKg; 101 | float bicycleWheelDiameterM; 102 | float gearRatio; 103 | 104 | //Page 71 105 | NSInteger lastReceivedCommandID; 106 | NSInteger sequence; 107 | NSInteger commandStatus; 108 | NSString *commandStatusString; 109 | NSString *dataString; 110 | 111 | //Page 80 112 | NSInteger hwRevision; 113 | NSInteger manufacturerID; 114 | NSInteger modelNumber; 115 | 116 | //Page 81 117 | NSInteger swRevisionSupplemental; 118 | NSInteger swRevisionMain; 119 | NSInteger serialNumber; 120 | } 121 | 122 | @property BOOL calibrationStarted; 123 | 124 | //Page 1 125 | @property NSInteger zeroOffsetCalibrationResponse; 126 | @property NSInteger spinDownCalibrationResponse; 127 | @property float temperatureResponseDegC; 128 | @property NSInteger zeroOffsetResponse; 129 | @property float spinDownTimeResponseSeconds; 130 | 131 | //Page 2 132 | @property NSInteger zeroOffsetCalibrationStatus; 133 | @property NSInteger spinDownCalibrationStatus; 134 | @property NSInteger temperatureCondition; 135 | @property NSInteger speedCondition; 136 | @property float currentTemperatureDegC; 137 | @property float targetSpeedKmH; 138 | @property float targetSpinDownTimeSeconds; 139 | 140 | //Page 16 141 | @property float elapsedTimeSeconds; 142 | @property NSInteger distanceTraveledMeters; 143 | @property float speedKmH; 144 | @property NSInteger heartRateBPM; 145 | @property NSInteger equipmentType; 146 | @property (nonatomic, retain) NSString *equipmentTypeString; 147 | @property BOOL virtualSpeed; 148 | 149 | //Page 17 150 | @property float cycleLengthM; 151 | @property float inclinePercent; 152 | @property float resistanceLevelPercent; 153 | 154 | //Page 25 155 | @property NSInteger updateEventCount; 156 | @property NSInteger cadenceRPM; 157 | @property NSInteger accumulatedPowerW; 158 | @property NSInteger powerW; 159 | 160 | //Page 48 161 | @property float totalResistancePercent; 162 | 163 | //Page 49 164 | @property float targetPowerW; 165 | 166 | //Page 50 167 | @property float windResistanceCoefficientKgM; 168 | @property float windSpeedKmH; 169 | @property float draftingFactor; 170 | 171 | //Page 51 172 | @property float gradePercent; 173 | @property float rollingResistanceCoefficient; 174 | 175 | //Page 54 176 | @property NSInteger maximumResistanceN; 177 | @property (nonatomic, retain) NSString *supportedMode; 178 | 179 | //Page 55 180 | @property float userWeightKg; 181 | @property NSInteger bicycleWheelDiameterOffsetMm; 182 | @property float bicycleWeightKg; 183 | @property float bicycleWheelDiameterM; 184 | @property float gearRatio; 185 | 186 | //Page 71 187 | @property NSInteger lastReceivedCommandID; 188 | @property NSInteger sequence; 189 | @property NSInteger commandStatus; 190 | @property (nonatomic, retain) NSString *commandStatusString; 191 | @property (nonatomic, retain) NSString *dataString; 192 | 193 | //Page 80 194 | @property NSInteger hwRevision; 195 | @property NSInteger manufacturerID; 196 | @property NSInteger modelNumber; 197 | 198 | //Page 81 199 | @property NSInteger swRevisionSupplemental; 200 | @property NSInteger swRevisionMain; 201 | @property NSInteger serialNumber; 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | -(void)startScanningAll; 210 | -(void)startScanning; 211 | -(void)stopScanning; 212 | 213 | -(NSMutableArray *)getDiscoveredPeripheralArray; 214 | -(NSInteger)getScanningState; 215 | -(BOOL)isConnected; 216 | 217 | -(void)connectWithUUID:(NSString *)UUIDToConnect; 218 | -(void)disconnect; 219 | 220 | 221 | 222 | //Control data pages 223 | -(void)sendBasicResistance:(float)totalResistancePercentValue; 224 | -(void)sendTargetPower:(float)targetPowerWValue; 225 | -(void)sendWindResistanceCoefficient:(float)windResistanceCoefficientKgMValue windSpeed:(float)windSpeedKmHValue draftingFactor:(float)draftingFactorValue; 226 | -(void)sendTrackResistanceWithGrade:(float)gradePercentValue rollingResistanceCoefficient:(float)rollingResistanceCoefficienValuet; 227 | 228 | //Request page 229 | -(void)sendRequestPage:(NSInteger)page; 230 | 231 | //Calibration 232 | -(void)sendCalibrationRequestForSpinDown:(BOOL)forSpinDown forZeroOffset:(BOOL)forZeroOffset; 233 | 234 | @end 235 | -------------------------------------------------------------------------------- /BleTrainerControl/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 23/09/2015. 6 | // Copyright © 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | @synthesize btleTrainerManager; 18 | 19 | 20 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 21 | // Override point for customization after application launch. 22 | 23 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 24 | 25 | ViewController *viewController = [[[ViewController alloc] initWithNibName:@"ViewController" bundle:nil] autorelease]; 26 | 27 | UINavigationController *navController = [[[UINavigationController alloc] initWithRootViewController:viewController] autorelease]; 28 | [navController.navigationBar setTranslucent:FALSE]; 29 | if([navController.navigationBar respondsToSelector:@selector(setBarTintColor:)]) 30 | { 31 | [navController.navigationBar setBarTintColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 32 | [navController.navigationBar setTintColor:[UIColor whiteColor]]; 33 | } 34 | else 35 | { 36 | [[UINavigationBar appearance] setBackgroundImage:[[UIImage alloc] init] forBarMetrics:UIBarMetricsDefault]; 37 | [[UINavigationBar appearance] setBackgroundColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 38 | } 39 | [navController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName : [UIColor whiteColor]}]; 40 | 41 | self.window.rootViewController = navController; 42 | [self.window makeKeyAndVisible]; 43 | 44 | 45 | //BTLE Trainer Manager 46 | btleTrainerManager = [[BTLETrainerManager alloc] init]; 47 | 48 | 49 | 50 | return YES; 51 | } 52 | 53 | - (void)applicationWillResignActive:(UIApplication *)application { 54 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 55 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 56 | } 57 | 58 | - (void)applicationDidEnterBackground:(UIApplication *)application { 59 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 60 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 61 | } 62 | 63 | - (void)applicationWillEnterForeground:(UIApplication *)application { 64 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 65 | } 66 | 67 | - (void)applicationDidBecomeActive:(UIApplication *)application { 68 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 69 | } 70 | 71 | - (void)applicationWillTerminate:(UIApplication *)application { 72 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 73 | // Saves changes in the application's managed object context before the application terminates. 74 | [self saveContext]; 75 | } 76 | 77 | #pragma mark - Core Data stack 78 | 79 | @synthesize managedObjectContext = _managedObjectContext; 80 | @synthesize managedObjectModel = _managedObjectModel; 81 | @synthesize persistentStoreCoordinator = _persistentStoreCoordinator; 82 | 83 | - (NSURL *)applicationDocumentsDirectory { 84 | // The directory the application uses to store the Core Data store file. This code uses a directory named "com.kinomap.BleTrainerControl" in the application's documents directory. 85 | return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 86 | } 87 | 88 | - (NSManagedObjectModel *)managedObjectModel { 89 | // The managed object model for the application. It is a fatal error for the application not to be able to find and load its model. 90 | if (_managedObjectModel != nil) { 91 | return _managedObjectModel; 92 | } 93 | NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"BleTrainerControl" withExtension:@"momd"]; 94 | _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 95 | return _managedObjectModel; 96 | } 97 | 98 | - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { 99 | // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. 100 | if (_persistentStoreCoordinator != nil) { 101 | return _persistentStoreCoordinator; 102 | } 103 | 104 | // Create the coordinator and store 105 | 106 | _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; 107 | NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"BleTrainerControl.sqlite"]; 108 | NSError *error = nil; 109 | NSString *failureReason = @"There was an error creating or loading the application's saved data."; 110 | if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) { 111 | // Report any error we got. 112 | NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 113 | dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data"; 114 | dict[NSLocalizedFailureReasonErrorKey] = failureReason; 115 | dict[NSUnderlyingErrorKey] = error; 116 | error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict]; 117 | // Replace this with code to handle the error appropriately. 118 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 119 | NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 120 | abort(); 121 | } 122 | 123 | return _persistentStoreCoordinator; 124 | } 125 | 126 | 127 | - (NSManagedObjectContext *)managedObjectContext { 128 | // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) 129 | if (_managedObjectContext != nil) { 130 | return _managedObjectContext; 131 | } 132 | 133 | NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 134 | if (!coordinator) { 135 | return nil; 136 | } 137 | _managedObjectContext = [[NSManagedObjectContext alloc] init]; 138 | [_managedObjectContext setPersistentStoreCoordinator:coordinator]; 139 | return _managedObjectContext; 140 | } 141 | 142 | #pragma mark - Core Data Saving support 143 | 144 | - (void)saveContext { 145 | NSManagedObjectContext *managedObjectContext = self.managedObjectContext; 146 | if (managedObjectContext != nil) { 147 | NSError *error = nil; 148 | if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) { 149 | // Replace this implementation with code to handle the error appropriately. 150 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 151 | NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 152 | abort(); 153 | } 154 | } 155 | } 156 | 157 | @end 158 | -------------------------------------------------------------------------------- /BleTrainerControl/TrackResistanceViewController.xib: -------------------------------------------------------------------------------- 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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 53 | 60 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /BleTrainerControl/WindResistanceViewController.xib: -------------------------------------------------------------------------------- 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 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 55 | 62 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 83 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /BleTrainerControl/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 27/08/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | #import "AppDelegate.h" 12 | 13 | @interface ViewController () 14 | 15 | @end 16 | 17 | @implementation ViewController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | // Do any additional setup after loading the view, typically from a nib. 22 | 23 | self.title = @"Home"; 24 | 25 | //App delegate 26 | appDelegate = [[UIApplication sharedApplication] delegate]; 27 | 28 | userDefaults = [NSUserDefaults standardUserDefaults]; 29 | 30 | //Table view delegates 31 | [discoveryTableView setDelegate:self]; 32 | [discoveryTableView setDataSource:self]; 33 | 34 | //Cells height 35 | [discoveryTableView setRowHeight:75.0]; 36 | 37 | //Update discovery button with state stopped by default 38 | [self updateDiscoveryStateAndButtonWithState:DiscoveryStateStopped]; 39 | 40 | //Fonts 41 | if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) 42 | [discoveryButton.titleLabel setFont:[UIFont boldSystemFontOfSize:50.0]]; 43 | else 44 | [discoveryButton.titleLabel setFont:[UIFont boldSystemFontOfSize:30.0]]; 45 | 46 | //Version 47 | [versionLabel setText:[NSString stringWithFormat:@"v%@(%@)", [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"], [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]]]; 48 | } 49 | 50 | -(void)viewWillAppear:(BOOL)animated 51 | { 52 | searchAllSwitch.on = [userDefaults boolForKey:@"keySearchAll"]; 53 | } 54 | 55 | -(void)viewWillDisappear:(BOOL)animated 56 | { 57 | //Stop discovery 58 | [self stopDiscovery]; 59 | } 60 | 61 | -(void)updateDiscoveryStateAndButtonWithState:(DiscoveryState)newDiscoveryState 62 | { 63 | discoveryState = newDiscoveryState; 64 | 65 | [UIView animateWithDuration:1.0 animations:^{ 66 | 67 | if(discoveryState == DiscoveryStateStopped) 68 | { 69 | [discoveryButton setTitle:@"START DISCOVERY" forState:UIControlStateNormal]; 70 | [discoveryButton setBackgroundColor:[UIColor whiteColor]]; 71 | [discoveryButton setTitleColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0] forState:UIControlStateNormal]; 72 | } 73 | else 74 | { 75 | [discoveryButton setTitle:@"STOP DISCOVERY" forState:UIControlStateNormal]; 76 | [discoveryButton setBackgroundColor:[UIColor redColor]]; 77 | [discoveryButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 78 | } 79 | 80 | } completion:^(BOOL finished) { 81 | 82 | 83 | 84 | }]; 85 | 86 | 87 | } 88 | 89 | 90 | -(IBAction)onStartDiscovery:(id)sender 91 | { 92 | if(discoveryState == DiscoveryStateStopped) 93 | { 94 | //Start discovery 95 | [self startDiscovery]; 96 | } 97 | else 98 | { 99 | //Stop discovery 100 | [self stopDiscovery]; 101 | } 102 | } 103 | 104 | 105 | -(void)startDiscovery 106 | { 107 | //Remove all object from array 108 | if(discoveredDeviceArray != nil) 109 | [discoveredDeviceArray removeAllObjects]; 110 | 111 | //Reload to clear old results 112 | [discoveryTableView reloadData]; 113 | 114 | //Discovery state started 115 | [self updateDiscoveryStateAndButtonWithState:DiscoveryStateStarted]; 116 | 117 | if([userDefaults boolForKey:@"keySearchAll"] == FALSE) 118 | { 119 | //Start scanning 120 | [appDelegate.btleTrainerManager startScanning]; 121 | } 122 | else 123 | { 124 | //Start scanning for all devices 125 | [appDelegate.btleTrainerManager startScanningAll]; 126 | } 127 | 128 | //Start discovery timer 129 | [self startDiscoveryTimer]; 130 | } 131 | 132 | -(void)stopDiscovery 133 | { 134 | //Discovery state started 135 | [self updateDiscoveryStateAndButtonWithState:DiscoveryStateStopped]; 136 | 137 | //Stop scanning 138 | [appDelegate.btleTrainerManager stopScanning]; 139 | 140 | //Start discovery timer 141 | [self stopDiscoveryTimer]; 142 | } 143 | 144 | 145 | -(void)startDiscoveryTimer 146 | { 147 | if(discoveryTimer == nil) 148 | { 149 | //Start discovery timer 150 | discoveryTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(checkDiscoveredDevices) userInfo:nil repeats:TRUE]; 151 | } 152 | } 153 | 154 | -(void)stopDiscoveryTimer 155 | { 156 | if(discoveryTimer != nil) 157 | { 158 | //Stop discovery timer 159 | if([discoveryTimer isValid] == TRUE) 160 | [discoveryTimer invalidate]; 161 | 162 | discoveryTimer = nil; 163 | } 164 | } 165 | 166 | 167 | -(void)checkDiscoveredDevices 168 | { 169 | //Get peripherals discovered array 170 | discoveredDeviceArray = [appDelegate.btleTrainerManager getDiscoveredPeripheralArray]; 171 | 172 | //Reload table view 173 | [discoveryTableView reloadData]; 174 | } 175 | 176 | 177 | 178 | 179 | //TABLE VIEW DELEGATE 180 | 181 | -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 182 | { 183 | return 1; 184 | } 185 | 186 | -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 187 | { 188 | if(discoveredDeviceArray != nil) 189 | return [discoveredDeviceArray count]; 190 | 191 | return 0; 192 | } 193 | 194 | -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 195 | { 196 | return 75.0; 197 | } 198 | 199 | -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 200 | { 201 | return @"Discovered devices"; 202 | } 203 | 204 | -(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section 205 | { 206 | // Background color 207 | [view setTintColor:[UIColor darkGrayColor]]; 208 | 209 | // Text Color 210 | UITableViewHeaderFooterView *header = (UITableViewHeaderFooterView *)view; 211 | [header.textLabel setTextColor:[UIColor whiteColor]]; 212 | } 213 | 214 | //For iOS 6 215 | -(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath 216 | { 217 | //Background color 218 | if(indexPath.row % 2 == 0) 219 | { 220 | [cell setBackgroundColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:0.5]]; 221 | [cell.textLabel setTextColor:[UIColor whiteColor]]; 222 | [cell.detailTextLabel setTextColor:[UIColor whiteColor]]; 223 | } 224 | else 225 | { 226 | [cell setBackgroundColor:[UIColor whiteColor]]; 227 | [cell.textLabel setTextColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 228 | [cell.detailTextLabel setTextColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 229 | } 230 | } 231 | 232 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 233 | { 234 | static NSString *cellIdentifier = @"Cell"; 235 | 236 | //Cell 237 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 238 | if(cell == nil) 239 | { 240 | //Init cell 241 | cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier] autorelease]; 242 | } 243 | 244 | //Background color 245 | if(indexPath.row % 2 == 0) 246 | { 247 | [cell setBackgroundColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:0.5]]; 248 | [cell.textLabel setTextColor:[UIColor whiteColor]]; 249 | [cell.detailTextLabel setTextColor:[UIColor whiteColor]]; 250 | } 251 | else 252 | { 253 | [cell setBackgroundColor:[UIColor whiteColor]]; 254 | [cell.textLabel setTextColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 255 | [cell.detailTextLabel setTextColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 256 | } 257 | 258 | //Disclosure indicator 259 | [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator]; 260 | 261 | //Background color label 262 | [cell.textLabel setBackgroundColor:[UIColor clearColor]]; 263 | [cell.detailTextLabel setBackgroundColor:[UIColor clearColor]]; 264 | 265 | //Fonts 266 | if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) 267 | { 268 | [cell.textLabel setFont:[UIFont boldSystemFontOfSize:30.0]]; 269 | [cell.detailTextLabel setFont:[UIFont systemFontOfSize:20.0]]; 270 | } 271 | else 272 | { 273 | [cell.textLabel setFont:[UIFont boldSystemFontOfSize:20.0]]; 274 | [cell.detailTextLabel setFont:[UIFont systemFontOfSize:12.0]]; 275 | } 276 | 277 | //Peripheral discovered 278 | CBPeripheral *peripheral = [discoveredDeviceArray objectAtIndex:indexPath.row]; 279 | 280 | //Name 281 | [cell.textLabel setText:peripheral.name]; 282 | 283 | //UUID 284 | if([peripheral respondsToSelector:@selector(identifier)]) 285 | [cell.detailTextLabel setText:[peripheral.identifier UUIDString]]; 286 | 287 | return cell; 288 | } 289 | 290 | -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 291 | { 292 | //Peripheral discovered 293 | CBPeripheral *peripheral = [discoveredDeviceArray objectAtIndex:indexPath.row]; 294 | 295 | 296 | //ConnectionViewController *connectionViewController = [[[ConnectionViewController alloc] initWithPeripheral:peripheral] autorelease]; 297 | 298 | ConnectionViewController *connectionViewController = nil; 299 | if([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) 300 | connectionViewController = [[[ConnectionViewController alloc] initWithNibName:@"ConnectionViewController" bundle:nil] autorelease]; 301 | else 302 | connectionViewController = [[[ConnectionViewController alloc] initWithNibName:@"ConnectionViewController_iPhone" bundle:nil] autorelease]; 303 | 304 | [connectionViewController setPeripheralSelected:peripheral]; 305 | 306 | if(connectionViewController != nil) 307 | [self.navigationController pushViewController:connectionViewController animated:TRUE]; 308 | } 309 | 310 | 311 | 312 | 313 | 314 | -(IBAction)onSearchAllValueChanged:(id)sender 315 | { 316 | [userDefaults setBool:searchAllSwitch.on forKey:@"keySearchAll"]; 317 | [userDefaults synchronize]; 318 | } 319 | 320 | 321 | 322 | - (void)didReceiveMemoryWarning { 323 | [super didReceiveMemoryWarning]; 324 | // Dispose of any resources that can be recreated. 325 | } 326 | 327 | @end 328 | -------------------------------------------------------------------------------- /BleTrainerControl/ConnectionViewController_iPhone.xib: -------------------------------------------------------------------------------- 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 | 34 | 46 | 58 | 70 | 82 | 94 | 101 | 108 | 109 | 110 | 111 | 112 | 113 | 125 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /BleTrainerControl/ConnectionViewController.xib: -------------------------------------------------------------------------------- 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 | 39 | 51 | 63 | 75 | 87 | 99 | 106 | 113 | 125 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /BleTrainerControl/BTLEManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // BTLEManager.m 3 | // KinomapTrainer 4 | // 5 | // Created by William Minol on 28/10/2014. 6 | // Copyright (c) 2014 Excellance. All rights reserved. 7 | // 8 | 9 | #import "BTLEManager.h" 10 | 11 | @implementation BTLEManager 12 | 13 | @synthesize btleManagerDelegate; 14 | @synthesize centralManager, scanningState, discoveredPeripheralArray, peripheralSelected; 15 | 16 | -(id)init 17 | { 18 | if(self = [super init]) 19 | { 20 | //Scanning state 21 | scanningState = BTLE_SCANNING_STATE_STOPPED; 22 | 23 | //Array of discovered peripheral 24 | discoveredPeripheralArray = [[NSMutableArray alloc] init]; 25 | 26 | //Init central manager 27 | if([centralManager respondsToSelector:@selector(initWithDelegate:queue:options:)]) 28 | centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil]; 29 | else 30 | centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil]; 31 | } 32 | 33 | return self; 34 | } 35 | 36 | 37 | -(void)startScanning 38 | { 39 | [self startScanningWithServicesUUIDArray:nil]; 40 | } 41 | 42 | -(void)startScanningWithServicesUUIDArray:(NSArray *)servicesUUIDArray 43 | { 44 | if(scanningState == BTLE_SCANNING_STATE_STOPPED) 45 | { 46 | scanningState = BTLE_SCANNING_STATE_STARTED; 47 | 48 | //Options to not allow duplicate peripheral 49 | NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:FALSE] forKey:CBCentralManagerScanOptionAllowDuplicatesKey]; 50 | 51 | //Start scanning 52 | [centralManager scanForPeripheralsWithServices:servicesUUIDArray options:options]; 53 | } 54 | } 55 | 56 | -(void)stopScanning 57 | { 58 | if(scanningState == BTLE_SCANNING_STATE_STARTED) 59 | { 60 | scanningState = BTLE_SCANNING_STATE_STOPPED; 61 | 62 | //Stop scanning 63 | [centralManager stopScan]; 64 | } 65 | } 66 | 67 | 68 | -(void)retrievePeripheralAndConnectWithUUID:(NSString *)peripheralUUID 69 | { 70 | if(peripheralUUID != nil) 71 | { 72 | if([centralManager respondsToSelector:@selector(retrievePeripheralsWithIdentifiers:)]) 73 | { 74 | NSArray *peripheralsRetrievedArray = [centralManager retrievePeripheralsWithIdentifiers:[NSArray arrayWithObject:[[[NSUUID alloc] initWithUUIDString:peripheralUUID] autorelease]]]; 75 | 76 | if([peripheralsRetrievedArray count] != 0) 77 | { 78 | CBPeripheral *peripheral = [peripheralsRetrievedArray objectAtIndex:0]; 79 | 80 | NSLog(@"Peripheral retrieved: %@ - UUID : %@", peripheral.name, [peripheral.identifier UUIDString]); 81 | 82 | if(peripheral != nil) 83 | { 84 | peripheralSelected = [peripheral copy]; 85 | 86 | //Connect peripheral 87 | [centralManager connectPeripheral:peripheralSelected options:nil]; 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | -(void)disconnectPeripheral 95 | { 96 | needToBeDisconnected = TRUE; 97 | 98 | if([peripheralSelected respondsToSelector:@selector(state)]) 99 | { 100 | if(peripheralSelected.state != CBPeripheralStateDisconnected) 101 | { 102 | //Connect the peripheral 103 | [centralManager cancelPeripheralConnection:peripheralSelected]; 104 | } 105 | } 106 | } 107 | 108 | 109 | 110 | -(void)writeValue:(NSData *)data toCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)writeType 111 | { 112 | if(peripheralSelected == nil) 113 | NSLog(@"peripheralSelected nil"); 114 | 115 | if(characteristic == nil) 116 | NSLog(@"characteristic nil"); 117 | 118 | if(peripheralSelected != nil && characteristic != nil) 119 | { 120 | [peripheralSelected writeValue:data forCharacteristic:characteristic type:writeType]; 121 | } 122 | } 123 | 124 | -(void)writeValue:(NSData *)data toCharacteristicUUIDString:(NSString *)characteristicUUIDString 125 | { 126 | #ifdef DEBUG 127 | NSLog(@"Tx : %@", [data description]); 128 | #endif 129 | 130 | if(peripheralSelected != nil) 131 | { 132 | CBCharacteristic *characteristicToWrite = nil; 133 | 134 | for(CBService *service in peripheralSelected.services) 135 | { 136 | for(CBCharacteristic *characteristic in service.characteristics) 137 | { 138 | NSString *characteristicUUID = nil; 139 | if([characteristic.UUID respondsToSelector:@selector(UUIDString)]) 140 | characteristicUUID = [characteristic.UUID UUIDString]; 141 | else 142 | characteristicUUID = [[[NSString alloc] initWithData:characteristic.UUID.data encoding:NSUTF8StringEncoding] autorelease]; 143 | 144 | if(characteristicUUID != nil && [characteristicUUID isEqualToString:characteristicUUIDString]) 145 | { 146 | characteristicToWrite = characteristic; 147 | break; 148 | } 149 | } 150 | if(characteristicToWrite != nil) 151 | break; 152 | } 153 | 154 | if(characteristicToWrite != nil) 155 | [self writeValue:data toCharacteristic:characteristicToWrite type:CBCharacteristicWriteWithResponse]; 156 | } 157 | } 158 | 159 | 160 | -(void)addPeripheralToArray:(CBPeripheral *)peripheral 161 | { 162 | BOOL UuidIsNil = FALSE; 163 | 164 | if([peripheral respondsToSelector:@selector(identifier)]) 165 | { 166 | if([peripheral.identifier UUIDString] == nil) 167 | UuidIsNil = TRUE; 168 | } 169 | 170 | //Check if the peripheral UUID is not nil befor adding it 171 | if(UuidIsNil == FALSE) 172 | { 173 | BOOL alreadyInArray = FALSE; 174 | for (CBPeripheral *peripheralInArray in discoveredPeripheralArray) 175 | { 176 | if([peripheral isEqual:peripheralInArray]) 177 | { 178 | alreadyInArray = TRUE; 179 | break; 180 | } 181 | } 182 | 183 | if(alreadyInArray == FALSE) 184 | { 185 | //Add peripheral to the array 186 | [discoveredPeripheralArray addObject:peripheral]; 187 | } 188 | } 189 | } 190 | 191 | 192 | //////////////////////////////// 193 | //Central Manager delegate 194 | //////////////// 195 | 196 | 197 | //When central manager state changes 198 | -(void)centralManagerDidUpdateState:(CBCentralManager *)central 199 | { 200 | //NSLog(@"centralManagerDidUpdateState:"); 201 | 202 | NSString *stateString = @""; 203 | 204 | switch (central.state) 205 | { 206 | case CBCentralManagerStatePoweredOff: 207 | stateString = @"POWERED OFF"; 208 | break; 209 | case CBCentralManagerStatePoweredOn: 210 | stateString = @"POWERED ON"; 211 | break; 212 | case CBCentralManagerStateResetting: 213 | stateString = @"RESETTING"; 214 | break; 215 | case CBCentralManagerStateUnauthorized: 216 | stateString = @"UNAUTHORIZED"; 217 | break; 218 | case CBCentralManagerStateUnknown: 219 | stateString = @"UNKNOWN"; 220 | break; 221 | case CBCentralManagerStateUnsupported: 222 | stateString = @"UNSUPPORTED"; 223 | break; 224 | default: 225 | stateString = @"OTHER"; 226 | break; 227 | } 228 | 229 | NSLog(@"Central Manager State : %@", stateString); 230 | } 231 | 232 | //When a peripheral is discovered 233 | -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI 234 | { 235 | if([peripheral respondsToSelector:@selector(identifier)]) 236 | NSLog(@"Peripheral discovered : %@ - %@", peripheral.name, [peripheral.identifier UUIDString]); 237 | 238 | [self addPeripheralToArray:peripheral]; 239 | } 240 | 241 | 242 | -(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral 243 | { 244 | [btleManagerDelegate peripheralIsConnected:TRUE]; 245 | 246 | if([peripheral respondsToSelector:@selector(identifier)]) 247 | NSLog(@"Peripheral connected : %@ - %@", peripheral.name, [peripheral.identifier UUIDString]); 248 | 249 | peripheralSelected = [peripheral copy]; 250 | 251 | [peripheralSelected setDelegate:self]; 252 | [peripheralSelected discoverServices:nil]; 253 | 254 | } 255 | 256 | -(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error 257 | { 258 | [btleManagerDelegate peripheralIsConnected:FALSE]; 259 | 260 | peripheralSelected = nil; 261 | //characteristicSelected = nil; 262 | 263 | //S'il est déconnecté alors qu'il ne devrait pas l'être, on le reconnecte 264 | if(needToBeDisconnected == FALSE) 265 | { 266 | if(peripheralSelected != nil) 267 | { 268 | //Reconnect peripheral disconnected 269 | [centralManager connectPeripheral:peripheralSelected options:nil]; 270 | } 271 | } 272 | else 273 | needToBeDisconnected = FALSE; 274 | 275 | if([peripheral respondsToSelector:@selector(identifier)]) 276 | NSLog(@"Peripheral disconnected : %@ - %@", peripheral.name, [peripheral.identifier UUIDString]); 277 | } 278 | 279 | -(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error 280 | { 281 | 282 | if([peripheral respondsToSelector:@selector(identifier)]) 283 | NSLog(@"Peripheral failed to connect : %@ - %@ - %@", peripheral.name, [peripheral.identifier UUIDString], [error description]); 284 | } 285 | 286 | -(void)centralManager:(CBCentralManager *)central didRetrieveConnectedPeripherals:(NSArray *)peripherals 287 | { 288 | for(CBPeripheral *peripheral in peripherals) 289 | { 290 | if([peripheral respondsToSelector:@selector(identifier)]) 291 | NSLog(@"Retrieve connected peripheral : %@ - %@", peripheral.name, [peripheral.identifier UUIDString]); 292 | } 293 | } 294 | 295 | -(void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals 296 | { 297 | /*for(CBPeripheral *peripheral in peripherals) 298 | { 299 | if([peripheral respondsToSelector:@selector(identifier)]) 300 | NSLog(@"Retrieve peripheral : %@ - %@", peripheral.name, [peripheral.identifier UUIDString]); 301 | else 302 | NSLog(@"Retrieve peripheral : %@ - %@", peripheral.name, peripheral.UUID); 303 | }*/ 304 | 305 | if([peripherals count] != 0) 306 | { 307 | CBPeripheral *peripheral = [peripherals objectAtIndex:0]; 308 | 309 | if(peripheral != nil) 310 | { 311 | peripheralSelected = [peripheral copy]; 312 | 313 | //Connect peripheral 314 | [centralManager connectPeripheral:peripheralSelected options:nil]; 315 | } 316 | } 317 | } 318 | 319 | //////////////// 320 | //Central Manager delegate 321 | //////////////////////////////// 322 | 323 | 324 | 325 | //////////////////////////////// 326 | //Central Manager delegate 327 | //////////////// 328 | 329 | -(void)peripheralDidUpdateName:(CBPeripheral *)peripheral 330 | { 331 | NSLog(@"peripheralDidUpdateName : %@", peripheral.name); 332 | } 333 | 334 | -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error 335 | { 336 | NSLog(@"didDiscoverCharacteristicsForService : %@", peripheral.name); 337 | 338 | for(CBCharacteristic *characteristic in service.characteristics) 339 | { 340 | NSLog(@"characteristic discovered : %@ - isNotifying : %i", [characteristic.UUID UUIDString], characteristic.isNotifying); 341 | 342 | //Pour s'abonner à la characteristic dès qu'il y a un changement de valeur 343 | [peripheral setNotifyValue:TRUE forCharacteristic:characteristic]; 344 | 345 | /* 346 | //Si l'UUID existe 347 | if(mainCharacterisiticUUID != nil) 348 | { 349 | if([characteristic.UUID respondsToSelector:@selector(UUIDString)]) 350 | { 351 | if([[characteristic.UUID UUIDString] isEqual:mainCharacterisiticUUID]) 352 | { 353 | characteristicSelected = characteristic; 354 | 355 | [btleManagerDelegate characteristicIsDiscovered:characteristic]; 356 | } 357 | } 358 | else 359 | { 360 | if([[[NSString alloc] initWithData:characteristic.UUID.data encoding:NSUTF8StringEncoding] isEqual:mainCharacterisiticUUID]) 361 | { 362 | characteristicSelected = characteristic; 363 | } 364 | } 365 | }*/ 366 | } 367 | } 368 | 369 | -(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 370 | { 371 | NSLog(@"peripheral - didDiscoverDescriptorsForCharacteristic : %@", peripheral.name); 372 | } 373 | 374 | -(void)peripheral:(CBPeripheral *)peripheral didDiscoverIncludedServicesForService:(CBService *)service error:(NSError *)error 375 | { 376 | NSLog(@"peripheral - didDiscoverIncludedServicesForService : %@", peripheral.name); 377 | } 378 | 379 | -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error 380 | { 381 | NSLog(@"peripheral - didDiscoverServices : %@", peripheral.name); 382 | 383 | for(CBService *service in peripheral.services) 384 | { 385 | NSLog(@"Service UUID : %@", [service.UUID UUIDString]); 386 | 387 | [peripheral setDelegate:self]; 388 | [peripheral discoverCharacteristics:nil forService:service]; 389 | } 390 | } 391 | 392 | -(void)peripheral:(CBPeripheral *)peripheral didModifyServices:(NSArray *)invalidatedServices 393 | { 394 | NSLog(@"peripheral - didModifyServices : %@", peripheral.name); 395 | } 396 | 397 | -(void)peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(NSError *)error 398 | { 399 | NSLog(@"peripheral - didReadRSSI : %@", peripheral.name); 400 | } 401 | 402 | -(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 403 | { 404 | /*if([characteristic.UUID respondsToSelector:@selector(UUIDString)]) 405 | NSLog(@"peripheral - didUpdateNotificationStateForCharacteristic : %@", [characteristic.UUID UUIDString]); 406 | else 407 | NSLog(@"peripheral - didUpdateNotificationStateForCharacteristic : %@", [[NSString alloc] initWithData:characteristic.UUID.data encoding:NSUTF8StringEncoding]);*/ 408 | 409 | } 410 | 411 | -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 412 | { 413 | //NSLog(@"dataReceived from characteristic : %@", [characteristic.UUID UUIDString]); 414 | [btleManagerDelegate dataReceivedFromPeripheral:peripheral withCharacteristic:characteristic error:error]; 415 | } 416 | 417 | -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error 418 | { 419 | NSLog(@"peripheral - didUpdateValueForDescriptor : %@", peripheral.name); 420 | } 421 | 422 | -(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 423 | { 424 | NSLog(@"peripheral - didWriteValueForCharacteristic : %@", peripheral.name); 425 | } 426 | 427 | -(void)peripheral:(CBPeripheral *)peripheral didWriteValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error 428 | { 429 | NSLog(@"peripheral - didWriteValueForDescriptor : %@", peripheral.name); 430 | } 431 | 432 | //////////////// 433 | //Peripheral delegate 434 | //////////////////////////////// 435 | 436 | 437 | -(void)dealloc 438 | { 439 | [centralManager release]; 440 | [discoveredPeripheralArray release]; 441 | 442 | [super dealloc]; 443 | } 444 | 445 | @end 446 | -------------------------------------------------------------------------------- /BleTrainerControl/SpinDownCalibrationViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SpinDownCalibrationViewController.m 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 04/09/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import "SpinDownCalibrationViewController.h" 10 | 11 | #import "AppDelegate.h" 12 | 13 | @interface SpinDownCalibrationViewController () 14 | 15 | @end 16 | 17 | @implementation SpinDownCalibrationViewController 18 | 19 | 20 | #define NB_SECTIONS 3 21 | 22 | #define SECTION_PAGE_16 0 23 | #define NB_ROWS_SECTION_PAGE_16 1 24 | #define ROW_PAGE_16_CURRENT_SPEED 0 25 | 26 | #define SECTION_PAGE_2 1 27 | #define NB_ROWS_SECTION_PAGE_2 6 28 | #define ROW_PAGE_2_TARGET_SPEED 0 29 | #define ROW_PAGE_2_SPEED_CONDITION 1 30 | #define ROW_PAGE_2_TARGET_SPIN_DOWN_TIME 2 31 | #define ROW_PAGE_2_CURRENT_TEMPERATURE 3 32 | #define ROW_PAGE_2_SPIN_DOWN_CALIBRATION_STATUS 4 33 | #define ROW_PAGE_2_TEMPERATURE_CONDITIONS 5 34 | 35 | #define SECTION_PAGE_1 2 36 | #define NB_ROWS_SECTION_PAGE_1 3 37 | #define ROW_PAGE_1_SPIN_DOWN_CALIBRATION_RESPONSE 0 38 | #define ROW_PAGE_1_TEMPERATURE_RESPONSE 1 39 | #define ROW_PAGE_1_SPIN_DOWN_TIME_RESPONSE 2 40 | 41 | 42 | - (void)viewDidLoad { 43 | [super viewDidLoad]; 44 | // Do any additional setup after loading the view from its nib. 45 | 46 | appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; 47 | 48 | [self.navigationItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStylePlain target:self action:@selector(onCloseButton:)] autorelease]]; 49 | 50 | [calibrationTableView setDelegate:self]; 51 | [calibrationTableView setDataSource:self]; 52 | 53 | Utils *utils = [[[Utils alloc] init] autorelease]; 54 | NSArray *buttonsArray = [NSArray arrayWithObjects:spinDownButton, nil]; 55 | for(UIButton *btn in buttonsArray) 56 | { 57 | [btn setBackgroundImage:[utils imageWithColor:[UIColor lightGrayColor]] forState:UIControlStateHighlighted]; 58 | [btn setBackgroundImage:[utils imageWithColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]] forState:UIControlStateNormal]; 59 | } 60 | } 61 | 62 | - (void)didReceiveMemoryWarning { 63 | [super didReceiveMemoryWarning]; 64 | // Dispose of any resources that can be recreated. 65 | } 66 | 67 | -(void)viewWillAppear:(BOOL)animated 68 | { 69 | getDataTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(refreshData) userInfo:nil repeats:TRUE]; 70 | } 71 | 72 | -(void)viewWillDisappear:(BOOL)animated 73 | { 74 | if(getDataTimer != nil) 75 | { 76 | //Stop discovery timer 77 | if([getDataTimer isValid] == TRUE) 78 | [getDataTimer invalidate]; 79 | 80 | getDataTimer = nil; 81 | } 82 | } 83 | 84 | -(void)refreshData 85 | { 86 | if(appDelegate.btleTrainerManager.calibrationStarted == TRUE) 87 | { 88 | [spinDownButton setBackgroundColor:[UIColor lightGrayColor]]; 89 | [spinDownButton setEnabled:FALSE]; 90 | } 91 | else 92 | { 93 | [spinDownButton setBackgroundColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 94 | [spinDownButton setEnabled:TRUE]; 95 | } 96 | 97 | [calibrationTableView reloadData]; 98 | 99 | } 100 | 101 | 102 | -(IBAction)onCloseButton:(id)sender 103 | { 104 | [super dismissViewControllerAnimated:TRUE completion:nil]; 105 | } 106 | 107 | -(IBAction)onStartSpinDownCalibrationButton:(id)sender 108 | { 109 | //Send request for spin-down calibration 110 | [appDelegate.btleTrainerManager sendCalibrationRequestForSpinDown:TRUE forZeroOffset:FALSE]; 111 | } 112 | 113 | 114 | 115 | 116 | 117 | 118 | //TABLE VIEW DELEGATE 119 | 120 | -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 121 | { 122 | return NB_SECTIONS; 123 | } 124 | 125 | -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 126 | { 127 | switch (section) 128 | { 129 | case SECTION_PAGE_16: 130 | return NB_ROWS_SECTION_PAGE_16; 131 | break; 132 | case SECTION_PAGE_2: 133 | return NB_ROWS_SECTION_PAGE_2; 134 | break; 135 | case SECTION_PAGE_1: 136 | return NB_ROWS_SECTION_PAGE_1; 137 | break; 138 | default: 139 | break; 140 | } 141 | 142 | return 0; 143 | } 144 | 145 | -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 146 | { 147 | return 50.0; 148 | } 149 | 150 | -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 151 | { 152 | switch (section) 153 | { 154 | case SECTION_PAGE_16: 155 | return @"Page 16 - General FE data"; 156 | break; 157 | case SECTION_PAGE_2: 158 | return @"Page 2 - Calibration in progress"; 159 | break; 160 | case SECTION_PAGE_1: 161 | return @"Page 1 - Calibration request and response"; 162 | break; 163 | default: 164 | break; 165 | } 166 | 167 | return @""; 168 | } 169 | 170 | -(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section 171 | { 172 | // Background color 173 | [view setTintColor:[UIColor darkGrayColor]]; 174 | 175 | // Text Color 176 | UITableViewHeaderFooterView *header = (UITableViewHeaderFooterView *)view; 177 | [header.textLabel setTextColor:[UIColor whiteColor]]; 178 | } 179 | 180 | //For iOS 6 181 | -(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath 182 | { 183 | //Background color 184 | if(indexPath.row % 2 == 0) 185 | { 186 | [cell setBackgroundColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:0.5]]; 187 | } 188 | else 189 | { 190 | [cell setBackgroundColor:[UIColor whiteColor]]; 191 | } 192 | } 193 | 194 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 195 | { 196 | static NSString *cellIdentifier = @"Cell"; 197 | 198 | //Cell 199 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 200 | if(cell == nil) 201 | { 202 | //Init cell 203 | cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier] autorelease]; 204 | } 205 | 206 | 207 | //Background color 208 | if(indexPath.row % 2 == 0) 209 | { 210 | [cell setBackgroundColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:0.5]]; 211 | [cell.textLabel setTextColor:[UIColor whiteColor]]; 212 | [cell.detailTextLabel setTextColor:[UIColor grayColor]]; 213 | } 214 | else 215 | { 216 | [cell setBackgroundColor:[UIColor whiteColor]]; 217 | [cell.textLabel setTextColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 218 | [cell.detailTextLabel setTextColor:[UIColor grayColor]]; 219 | } 220 | 221 | 222 | //Background color label 223 | [cell.textLabel setBackgroundColor:[UIColor clearColor]]; 224 | [cell.detailTextLabel setBackgroundColor:[UIColor clearColor]]; 225 | 226 | 227 | //No indicator 228 | [cell setAccessoryType:UITableViewCellAccessoryNone]; 229 | //No selection 230 | [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; 231 | 232 | 233 | 234 | switch (indexPath.section) 235 | { 236 | case SECTION_PAGE_16: 237 | { 238 | switch (indexPath.row) 239 | { 240 | case ROW_PAGE_16_CURRENT_SPEED: 241 | { 242 | [cell.textLabel setText:@"Speed"]; 243 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%0.1f km/h", appDelegate.btleTrainerManager.speedKmH]]; 244 | 245 | if(appDelegate.btleTrainerManager.speedCondition == SPEED_CONDITION_NOT_APPLICABLE) 246 | { 247 | //[cell.detailTextLabel setTextColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 248 | } 249 | else if(appDelegate.btleTrainerManager.speedCondition == SPEED_CONDITION_CURRENT_SPEED_TOO_LOW) 250 | { 251 | [cell.detailTextLabel setTextColor:[UIColor redColor]]; 252 | } 253 | else if(appDelegate.btleTrainerManager.speedCondition == SPEED_CONDITION_SPEED_OK) 254 | { 255 | [cell.detailTextLabel setTextColor:[UIColor greenColor]]; 256 | } 257 | else if(appDelegate.btleTrainerManager.speedCondition == SPEED_CONDITION_RESERVED) 258 | { 259 | //[cell.detailTextLabel setTextColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 260 | } 261 | } 262 | break; 263 | default: 264 | break; 265 | } 266 | } 267 | break; 268 | case SECTION_PAGE_2: 269 | { 270 | switch (indexPath.row) 271 | { 272 | case ROW_PAGE_2_TARGET_SPEED: 273 | { 274 | [cell.textLabel setText:@"Target speed"]; 275 | 276 | if(appDelegate.btleTrainerManager.targetSpeedKmH == -1) 277 | [cell.detailTextLabel setText:@"-"]; 278 | else 279 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%0.1f km/h", appDelegate.btleTrainerManager.targetSpeedKmH]]; 280 | } 281 | break; 282 | case ROW_PAGE_2_SPEED_CONDITION: 283 | { 284 | [cell.textLabel setText:@"Speed conditions"]; 285 | 286 | if(appDelegate.btleTrainerManager.speedCondition == -1) 287 | { 288 | [cell.detailTextLabel setText:@"-"]; 289 | } 290 | else if(appDelegate.btleTrainerManager.speedCondition == SPEED_CONDITION_NOT_APPLICABLE) 291 | { 292 | [cell.detailTextLabel setText:@"Not applicable"]; 293 | //[cell.detailTextLabel setTextColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 294 | } 295 | else if(appDelegate.btleTrainerManager.speedCondition == SPEED_CONDITION_CURRENT_SPEED_TOO_LOW) 296 | { 297 | [cell.detailTextLabel setText:@"Current speed too low"]; 298 | [cell.detailTextLabel setTextColor:[UIColor redColor]]; 299 | } 300 | else if(appDelegate.btleTrainerManager.speedCondition == SPEED_CONDITION_SPEED_OK) 301 | { 302 | [cell.detailTextLabel setText:@"Stop pedaling and wait for the wheel stop spinning"]; 303 | [cell.detailTextLabel setTextColor:[UIColor greenColor]]; 304 | } 305 | else if(appDelegate.btleTrainerManager.speedCondition == SPEED_CONDITION_RESERVED) 306 | { 307 | [cell.detailTextLabel setText:@"Reserved"]; 308 | //[cell.detailTextLabel setTextColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 309 | } 310 | } 311 | break; 312 | case ROW_PAGE_2_TARGET_SPIN_DOWN_TIME: 313 | { 314 | [cell.textLabel setText:@"Target spin down time"]; 315 | 316 | if(appDelegate.btleTrainerManager.targetSpinDownTimeSeconds == -1) 317 | [cell.detailTextLabel setText:@"-"]; 318 | else 319 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%0.3f s", appDelegate.btleTrainerManager.targetSpinDownTimeSeconds]]; 320 | } 321 | break; 322 | case ROW_PAGE_2_CURRENT_TEMPERATURE: 323 | { 324 | [cell.textLabel setText:@"Current temperature"]; 325 | 326 | if(appDelegate.btleTrainerManager.currentTemperatureDegC == -1) 327 | [cell.detailTextLabel setText:@"-"]; 328 | else 329 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%0.1f °C", appDelegate.btleTrainerManager.currentTemperatureDegC]]; 330 | } 331 | break; 332 | case ROW_PAGE_2_SPIN_DOWN_CALIBRATION_STATUS: 333 | { 334 | [cell.textLabel setText:@"Spin-down calibration status"]; 335 | 336 | if(appDelegate.btleTrainerManager.spinDownCalibrationStatus == -1) 337 | [cell.detailTextLabel setText:@"-"]; 338 | else if(appDelegate.btleTrainerManager.spinDownCalibrationStatus == CALIBRATION_STATUS_NOT_REQUESTED) 339 | [cell.detailTextLabel setText:@"Not requested"]; 340 | else if(appDelegate.btleTrainerManager.spinDownCalibrationStatus == CALIBRATION_STATUS_PENDING) 341 | [cell.detailTextLabel setText:@"Pending"]; 342 | } 343 | break; 344 | case ROW_PAGE_2_TEMPERATURE_CONDITIONS: 345 | { 346 | [cell.textLabel setText:@"Temperature conditions"]; 347 | 348 | if(appDelegate.btleTrainerManager.temperatureCondition == -1) 349 | [cell.detailTextLabel setText:@"-"]; 350 | else if(appDelegate.btleTrainerManager.temperatureCondition == TEMPERATURE_CONDITION_NOT_APPLICABLE) 351 | [cell.detailTextLabel setText:@"Not applicable"]; 352 | else if(appDelegate.btleTrainerManager.temperatureCondition == TEMPERATURE_CONDITION_CURRENT_TEMPERATURE_TOO_LOW) 353 | [cell.detailTextLabel setText:@"Current temperature too low"]; 354 | else if(appDelegate.btleTrainerManager.temperatureCondition == TEMPERATURE_CONDITION_TEMPERATURE_OK) 355 | [cell.detailTextLabel setText:@"Temperature OK"]; 356 | else if(appDelegate.btleTrainerManager.temperatureCondition == TEMPERATURE_CONDITION_CURRENT_TEMPERATURE_TOO_HIGH) 357 | [cell.detailTextLabel setText:@"Current temperature too high"]; 358 | } 359 | break; 360 | default: 361 | break; 362 | } 363 | } 364 | break; 365 | case SECTION_PAGE_1: 366 | { 367 | switch (indexPath.row) 368 | { 369 | case ROW_PAGE_1_SPIN_DOWN_CALIBRATION_RESPONSE: 370 | { 371 | [cell.textLabel setText:@"Spin-down calibration request/response"]; 372 | 373 | if(appDelegate.btleTrainerManager.spinDownCalibrationResponse == -1) 374 | [cell.detailTextLabel setText:@"-"]; 375 | else if(appDelegate.btleTrainerManager.spinDownCalibrationResponse == CALIBRATION_RESPONSE_FAILURE_NOT_ATTEMPTED) 376 | { 377 | [cell.detailTextLabel setTextColor:[UIColor redColor]]; 378 | [cell.detailTextLabel setText:@"Failure / Not attempted"]; 379 | } 380 | else if(appDelegate.btleTrainerManager.spinDownCalibrationResponse == CALIBRATION_RESPONSE_SUCCESS) 381 | { 382 | [cell.detailTextLabel setTextColor:[UIColor greenColor]]; 383 | [cell.detailTextLabel setText:@"Success"]; 384 | } 385 | else 386 | [cell.detailTextLabel setText:@"-"]; 387 | } 388 | break; 389 | case ROW_PAGE_1_TEMPERATURE_RESPONSE: 390 | { 391 | [cell.textLabel setText:@"Temperature"]; 392 | 393 | if(appDelegate.btleTrainerManager.temperatureResponseDegC == -1) 394 | [cell.detailTextLabel setText:@"-"]; 395 | else 396 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%0.1f °C", appDelegate.btleTrainerManager.temperatureResponseDegC]]; 397 | } 398 | break; 399 | case ROW_PAGE_1_SPIN_DOWN_TIME_RESPONSE: 400 | { 401 | [cell.textLabel setText:@"Spin-down time"]; 402 | 403 | if(appDelegate.btleTrainerManager.spinDownTimeResponseSeconds == -1) 404 | [cell.detailTextLabel setText:@"-"]; 405 | else 406 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%0.3f s", appDelegate.btleTrainerManager.spinDownTimeResponseSeconds]]; 407 | } 408 | break; 409 | default: 410 | break; 411 | } 412 | } 413 | break; 414 | default: 415 | break; 416 | } 417 | 418 | 419 | return cell; 420 | } 421 | 422 | -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 423 | { 424 | 425 | } 426 | 427 | 428 | 429 | 430 | 431 | /* 432 | #pragma mark - Navigation 433 | 434 | // In a storyboard-based application, you will often want to do a little preparation before navigation 435 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 436 | // Get the new view controller using [segue destinationViewController]. 437 | // Pass the selected object to the new view controller. 438 | } 439 | */ 440 | 441 | @end 442 | -------------------------------------------------------------------------------- /BleTrainerControl/SpinDownCalibrationViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 28 | 35 | 43 | 50 | 58 | 65 | 73 | 80 | 88 | 95 | 103 | 110 | 122 | 130 | 137 | 145 | 152 | 160 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /BleTrainerControl/ConnectionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ConnectionViewController.m 3 | // BleTrainerControl 4 | // 5 | // Created by William Minol on 28/08/2015. 6 | // Copyright (c) 2015 Kinomap. All rights reserved. 7 | // 8 | 9 | #import "ConnectionViewController.h" 10 | 11 | #import "AppDelegate.h" 12 | 13 | @interface ConnectionViewController () 14 | 15 | @end 16 | 17 | @implementation ConnectionViewController 18 | 19 | @synthesize peripheralSelected; 20 | 21 | 22 | #define NB_SECTIONS 14 23 | 24 | #define SECTION_PAGE_16 2 25 | #define NB_ROWS_SECTION_PAGE_16 6 26 | #define ROW_PAGE_16_EQUIPMENT_TYPE 0 27 | #define ROW_PAGE_16_ELAPSED_TIME 1 28 | #define ROW_PAGE_16_DISTANCE_TRAVELED 2 29 | #define ROW_PAGE_16_SPEED 3 30 | #define ROW_PAGE_16_HEART_RATE 4 31 | #define ROW_PAGE_16_VIRTUAL_SPEED 5 32 | 33 | 34 | #define SECTION_PAGE_17 3 35 | #define NB_ROWS_SECTION_PAGE_17 3 36 | #define ROW_PAGE_17_CYCLE_LENGTH 0 37 | #define ROW_PAGE_17_INCLINE 1 38 | #define ROW_PAGE_17_RESISTANCE_LEVEL 2 39 | 40 | 41 | #define SECTION_PAGE_25 4 42 | #define NB_ROWS_SECTION_PAGE_25 4 43 | #define ROW_PAGE_25_UPDATE_EVENT_COUNT 0 44 | #define ROW_PAGE_25_INSTANTANEOUS_CADENCE 1 45 | #define ROW_PAGE_25_ACCUMULATED_POWER 2 46 | #define ROW_PAGE_25_INSTANTANEOUS_POWER 3 47 | 48 | 49 | #define SECTION_PAGE_54 9 50 | #define NB_ROWS_SECTION_PAGE_54 2 51 | #define ROW_PAGE_54_MAXIMUM_RESISTANCE 0 52 | #define ROW_PAGE_54_SUPPORTED_MODE 1 53 | 54 | 55 | #define SECTION_PAGE_55 10 56 | #define NB_ROWS_SECTION_PAGE_55 5 57 | #define ROW_PAGE_55_USER_WEIGHT 0 58 | #define ROW_PAGE_55_BICYCLE_WHEEL_DIAMETER_OFFSET 1 59 | #define ROW_PAGE_55_BICYCLE_WEIGHT 2 60 | #define ROW_PAGE_55_BICYCLE_WHEEL_DIAMETER 3 61 | #define ROW_PAGE_55_GEAR_RATIO 4 62 | 63 | 64 | #define SECTION_PAGE_71 11 65 | #define NB_ROWS_SECTION_PAGE_71 4 66 | #define ROW_PAGE_71_LAST_RECEIVED_COMMAND_ID 0 67 | #define ROW_PAGE_71_SEQUENCE 1 68 | #define ROW_PAGE_71_COMMAND_STATUS 2 69 | #define ROW_PAGE_71_DATA 3 70 | 71 | 72 | #define SECTION_PAGE_80 12 73 | #define NB_ROWS_SECTION_PAGE_80 3 74 | #define ROW_PAGE_80_HW_REVISION 0 75 | #define ROW_PAGE_80_MANUFACTURER_ID 1 76 | #define ROW_PAGE_80_MODEL_NUMBER 2 77 | 78 | 79 | #define SECTION_PAGE_81 13 80 | #define NB_ROWS_SECTION_PAGE_81 3 81 | #define ROW_PAGE_81_SW_REVISION_SUPPLEMENTAL 0 82 | #define ROW_PAGE_81_SW_REVISION_MAIN 1 83 | #define ROW_PAGE_81_SERIAL_NUMBER 2 84 | 85 | 86 | 87 | 88 | - (void)viewDidLoad 89 | { 90 | [super viewDidLoad]; 91 | // Do any additional setup after loading the view from its nib. 92 | 93 | appDelegate = [[UIApplication sharedApplication] delegate]; 94 | 95 | if(peripheralSelected != nil) 96 | self.title = peripheralSelected.name; 97 | 98 | [dataPagesTableView setDelegate:self]; 99 | [dataPagesTableView setDataSource:self]; 100 | 101 | 102 | Utils *utils = [[[Utils alloc] init] autorelease]; 103 | NSArray *buttonsArray = [NSArray arrayWithObjects:trackResistanceButton, windResistanceButton, basicResistanceButton, targetPowerButton, calibrationButton, requestPage54Button, requestPage55Button, nil]; 104 | for(UIButton *btn in buttonsArray) 105 | { 106 | [btn setBackgroundImage:[utils imageWithColor:[UIColor lightGrayColor]] forState:UIControlStateHighlighted]; 107 | [btn setBackgroundImage:[utils imageWithColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]] forState:UIControlStateNormal]; 108 | } 109 | } 110 | 111 | 112 | 113 | 114 | -(void)viewWillAppear:(BOOL)animated 115 | { 116 | if(isDisplayingModalView == FALSE) 117 | { 118 | if(peripheralSelected != nil) 119 | { 120 | [self connect]; 121 | } 122 | else 123 | { 124 | UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"Error" message:@"Device doesn't exist" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Ok", nil] autorelease]; 125 | [alert show]; 126 | 127 | [self.navigationController popViewControllerAnimated:TRUE]; 128 | } 129 | 130 | getDataTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(refreshData) userInfo:nil repeats:TRUE]; 131 | } 132 | else 133 | { 134 | isDisplayingModalView = FALSE; 135 | } 136 | } 137 | 138 | -(void)viewWillDisappear:(BOOL)animated 139 | { 140 | if(isDisplayingModalView == FALSE) 141 | { 142 | [self disconnect]; 143 | 144 | if(getDataTimer != nil) 145 | { 146 | //Stop discovery timer 147 | if([getDataTimer isValid] == TRUE) 148 | [getDataTimer invalidate]; 149 | 150 | getDataTimer = nil; 151 | } 152 | } 153 | } 154 | 155 | -(void)connect 156 | { 157 | if(peripheralSelected != nil) 158 | { 159 | if([peripheralSelected respondsToSelector:@selector(identifier)]) 160 | [appDelegate.btleTrainerManager connectWithUUID:[peripheralSelected.identifier UUIDString]]; 161 | } 162 | } 163 | 164 | -(void)disconnect 165 | { 166 | if(peripheralSelected != nil) 167 | { 168 | [appDelegate.btleTrainerManager disconnect]; 169 | } 170 | } 171 | 172 | -(void)refreshData 173 | { 174 | if(appDelegate.btleTrainerManager.isConnected == TRUE) 175 | { 176 | [statusValueLabel setText:@"Connected"]; 177 | [statusValueLabel setTextColor:[UIColor greenColor]]; 178 | 179 | 180 | } 181 | else 182 | { 183 | //Try to reconnect 184 | [self connect]; 185 | 186 | [statusValueLabel setText:@"Disconnected"]; 187 | [statusValueLabel setTextColor:[UIColor redColor]]; 188 | } 189 | 190 | [dataPagesTableView reloadData]; 191 | } 192 | 193 | 194 | 195 | 196 | //TABLE VIEW DELEGATE 197 | 198 | -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 199 | { 200 | return NB_SECTIONS; 201 | } 202 | 203 | -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 204 | { 205 | switch (section) 206 | { 207 | case SECTION_PAGE_16: 208 | return NB_ROWS_SECTION_PAGE_16; 209 | break; 210 | case SECTION_PAGE_17: 211 | return NB_ROWS_SECTION_PAGE_17; 212 | break; 213 | case SECTION_PAGE_25: 214 | return NB_ROWS_SECTION_PAGE_25; 215 | break; 216 | case SECTION_PAGE_54: 217 | return NB_ROWS_SECTION_PAGE_54; 218 | break; 219 | case SECTION_PAGE_55: 220 | return NB_ROWS_SECTION_PAGE_55; 221 | break; 222 | case SECTION_PAGE_71: 223 | return NB_ROWS_SECTION_PAGE_71; 224 | break; 225 | case SECTION_PAGE_80: 226 | return NB_ROWS_SECTION_PAGE_80; 227 | break; 228 | case SECTION_PAGE_81: 229 | return NB_ROWS_SECTION_PAGE_81; 230 | break; 231 | default: 232 | break; 233 | } 234 | 235 | return 0; 236 | } 237 | 238 | -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 239 | { 240 | return 50.0; 241 | } 242 | 243 | -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 244 | { 245 | switch (section) 246 | { 247 | case SECTION_PAGE_16: 248 | return @"Page 16 - General FE data"; 249 | break; 250 | case SECTION_PAGE_17: 251 | return @"Page 17 - General settings"; 252 | break; 253 | case SECTION_PAGE_25: 254 | return @"Page 25 - Specific trainer"; 255 | break; 256 | case SECTION_PAGE_54: 257 | return @"Page 54 - FE capabilities"; 258 | break; 259 | case SECTION_PAGE_55: 260 | return @"Page 55 - User configuration"; 261 | break; 262 | case SECTION_PAGE_71: 263 | return @"Page 71 - Command status"; 264 | break; 265 | case SECTION_PAGE_80: 266 | return @"Page 80 - Manufacturer's identification"; 267 | break; 268 | case SECTION_PAGE_81: 269 | return @"Page 81 - Product information"; 270 | break; 271 | default: 272 | break; 273 | } 274 | 275 | return @""; 276 | } 277 | 278 | -(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section 279 | { 280 | // Background color 281 | [view setTintColor:[UIColor darkGrayColor]]; 282 | 283 | // Text Color 284 | UITableViewHeaderFooterView *header = (UITableViewHeaderFooterView *)view; 285 | [header.textLabel setTextColor:[UIColor whiteColor]]; 286 | } 287 | 288 | //For iOS 6 289 | -(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath 290 | { 291 | //Background color 292 | if(indexPath.row % 2 == 0) 293 | { 294 | [cell setBackgroundColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:0.5]]; 295 | [cell.textLabel setTextColor:[UIColor whiteColor]]; 296 | [cell.detailTextLabel setTextColor:[UIColor whiteColor]]; 297 | } 298 | else 299 | { 300 | [cell setBackgroundColor:[UIColor whiteColor]]; 301 | [cell.textLabel setTextColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 302 | [cell.detailTextLabel setTextColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 303 | } 304 | } 305 | 306 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 307 | { 308 | static NSString *cellIdentifier = @"Cell"; 309 | 310 | //Cell 311 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 312 | if(cell == nil) 313 | { 314 | //Init cell 315 | cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier] autorelease]; 316 | } 317 | 318 | //Background color 319 | if(indexPath.row % 2 == 0) 320 | { 321 | [cell setBackgroundColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:0.5]]; 322 | [cell.textLabel setTextColor:[UIColor whiteColor]]; 323 | [cell.detailTextLabel setTextColor:[UIColor grayColor]]; 324 | } 325 | else 326 | { 327 | [cell setBackgroundColor:[UIColor whiteColor]]; 328 | [cell.textLabel setTextColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 329 | [cell.detailTextLabel setTextColor:[UIColor grayColor]]; 330 | } 331 | 332 | 333 | //Background color label 334 | [cell.textLabel setBackgroundColor:[UIColor clearColor]]; 335 | [cell.detailTextLabel setBackgroundColor:[UIColor clearColor]]; 336 | 337 | 338 | //No indicator 339 | [cell setAccessoryType:UITableViewCellAccessoryNone]; 340 | //No selection 341 | [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; 342 | 343 | 344 | 345 | switch (indexPath.section) 346 | { 347 | case SECTION_PAGE_16: 348 | { 349 | switch (indexPath.row) 350 | { 351 | case ROW_PAGE_16_EQUIPMENT_TYPE: 352 | { 353 | [cell.textLabel setText:@"Equipment type"]; 354 | if(appDelegate.btleTrainerManager.equipmentTypeString != nil) 355 | [cell.detailTextLabel setText:appDelegate.btleTrainerManager.equipmentTypeString]; 356 | else 357 | [cell.detailTextLabel setText:@"-"]; 358 | } 359 | break; 360 | case ROW_PAGE_16_ELAPSED_TIME: 361 | { 362 | [cell.textLabel setText:@"Elapsed time"]; 363 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li seconds", (long)appDelegate.btleTrainerManager.elapsedTimeSeconds]]; 364 | } 365 | break; 366 | case ROW_PAGE_16_DISTANCE_TRAVELED: 367 | { 368 | [cell.textLabel setText:@"Distance traveled"]; 369 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li m", (long)appDelegate.btleTrainerManager.distanceTraveledMeters]]; 370 | } 371 | break; 372 | case ROW_PAGE_16_SPEED: 373 | { 374 | [cell.textLabel setText:@"Speed"]; 375 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%0.1f km/h", appDelegate.btleTrainerManager.speedKmH]]; 376 | } 377 | break; 378 | case ROW_PAGE_16_HEART_RATE: 379 | { 380 | [cell.textLabel setText:@"Heart rate"]; 381 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li BPM", (long)appDelegate.btleTrainerManager.heartRateBPM]]; 382 | } 383 | break; 384 | case ROW_PAGE_16_VIRTUAL_SPEED: 385 | { 386 | [cell.textLabel setText:@"Virtual speed"]; 387 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%@", appDelegate.btleTrainerManager.virtualSpeed? @"True":@"False"]]; 388 | } 389 | break; 390 | default: 391 | break; 392 | } 393 | } 394 | break; 395 | case SECTION_PAGE_17: 396 | { 397 | switch (indexPath.row) 398 | { 399 | case ROW_PAGE_17_CYCLE_LENGTH: 400 | { 401 | [cell.textLabel setText:@"Cycle length"]; 402 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%0.2f m", appDelegate.btleTrainerManager.cycleLengthM]]; 403 | } 404 | break; 405 | case ROW_PAGE_17_INCLINE: 406 | { 407 | [cell.textLabel setText:@"Incline"]; 408 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%0.2f %%", appDelegate.btleTrainerManager.inclinePercent]]; 409 | } 410 | break; 411 | case ROW_PAGE_17_RESISTANCE_LEVEL: 412 | { 413 | [cell.textLabel setText:@"Resistance level"]; 414 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%0.1f %%", appDelegate.btleTrainerManager.resistanceLevelPercent]]; 415 | } 416 | break; 417 | default: 418 | break; 419 | } 420 | } 421 | break; 422 | case SECTION_PAGE_25: 423 | { 424 | switch (indexPath.row) 425 | { 426 | case ROW_PAGE_25_UPDATE_EVENT_COUNT: 427 | { 428 | [cell.textLabel setText:@"Update event count"]; 429 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li", (long)appDelegate.btleTrainerManager.updateEventCount]]; 430 | } 431 | break; 432 | case ROW_PAGE_25_INSTANTANEOUS_CADENCE: 433 | { 434 | [cell.textLabel setText:@"Instantaneous cadence"]; 435 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li RPM", (long)appDelegate.btleTrainerManager.cadenceRPM]]; 436 | } 437 | break; 438 | case ROW_PAGE_25_ACCUMULATED_POWER: 439 | { 440 | [cell.textLabel setText:@"Accumulated power"]; 441 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li W", (long)appDelegate.btleTrainerManager.accumulatedPowerW]]; 442 | } 443 | break; 444 | case ROW_PAGE_25_INSTANTANEOUS_POWER: 445 | { 446 | [cell.textLabel setText:@"Instantaneous power"]; 447 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li W", (long)appDelegate.btleTrainerManager.powerW]]; 448 | } 449 | break; 450 | default: 451 | break; 452 | } 453 | } 454 | break; 455 | case SECTION_PAGE_54: 456 | { 457 | switch (indexPath.row) 458 | { 459 | case ROW_PAGE_54_MAXIMUM_RESISTANCE: 460 | { 461 | [cell.textLabel setText:@"Maximum resistance"]; 462 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li N", (long)appDelegate.btleTrainerManager.maximumResistanceN]]; 463 | } 464 | break; 465 | case ROW_PAGE_54_SUPPORTED_MODE: 466 | { 467 | [cell.textLabel setText:@"Supported mode(s)"]; 468 | if(appDelegate.btleTrainerManager.supportedMode != nil) 469 | [cell.detailTextLabel setText:appDelegate.btleTrainerManager.supportedMode]; 470 | else 471 | [cell.detailTextLabel setText:@"-"]; 472 | } 473 | break; 474 | default: 475 | break; 476 | } 477 | } 478 | break; 479 | case SECTION_PAGE_55: 480 | { 481 | switch (indexPath.row) 482 | { 483 | case ROW_PAGE_55_USER_WEIGHT: 484 | { 485 | [cell.textLabel setText:@"User weight"]; 486 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%0.0f kg", appDelegate.btleTrainerManager.userWeightKg]]; 487 | } 488 | break; 489 | case ROW_PAGE_55_BICYCLE_WHEEL_DIAMETER_OFFSET: 490 | { 491 | [cell.textLabel setText:@"Bicycle wheel diameter offset"]; 492 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li mm", (long)appDelegate.btleTrainerManager.bicycleWheelDiameterOffsetMm]]; 493 | } 494 | break; 495 | case ROW_PAGE_55_BICYCLE_WEIGHT: 496 | { 497 | [cell.textLabel setText:@"Bicycle weight"]; 498 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%0.2f kg", appDelegate.btleTrainerManager.bicycleWeightKg]]; 499 | } 500 | break; 501 | case ROW_PAGE_55_BICYCLE_WHEEL_DIAMETER: 502 | { 503 | [cell.textLabel setText:@"Bicycle wheel diameter"]; 504 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%0.2f m", appDelegate.btleTrainerManager.bicycleWheelDiameterM]]; 505 | } 506 | break; 507 | case ROW_PAGE_55_GEAR_RATIO: 508 | { 509 | [cell.textLabel setText:@"Gear ratio"]; 510 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%0.2f", appDelegate.btleTrainerManager.gearRatio]]; 511 | } 512 | break; 513 | default: 514 | break; 515 | } 516 | } 517 | break; 518 | case SECTION_PAGE_71: 519 | { 520 | switch (indexPath.row) 521 | { 522 | case ROW_PAGE_71_LAST_RECEIVED_COMMAND_ID: 523 | { 524 | [cell.textLabel setText:@"Last received command ID"]; 525 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"Page %li", (long)appDelegate.btleTrainerManager.lastReceivedCommandID]]; 526 | } 527 | break; 528 | case ROW_PAGE_71_SEQUENCE: 529 | { 530 | [cell.textLabel setText:@"Sequence"]; 531 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li", (long)appDelegate.btleTrainerManager.sequence]]; 532 | } 533 | break; 534 | case ROW_PAGE_71_COMMAND_STATUS: 535 | { 536 | [cell.textLabel setText:@"Command status"]; 537 | if(appDelegate.btleTrainerManager.commandStatusString != nil) 538 | [cell.detailTextLabel setText:appDelegate.btleTrainerManager.commandStatusString]; 539 | else 540 | [cell.detailTextLabel setText:@"-"]; 541 | } 542 | break; 543 | case ROW_PAGE_71_DATA: 544 | { 545 | [cell.textLabel setText:@"Data"]; 546 | if(appDelegate.btleTrainerManager.dataString != nil) 547 | [cell.detailTextLabel setText:appDelegate.btleTrainerManager.dataString]; 548 | else 549 | [cell.detailTextLabel setText:@"-"]; 550 | } 551 | break; 552 | default: 553 | break; 554 | } 555 | } 556 | break; 557 | case SECTION_PAGE_80: 558 | { 559 | switch (indexPath.row) 560 | { 561 | case ROW_PAGE_80_HW_REVISION: 562 | { 563 | [cell.textLabel setText:@"HW revision"]; 564 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li", (long)appDelegate.btleTrainerManager.hwRevision]]; 565 | } 566 | break; 567 | case ROW_PAGE_80_MANUFACTURER_ID: 568 | { 569 | [cell.textLabel setText:@"Manufacturer ID"]; 570 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li", (long)appDelegate.btleTrainerManager.manufacturerID]]; 571 | } 572 | break; 573 | case ROW_PAGE_80_MODEL_NUMBER: 574 | { 575 | [cell.textLabel setText:@"Model number"]; 576 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li", (long)appDelegate.btleTrainerManager.modelNumber]]; 577 | } 578 | break; 579 | default: 580 | break; 581 | } 582 | } 583 | break; 584 | case SECTION_PAGE_81: 585 | { 586 | switch (indexPath.row) 587 | { 588 | case ROW_PAGE_81_SW_REVISION_SUPPLEMENTAL: 589 | { 590 | [cell.textLabel setText:@"SW revision supplemental"]; 591 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li", (long)appDelegate.btleTrainerManager.swRevisionSupplemental]]; 592 | } 593 | break; 594 | case ROW_PAGE_81_SW_REVISION_MAIN: 595 | { 596 | [cell.textLabel setText:@"SW revision main"]; 597 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li", (long)appDelegate.btleTrainerManager.swRevisionMain]]; 598 | } 599 | break; 600 | case ROW_PAGE_81_SERIAL_NUMBER: 601 | { 602 | [cell.textLabel setText:@"Serial number"]; 603 | [cell.detailTextLabel setText:[NSString stringWithFormat:@"%li", (long)appDelegate.btleTrainerManager.serialNumber]]; 604 | } 605 | break; 606 | default: 607 | break; 608 | } 609 | } 610 | break; 611 | default: 612 | break; 613 | } 614 | 615 | 616 | return cell; 617 | } 618 | 619 | -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 620 | { 621 | 622 | } 623 | 624 | 625 | 626 | -(IBAction)onBasicResistance:(id)sender 627 | { 628 | if(appDelegate.btleTrainerManager.isConnected == TRUE) 629 | { 630 | BasicResistanceViewController *basicResistanceViewController = [[[BasicResistanceViewController alloc] init] autorelease]; 631 | [self openModalWithRootViewlController:basicResistanceViewController]; 632 | } 633 | } 634 | 635 | -(IBAction)onTargetPower:(id)sender 636 | { 637 | if(appDelegate.btleTrainerManager.isConnected == TRUE) 638 | { 639 | TargetPowerViewController *targetPowerViewController = [[[TargetPowerViewController alloc] init] autorelease]; 640 | [self openModalWithRootViewlController:targetPowerViewController]; 641 | } 642 | } 643 | 644 | -(IBAction)onWindResistance:(id)sender 645 | { 646 | if(appDelegate.btleTrainerManager.isConnected == TRUE) 647 | { 648 | WindResistanceViewController *windResistanceViewController = [[[WindResistanceViewController alloc] init] autorelease]; 649 | [self openModalWithRootViewlController:windResistanceViewController]; 650 | } 651 | } 652 | 653 | -(IBAction)onTrackResistance:(id)sender 654 | { 655 | if(appDelegate.btleTrainerManager.isConnected == TRUE) 656 | { 657 | TrackResistanceViewController *trackResistanceViewController = [[[TrackResistanceViewController alloc] init] autorelease]; 658 | [self openModalWithRootViewlController:trackResistanceViewController]; 659 | } 660 | } 661 | 662 | 663 | 664 | 665 | //Calibration 666 | -(IBAction)onCalibration:(id)sender 667 | { 668 | if(appDelegate.btleTrainerManager.isConnected == TRUE) 669 | { 670 | SpinDownCalibrationViewController *spinDownCalibrationViewController = [[[SpinDownCalibrationViewController alloc] init] autorelease]; 671 | [self openModalWithRootViewlController:spinDownCalibrationViewController]; 672 | } 673 | } 674 | 675 | 676 | 677 | 678 | -(void)openModalWithRootViewlController:(UIViewController *)rootViewController 679 | { 680 | isDisplayingModalView = TRUE; 681 | 682 | UINavigationController *navController = [[[UINavigationController alloc] initWithRootViewController:rootViewController] autorelease]; 683 | [navController.navigationBar setTranslucent:FALSE]; 684 | if([navController.navigationBar respondsToSelector:@selector(setBarTintColor:)]) 685 | { 686 | [navController.navigationBar setBarTintColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 687 | [navController.navigationBar setTintColor:[UIColor whiteColor]]; 688 | } 689 | else 690 | { 691 | [[UINavigationBar appearance] setBackgroundImage:[[UIImage alloc] init] forBarMetrics:UIBarMetricsDefault]; 692 | [[UINavigationBar appearance] setBackgroundColor:[UIColor colorWithRed:76./255. green:164./255. blue:223./255. alpha:1.0]]; 693 | } 694 | [navController.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName : [UIColor whiteColor]}]; 695 | 696 | [navController setModalPresentationStyle:UIModalPresentationFormSheet]; 697 | 698 | [self presentViewController:navController animated:TRUE completion:nil]; 699 | } 700 | 701 | 702 | 703 | //Request page 704 | -(IBAction)requestPage54:(id)sender 705 | { 706 | if(appDelegate.btleTrainerManager.isConnected == TRUE) 707 | [appDelegate.btleTrainerManager sendRequestPage:54]; 708 | } 709 | 710 | -(IBAction)requestPage55:(id)sender 711 | { 712 | if(appDelegate.btleTrainerManager.isConnected == TRUE) 713 | [appDelegate.btleTrainerManager sendRequestPage:55]; 714 | } 715 | 716 | 717 | 718 | 719 | - (void)didReceiveMemoryWarning { 720 | [super didReceiveMemoryWarning]; 721 | // Dispose of any resources that can be recreated. 722 | } 723 | 724 | /* 725 | #pragma mark - Navigation 726 | 727 | // In a storyboard-based application, you will often want to do a little preparation before navigation 728 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 729 | // Get the new view controller using [segue destinationViewController]. 730 | // Pass the selected object to the new view controller. 731 | } 732 | */ 733 | 734 | 735 | 736 | @end 737 | --------------------------------------------------------------------------------