Tag: objective-c
Polling Battery percentage on iPhone
Introduction:
This article explores techniques on how to periodically poll/check battery percentage on an Apple iPhone device using X Code and objective-c.
Use case/Purpose:
I have three apple devices ( two iPhones and a MacBook Air ). Without having to physically locate each device, I want to be able to check the battery percentage of each of the devices.
Covered in this post:
This post covers the mechanics of how to poll for battery percentage on an iOS device, polling for battery percentage on a MacOs device is covered in another article:
https://nsaxelby.com/2018/05/10/polling-battery-percentage-on-macos/
Implementation (iOS):
Theory:
We want a background task to periodically check the battery percentage and do something with it such as post it to an external service ( external service not included in this post ). We want to know the status of the battery every 5 minutes which checks the battery level.
We should do this by using a NSTimer and using UIDevice to query the batteryLevel.
Step 1: Setting up a poller to output the battery percentage level to output
We can set this up by initialising the NSTimer from the didFinishLaunchingWithOtions method of the AppDelegate.m file.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[UIDevice currentDevice] setBatteryMonitoringEnabled:YES]; // For the purposes of testing, I have set the interval to 20 seconds [NSTimer scheduledTimerWithTimeInterval:20.0 target:self selector:@selector(UpdateBatteryStatus) userInfo:nil repeats:YES]; return YES; } - (void)UpdateBatteryStatus { UIDevice *myDevice = [UIDevice currentDevice]; float batteryPercentageLeft = [myDevice batteryLevel] * 100; NSLog(@"Battery Percentage: %f", batteryPercentageLeft); // TODO add additional code here to do whatever you want with the battery percentage }
If you run the application now, you will see the following in the output:
Note: if you are running the simulator ( as opposed to a real device ) the percentage will show as -100 because the simulator does not have a battery.
This is a good start, however, if the user minimises the application to the background ( press the home button on the device so that the app is not longer the ‘active’ app ), the NSTimer will stop. The use-case of this application was to be able to monitor the battery percentage of the device when the user does not have the device to hand. For this scenario, it is therefore necessary to allow the NSTimer to continue to poll the battery percentage even whilst the app is in the background.
Note: if you are running the simulator, you will see that the NSTimer continues polling in the background even when the app is in the background. The simulator works differently from the real device in this case.
This is where we hit a problem. After doing some research, I found Apple does not want an app to process in the background unless there is a very good reason for the app to do so. The full explanation on background execution modes is available here:
To summarise the page, apps that are allowed to periodically execute within the background:
- GPS type applications ( navigation apps )
- Music/VOIP apps ( music streaming, or communication apps such as Skype )
- Apps which talk to bluetooth devices ( headphones, etc )
There are other types of apps, but the frequency/control of the background execution of the other types in the list, are not suitable for this use case ( we want polling every 5 minutes ).
What does this mean? Basically, if you are planning on releasing a pure battery monitoring app ( pure as in; no other major functionality ) to the app store, you are likely to be unsuccessful with your submission.
In my case, I was not planning on releasing the app to the app store, so I had to find a technical workaround ( aka a hack ) in order for my NSTimer to be able to execute even when in the background.
Step 2: Find a workaround to allow background execution
Theory: Alter the app so that it has a background execution attribute, and then imitate that background execution mode to keep the app alive in the background.
Firstly we need to allow the background execution mode within the capabilities of the app, we would state within the build settings that this app is of a type of ‘Audio, AirPlay and Picture in Picture’ app. We enable Background Modes capabilities within the Targets of the app:
If we run the application now, you will see that it has made no difference, when the app resides to the background, the NSTimer stops working.
In order to keep the app alive in the background, we need to act like a regular audio player app. To do this, we will add an AVPlayer to the code, and have that player play a silent track on a continuous loop.
Within the AppDelegate.h import the AVFoundation library:
#import <AVFoundation/AVFoundation.h>
Add a AVPlayer as a property:
@property (nonatomic, strong) AVPlayer *player;
Now within the AppDelegate.m it’s time to setup the AVPlayer to begin playing a silent track upon app startup.
Put the following code into the didFinishLaunchingWithOptions method:
// Start playing silent file NSError *sessionError = nil; [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&sessionError]; AVPlayerItem *item = [AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:@"2-seconds-of-silence" withExtension:@"mp3"]]; [self setPlayer:[[AVPlayer alloc] initWithPlayerItem:item]]; [[self player] setActionAtItemEnd:AVPlayerActionAtItemEndNone]; [[self player] play];
You will notice that we referenced a resource with the name “2-seconds-of-silence” which has an extension of “.mp3” , this is the silent audio track that we need to go and obtain and import into the project before running the app.
Another key mention is the use of the withOptions:AVAudioSessionCategoryOptionMixWithOthers option that is used within the AVVideoSession . This option allows the silent track to continue playing and not affect other audio apps, the silent track will just mix into whatever other audio is playing, and given that the track we are playing is silent, it should have no affect on other audio output.
The complete didFinishLaunchingWithOptions will now look something like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[UIDevice currentDevice] setBatteryMonitoringEnabled:YES]; // Start playing silent file NSError *sessionError = nil; [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&sessionError]; AVPlayerItem *item = [AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:@"2-seconds-of-silence" withExtension:@"mp3"]]; [self setPlayer:[[AVPlayer alloc] initWithPlayerItem:item]]; [[self player] setActionAtItemEnd:AVPlayerActionAtItemEndNone]; [[self player] play]; // For the purposes of testing, I have set the interval to 20 seconds [NSTimer scheduledTimerWithTimeInterval:20.0 target:self selector:@selector(UpdateBatteryStatus) userInfo:nil repeats:YES]; return YES; }
Step 3: Obtain the silent audio track
I obtained my silent audio track from the follow GitHub repo:
https://github.com/anars/blank-audio
You can use pretty much any duration of silent audio track, it should not make a difference which one you choose, I chose the 2 second audio track.
Once downloaded, you simply have to drag and drop it into your project from your finder window, and drop it into the list of files within your project navigator:
In order to test the background/foreground execution, I put an additional couple of logs within AppDelegate.m into the background/foreground event handlers, so that I could check that execution was happening where it should do:
- (void)applicationDidEnterBackground:(UIApplication *)application { NSLog(@"App now in background"); }
- (void)applicationDidBecomeActive:(UIApplication *)application { NSLog(@"App now in foreground"); }
If we execute our application on a real device, and put the app into the background ( press home button ) it should now give output like this:
Full git source for the project available at GitHub:
https://github.com/nsaxelby/BatteryMonitorApp
MacOs application is explained in another post within my blog:
https://nsaxelby.com/2018/05/10/polling-battery-percentage-on-macos/
Out of interest, this app allowed me to build up a history of how my iPhone battery performs over time, example screenshot of data that I was able to obtain, ( I may post the source code for the full project at a later date ).
Polling battery percentage on MacOs
May 10, 2018
Programming
No Comments
Nick
This is an extension of my last post which covered polling battery percentage within iOS:
https://nsaxelby.com/2018/05/06/polling-battery-percentage-on-iphone/
This post is describes how to do it in MacOs
Introduction:
This post explores how to poll for the battery percentage on a MacOsx device. My MacOs device that I’m building on is a High Sierra MacBook Air ( 2014 ).
Implementation:
Fortunately querying the battery percentage on a MacOs device is much easier than on a iOS. You don’t have to worry about all of the ‘background capabilities’ and modes requires for the iOS implementation. We will use the same NSTimer to poll within the app every X seconds, but we will use IOPowerSources.h within IOKit to obtain the battery percentage.
Step 1:
Within your MacOs application AppDelegate.h ( or wherever you want to put it ) import the IOKit ‘s IOPowerSources :
Within your AppDelegate.m create the method that will get the battery percentage and do whatever you want to do with the percentage, add the following method:
Step 2:
Hook up your method of a NSTimer by putting the following code in your applicationDidFinishLaunching within the AppDelegate.m :
If you fire up your app, you will see the the debug output look something like this:
Full git source code sample application here:
https://github.com/nsaxelby/MacOsBatteryMonitorApp
applemacosobjective-cxcode