Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does CMMotionActivityManager not return any recent activity data?

Tags:

ios

iphone

I am working on an iPhone app that tracks how many steps a person takes and how much time they invest in doing that. The step counting is done using the CMStepCounter class. However, time stamps are only provided with live steps, and not with historical step data. So to check how much time has actually elapsed while walking, I am using CMMotionActivityManager.

I have two methods, one for live counting, and one for fetching historical data while the app was in the background. The live counting works well enough, but the method for fetching historical activity data does not! Whenever the app enters the background I save the current date (via NSUserDefaults), and when the app enters the foreground again, I use queryActivityStartingFromDate with the aforementioned date as the start date, and the current date as the end date. This way it should tell me about the activities that happened in the meantime when the app was suspended. But usually the array of activities turns up empty, even if there was motion in between. If I enter the start of day as the start date, I get a huge array of activities, but there is sometimes fluctuation in the exact amount of entries.

I get the feeling that there is a delay in the logging of activities. Could that be my problem? But then, even if I wait half an hour before resuming the app, correct logging of the activity that occurred in between is not guaranteed.

The method for fetching historical activity data is at the bottom of this post. It is called on launch and then every time the app gets back into the foreground. It's always called with

[self calculateHistoricalActivitySince: _enteredBackgroundAt];

_enteredBackground is an NSDate. On launch, a method is called to restore some NSUserDefaults, among them this date. Every time the app enters the background it is set to the then-current date. When restoring this date the method makes sure it’s from the same day – if it’s an older date (i.e. the app was last used days ago), then it’s set to the start of the current day (0am today). self.activityInSecondsToday is an NSInteger.

-(void)calculateHistoricalActivitySince: (NSDate *) date
{

[_motionActivityManager queryActivityStartingFromDate: date
                                               toDate: [NSDate date]
                                              toQueue: _activityQueue
                                          withHandler:^(NSArray *activities,
                                                        NSError *error)
 {
     if ([error code] == CMErrorUnknown)
     {
         NSLog(@"Motion Activity Manager Error: %@", error);
     }

     // Fill my activity array with historical data if it was walking or running
     else
     {
         NSLog(@"Historical Total Activities from %@ \rto %@:\r %i", date, [NSDate date], (int)[activities count]);

         // Create an array for all relevant activity data
         _activityStorage = [NSMutableArray new];

         for (int i = 0; i < [activities count]; i++)
         {
             if( [[activities objectAtIndex: i] walking] || [[activities objectAtIndex: i] running] || [[activities objectAtIndex: i] unknown])
                 [_activityStorage addObject: [activities objectAtIndex: i]];
         }

         // Calculate the time interval between each entry and increase activityInSecondsToday if the interval is not too large.
         for (int i = 1; i < [_activityStorage count]; i++)
         {
             NSTimeInterval timeInterval = [[[_activityStorage objectAtIndex: i] startDate] timeIntervalSinceDate:[[_activityStorage objectAtIndex: i-1] startDate]];

             if (timeInterval <= _maxTimeBetweenTimestampsForContinuedActivity)
                 self.activityInSecondsToday += timeInterval;

             NSLog(@"ACTIVITY SINCE: %@ \r %ld", date, _activityInSecondsToday);
         }
         NSLog(@"Last activity’s timestamp: %@", [[_activityStorage objectAtIndex: [_activityStorage count]-1] startDate]);

         [_activityStorage removeAllObjects];
     }
 }];

}

like image 289
willi Avatar asked Jan 25 '15 22:01

willi


1 Answers

So, it seems there isn't a lot of info on this topic on the web. But I've figured out a solution on my own which I'd like to share in case anyone wants to do something similar.

Since iOS 8, CMStepCounter has already been deprecated. There is now CMPedometer, so I've adopted that API instead. CMMotionActivityManager is not necessary for this anymore either. That makes it easier! The weird delay problem described above also doesn't happen with CMPedometer.

The problem is similar as before: for live-counting you get steps and timestamps (now masked as start and end dates, and everything is encapsulated in a container called pedometerData); but for historical data you get less precise data. The new API does give you start and end dates for historical data, which is great. But I've found out that the API is very lazy, so for example if you want the data from yesterday at dawn till yesterday at midnight, the start date would be the date of the first recognized activity and the end date of the last. Everything in-between would be one huge hours-long activity! It would not encapsulate the activities into distinct pedometerData objects with precise time stamps like it does with live-counting.

So my solution is polling the API for smaller time frames recursively. I have a start date in the recent past and I want all activity information (steps and the time spent walking) from then till now. So I take the time frame I want to cover and I split it into shorter intervals. I then recursively ask [CMPedometer queryPedometerDataFromDate] whether there were any steps in each interval. If so, I add those steps to my private step variable, and I add the activity time (endDate - startDate) to my private seconds variable. If the current interval is still in the past, I'll go to the next interval and do the same thing again, recursively.

This way you can get rather precise and reliable results. Of course, the smaller and more precise your intervals, the more often this method will be executed and the longer it takes. Thus I have three different interval sizes (one hour, one minute, 5 seconds) so that times without any activity can be skipped quicker. There is a lot of potential for optimizations, obviously.

like image 139
willi Avatar answered Oct 11 '22 00:10

willi