From f3cac8e520a3ecb861b78b440d6b81a2399fa3ce Mon Sep 17 00:00:00 2001 From: Aaron Parecki Date: Tue, 27 Oct 2015 14:31:36 -0700 Subject: [PATCH] add Pebble support for trips --- GPSLogger.xcodeproj/project.pbxproj | 14 +++ GPSLogger/FirstViewController.m | 4 +- GPSLogger/GLManager.h | 2 + GPSLogger/GLManager.m | 21 ++++- GPSLogger/Info.plist | 7 ++ GPSLogger/PebbleManager.h | 22 +++++ GPSLogger/PebbleManager.m | 137 ++++++++++++++++++++++++++++ Podfile | 1 + Podfile.lock | 3 + 9 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 GPSLogger/PebbleManager.h create mode 100644 GPSLogger/PebbleManager.m diff --git a/GPSLogger.xcodeproj/project.pbxproj b/GPSLogger.xcodeproj/project.pbxproj index 28665ee..f02d014 100644 --- a/GPSLogger.xcodeproj/project.pbxproj +++ b/GPSLogger.xcodeproj/project.pbxproj @@ -8,6 +8,9 @@ /* Begin PBXBuildFile section */ 0F444F0C1BD04A7E00106C2E /* TripModeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0F444F0B1BD04A7E00106C2E /* TripModeViewController.m */; settings = {ASSET_TAGS = (); }; }; + 0FB85ABC1BE0048300F25126 /* PebbleManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0FB85ABB1BE0048300F25126 /* PebbleManager.m */; settings = {ASSET_TAGS = (); }; }; + 0FB85ABE1BE0151300F25126 /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0FB85ABD1BE0151300F25126 /* CoreBluetooth.framework */; }; + 0FB85AC01BE0151800F25126 /* ExternalAccessory.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0FB85ABF1BE0151800F25126 /* ExternalAccessory.framework */; }; 0FFD31571BAB7A34009B3A03 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0FFD31561BAB7A34009B3A03 /* main.m */; }; 0FFD315A1BAB7A34009B3A03 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0FFD31591BAB7A34009B3A03 /* AppDelegate.m */; }; 0FFD315D1BAB7A34009B3A03 /* FirstViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0FFD315C1BAB7A34009B3A03 /* FirstViewController.m */; }; @@ -27,6 +30,10 @@ /* Begin PBXFileReference section */ 0F444F0A1BD04A7E00106C2E /* TripModeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TripModeViewController.h; sourceTree = ""; }; 0F444F0B1BD04A7E00106C2E /* TripModeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TripModeViewController.m; sourceTree = ""; }; + 0FB85ABA1BE0048300F25126 /* PebbleManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PebbleManager.h; sourceTree = ""; }; + 0FB85ABB1BE0048300F25126 /* PebbleManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PebbleManager.m; sourceTree = ""; }; + 0FB85ABD1BE0151300F25126 /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; }; + 0FB85ABF1BE0151800F25126 /* ExternalAccessory.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ExternalAccessory.framework; path = System/Library/Frameworks/ExternalAccessory.framework; sourceTree = SDKROOT; }; 0FFD31521BAB7A34009B3A03 /* GPSLogger.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GPSLogger.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0FFD31561BAB7A34009B3A03 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 0FFD31581BAB7A34009B3A03 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -57,6 +64,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 0FB85AC01BE0151800F25126 /* ExternalAccessory.framework in Frameworks */, + 0FB85ABE1BE0151300F25126 /* CoreBluetooth.framework in Frameworks */, 0FFD31781BAB7C7F009B3A03 /* CoreLocation.framework in Frameworks */, 0FFD31791BAB7C7F009B3A03 /* CoreMotion.framework in Frameworks */, 0FFD317A1BAB7C7F009B3A03 /* libsqlite3.tbd in Frameworks */, @@ -71,6 +80,8 @@ 0BE775F16162CE9DF6AE0775 /* Frameworks */ = { isa = PBXGroup; children = ( + 0FB85ABF1BE0151800F25126 /* ExternalAccessory.framework */, + 0FB85ABD1BE0151300F25126 /* CoreBluetooth.framework */, 0FFD31741BAB7C7F009B3A03 /* CoreLocation.framework */, 0FFD31751BAB7C7F009B3A03 /* CoreMotion.framework */, 0FFD31761BAB7C7F009B3A03 /* libsqlite3.tbd */, @@ -111,6 +122,8 @@ 0FFD315F1BAB7A34009B3A03 /* SecondViewController.m */, 0FFD31711BAB7BEA009B3A03 /* GLManager.h */, 0FFD31721BAB7BEA009B3A03 /* GLManager.m */, + 0FB85ABA1BE0048300F25126 /* PebbleManager.h */, + 0FB85ABB1BE0048300F25126 /* PebbleManager.m */, 0FFD31611BAB7A34009B3A03 /* Main.storyboard */, 0FFD31641BAB7A34009B3A03 /* Assets.xcassets */, 0FFD31661BAB7A34009B3A03 /* LaunchScreen.storyboard */, @@ -264,6 +277,7 @@ 0FFD315D1BAB7A34009B3A03 /* FirstViewController.m in Sources */, 0FFD317F1BAB7CC7009B3A03 /* LOLDatabase.m in Sources */, 0FFD31571BAB7A34009B3A03 /* main.m in Sources */, + 0FB85ABC1BE0048300F25126 /* PebbleManager.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/GPSLogger/FirstViewController.m b/GPSLogger/FirstViewController.m index c9c750d..2d8323a 100644 --- a/GPSLogger/FirstViewController.m +++ b/GPSLogger/FirstViewController.m @@ -8,6 +8,7 @@ #import "FirstViewController.h" #import "GLManager.h" +#import "PebbleManager.h" static NSString *const GLTripTrackingEnabledDefaultsName = @"GLTripTrackingEnabledDefaults"; @@ -215,7 +216,8 @@ - (void)updateTripState { [self.tripStartStopButton setTitle:@"Stop" forState:UIControlStateNormal]; self.tripStartStopButton.backgroundColor = [UIColor colorWithRed:252.f/255.f green:109.f/255.f blue:111.f/255.f alpha:1]; self.tripDurationLabel.text = [FirstViewController timeFormatted:[GLManager sharedManager].currentTripDuration]; - self.tripDistanceLabel.text = [NSString stringWithFormat:@"%0.2f", [GLManager sharedManager].currentTripDistance * 0.000621371]; + self.tripDistanceLabel.text = [NSString stringWithFormat:@"%0.2f", [GLManager sharedManager].currentTripDistance * MetersToMiles]; + [[PebbleManager sharedManager] refreshWatchface]; } else { [self.tripStartStopButton setTitle:@"Start" forState:UIControlStateNormal]; self.tripStartStopButton.backgroundColor = [UIColor colorWithRed:106.f/255.f green:212.f/255.f blue:150.f/255.f alpha:1]; diff --git a/GPSLogger/GLManager.h b/GPSLogger/GLManager.h index 5e33bc8..7366cc7 100644 --- a/GPSLogger/GLManager.h +++ b/GPSLogger/GLManager.h @@ -38,6 +38,7 @@ static NSString *const GLTripModeTrain = @"train"; static NSString *const GLTripModePlane = @"plane"; static int const PointsPerBatch = 200; +static double const MetersToMiles = 0.000621371; typedef enum { kGLSignificantLocationDisabled, @@ -89,6 +90,7 @@ typedef enum { - (NSDate *)currentTripStart; - (CLLocationDistance)currentTripDistance; - (NSTimeInterval)currentTripDuration; +- (double)currentTripSpeed; - (void)startTrip; - (void)endTrip; diff --git a/GPSLogger/GLManager.m b/GPSLogger/GLManager.m index 994e79d..a584370 100644 --- a/GPSLogger/GLManager.m +++ b/GPSLogger/GLManager.m @@ -10,6 +10,7 @@ #import "AFHTTPSessionManager.h" #import "LOLDatabase.h" #import "FMDatabase.h" +#import "PebbleManager.h" @interface GLManager() @@ -238,6 +239,7 @@ - (void)restoreTrackingState { if(self.tripInProgress) { // If a trip is in progress, open the trip DB now [self.tripdb open]; + [[PebbleManager sharedManager] startWatchSession]; } } else { [self disableTracking]; @@ -409,11 +411,25 @@ - (CLLocationDistance)currentTripDistance { return distance; } +/** + * speed in miles per hour + */ +- (double)currentTripSpeed { + if(!self.tripInProgress) { + return -1; + } + + double speedMS = self.currentTripDistance / self.currentTripDuration; + return speedMS * 2.23694; +} + - (void)startTrip { if(self.tripInProgress) { return; } + [[PebbleManager sharedManager] startWatchSession]; + NSDate *startDate = [NSDate date]; [[NSUserDefaults standardUserDefaults] setObject:startDate forKey:GLTripStartTimeDefaultsName]; [[NSUserDefaults standardUserDefaults] synchronize]; @@ -434,6 +450,8 @@ - (void)endTripFromAutopause:(BOOL)autopause { return; } + [[PebbleManager sharedManager] stopWatchSession]; + [self.db accessCollection:GLLocationQueueName withBlock:^(id accessor) { NSString *timestamp = [GLManager iso8601DateStringFromDate:[NSDate date]]; NSMutableDictionary *update = [NSMutableDictionary dictionaryWithDictionary:@{ @@ -669,7 +687,7 @@ - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray NSLog(@"Received %d locations", (int)locations.count); - NSLog(@"%@", locations); + // NSLog(@"%@", locations); // Queue the point in the database [self.db accessCollection:GLLocationQueueName withBlock:^(id accessor) { @@ -735,6 +753,7 @@ - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray // If a trip is in progress, add to the trip's list too if(self.tripInProgress && loc.horizontalAccuracy <= 100) { [self.tripdb executeUpdate:@"INSERT INTO trips (timestamp, latitude, longitude) VALUES (?, ?, ?)", [NSNumber numberWithInt:[loc.timestamp timeIntervalSince1970]], [NSNumber numberWithDouble:loc.coordinate.latitude], [NSNumber numberWithDouble:loc.coordinate.longitude]]; + [[PebbleManager sharedManager] refreshWatchface]; _currentTripHasNewData = YES; } diff --git a/GPSLogger/Info.plist b/GPSLogger/Info.plist index 29f70f2..32c4ef9 100644 --- a/GPSLogger/Info.plist +++ b/GPSLogger/Info.plist @@ -30,6 +30,9 @@ Please enable location services UIBackgroundModes + bluetooth-central + bluetooth-peripheral + external-accessory fetch location remote-notification @@ -52,6 +55,10 @@ + UISupportedExternalAccessoryProtocols + + com.getpebble.public + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/GPSLogger/PebbleManager.h b/GPSLogger/PebbleManager.h new file mode 100644 index 0000000..2badefa --- /dev/null +++ b/GPSLogger/PebbleManager.h @@ -0,0 +1,22 @@ +// +// PebbleManager.h +// GPSLogger +// +// Created by Aaron Parecki on 10/27/15. +// Copyright © 2015 Aaron Parecki. All rights reserved. +// + +#import +@import PebbleKit; + +@interface PebbleManager : NSObject + ++ (PebbleManager *)sharedManager; + +- (void)startWatchSession; +- (void)stopWatchSession; + +- (void)refreshWatchface; +- (void)updateTripInfoWithTime:(NSTimeInterval)time distance:(double)distance speed:(double)speed; + +@end diff --git a/GPSLogger/PebbleManager.m b/GPSLogger/PebbleManager.m new file mode 100644 index 0000000..fc170bf --- /dev/null +++ b/GPSLogger/PebbleManager.m @@ -0,0 +1,137 @@ +// +// PebbleManager.m +// GPSLogger +// +// Created by Aaron Parecki on 10/27/15. +// Copyright © 2015 Aaron Parecki. All rights reserved. +// + +#import "PebbleManager.h" +#import "GLManager.h" + +@implementation PebbleManager { + PBWatch *_targetWatch; + bool sportsEnabled; +} + ++ (PebbleManager *)sharedManager { + static PebbleManager *_instance = nil; + + @synchronized (self) { + if (_instance == nil) { + _instance = [[self alloc] init]; + + [[PBPebbleCentral defaultCentral] setDelegate:_instance]; + // Configure our communications channel to target the sports app: + [[PBPebbleCentral defaultCentral] setAppUUID:PBSportsUUID]; + [[PBPebbleCentral defaultCentral] run]; + NSLog(@"Registered watches: %@", [[PBPebbleCentral defaultCentral] registeredWatches]); + } + } + + return _instance; +} + +- (void)startWatchSession { + NSLog(@"Pebble: Starting watch session with last connected watch: %@", [[PBPebbleCentral defaultCentral] lastConnectedWatch]); + + [self setTargetWatch:[[PBPebbleCentral defaultCentral] lastConnectedWatch]]; + +// if(!sportsEnabled) return; + [self configureWatchSession]; +} + +- (void)configureWatchSession { + [_targetWatch sportsAppSetLabel:NO onSent:^(PBWatch *watch, NSError *error) { + if(error) { + NSLog(@"Pebble: Failed setting label to 'speed'"); + } else { + NSLog(@"Pebble: Set label to 'speed'"); + } + }]; + [_targetWatch sportsAppSetMetric:NO onSent:^(PBWatch *watch, NSError *error) { + if(error) { + NSLog(@"Pebble: Failed to set units to imperial"); + } else { + NSLog(@"Pebble: Set units to imperial"); + } + }]; + [_targetWatch sportsAppLaunch:^(PBWatch *watch, NSError *error) { + if(error) { + NSLog(@"Pebble: Failed sending launch command"); + } else { + NSLog(@"Pebble: launch command sent"); + } + }]; +} + +- (void)stopWatchSession { + if(!sportsEnabled) return; + + [_targetWatch sportsAppKill:^(PBWatch *watch, NSError *error) { + if(error) { + NSLog(@"Pebble: Failed to kill session"); + } else { + NSLog(@"Pebble: Successfully killed session"); + } + }]; + [_targetWatch releaseSharedSession]; +} + +- (void)refreshWatchface { + GLManager *m = [GLManager sharedManager]; + [self updateTripInfoWithTime:m.currentTripDuration distance:m.currentTripDistance*MetersToMiles speed:m.currentTripSpeed]; +} + +- (void)updateTripInfoWithTime:(NSTimeInterval)time distance:(double)distance speed:(double)speed { + NSDictionary *pebbleDict = @{ + PBSportsTimeKey: [PBSportsUpdate timeStringFromFloat:time], + PBSportsDistanceKey: [NSString stringWithFormat:@"%2.02f", distance], + PBSportsDataKey: [NSString stringWithFormat:@"%2.02f", speed] + }; + [_targetWatch sportsAppUpdate:pebbleDict onSent:^(PBWatch *watch, NSError *error) { + if(error) { + NSLog(@"Pebble: Failed to send update"); + } else { + } + }]; +} + + +#pragma mark - + +- (void)setTargetWatch:(PBWatch*)watch { + _targetWatch = watch; + + // Test if the Pebble's firmware supports AppMessages / Sports: + [watch appMessagesGetIsSupported:^(PBWatch *watch, BOOL isAppMessagesSupported) { + if (isAppMessagesSupported) { + [[PBPebbleCentral defaultCentral] setAppUUID:PBSportsUUID]; + + NSLog(@"Pebble: %@ supports AppMessages :D", [watch name]); + sportsEnabled = YES; + + [self configureWatchSession]; + } else { + + NSLog(@"Pebble: %@ does NOT support AppMessages :'(", [watch name]); + sportsEnabled = NO; + } + }]; +} + +#pragma mark - Pebble delegate + +- (void)pebbleCentral:(PBPebbleCentral*)central watchDidConnect:(PBWatch*)watch isNew:(BOOL)isNew { + NSLog(@"Pebble: watch %@ connected", watch.name); + [self setTargetWatch:watch]; +} + +- (void)pebbleCentral:(PBPebbleCentral*)central watchDidDisconnect:(PBWatch*)watch { + NSLog(@"Pebble: Watch %@ disconnected", watch.name); + if (_targetWatch == watch || [watch isEqual:_targetWatch]) { + [self setTargetWatch:nil]; + } +} + +@end diff --git a/Podfile b/Podfile index c76308a..cfa51b9 100644 --- a/Podfile +++ b/Podfile @@ -4,5 +4,6 @@ target 'GPSLogger' do pod 'AFNetworking' pod 'FMDB' +pod 'PebbleKit' end diff --git a/Podfile.lock b/Podfile.lock index dd4c256..739905e 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -25,13 +25,16 @@ PODS: - FMDB/common (2.5) - FMDB/standard (2.5): - FMDB/common + - PebbleKit (3.0.0) DEPENDENCIES: - AFNetworking - FMDB + - PebbleKit SPEC CHECKSUMS: AFNetworking: 79f7eb1a0fcfa7beb409332b2ca49afe9ce53b05 FMDB: 96e8f1bcc1329e269330f99770ad4285d9003e52 + PebbleKit: 575087dc3e7ca1687929dd4903baabcf8f224cff COCOAPODS: 0.38.2