├── .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 |
--------------------------------------------------------------------------------