Reading the battery level programmatically

A beta-tester was complaining the application was draining its battery. To check if this was really the case, I needed to get the battery level with a better accuracy than just looking at the battery icon. So I used this little routine to check.

This routine is adapted from a post on MacRumors.com.

It uses non-approved calls, so don’t post your application with this code or you may be rejected!

The method I used was:

  • Copy the IOPowerSources.h and IOPSKeys.h from the Simulator into my project
  • Add the libIOKit.A.dylib from the iPhone to my project

To get a copy of the libIOKit.A.dylib without jailbreaking my iPhone, I used FSWalker from Nicolas Seriot, a nifty utility which creates a web server on your iPhone and allows you to navigate your iPhone file system with a web browser.

So launch FSWalker, and with your browser go to http://192.168.2.100:20000/System/Library/Frameworks/IOKit.framework/Versions/A (replace the IP address with the address of your iPhone).
Download the IOKit file and add it to your project with the name « libIOKit.A.dylib ».

#include "IOPowerSources.h"
#include "IOPSKeys.h"

- (double) batteryLevel
{
  CFTypeRef blob = IOPSCopyPowerSourcesInfo();
  CFArrayRef sources = IOPSCopyPowerSourcesList(blob);
  
  CFDictionaryRef pSource = NULL;
  const void *psValue;
  
  int numOfSources = CFArrayGetCount(sources);
  if (numOfSources == 0) {
    NSLog(@"Error in CFArrayGetCount");
    return -1.0f;
  }
  
  for (int i = 0 ; i < numOfSources ; i++)
  {
    pSource = IOPSGetPowerSourceDescription(blob, CFArrayGetValueAtIndex(sources, i));
    if (!pSource) {
      NSLog(@"Error in IOPSGetPowerSourceDescription");
      return -1.0f;
    }
    psValue = (CFStringRef)CFDictionaryGetValue(pSource, CFSTR(kIOPSNameKey));
    
    int curCapacity = 0;
    int maxCapacity = 0;
    double percent;
    
    psValue = CFDictionaryGetValue(pSource, CFSTR(kIOPSCurrentCapacityKey));
    CFNumberGetValue((CFNumberRef)psValue, kCFNumberSInt32Type, &curCapacity);
    
    psValue = CFDictionaryGetValue(pSource, CFSTR(kIOPSMaxCapacityKey));
    CFNumberGetValue((CFNumberRef)psValue, kCFNumberSInt32Type, &maxCapacity);
    
    percent = ((double)curCapacity/(double)maxCapacity * 100.0f);
    
    return percent; 
  }
  return -1.0f;
}

Don't forget to remove the headers and libIOKit.A.dylib from your code before shipping!

20 réflexions au sujet de “Reading the battery level programmatically”

  1. Hi there,
    I was wondering if someone could help me. This stuff is good and everyone else seems to be able to get it to work, I however can not. I have added the necessary files to my project, but when I run the code I get three errors. Could someone please tell me where the actual code has to be inserted for it to work, and also anything else that has to be done. The specific errors I am getting can be posted if needed. As you can probably tell I am a beginner to iphone programming!

    Any help would be greatly appreciated!

  2. Stuart,

    Did you add the libIOKit.A.dylib to your project?

    Anyway, my post was pre-3.0 SDK, so you should use the batteryLevel method of the UIDevice class:

    float level = [[UIDevice currentDevice] batteryLevel];

    Before reading the battery level, you must enable the battery monitoring:

    [[UIDevice currentDevice] setBatteryMonitoringEnabled:YES];

    Hope this helps,

  3. Hi Stephan,
    I didn’t understand!

    « Don’t forget to remove the headers and libIOKit.A.dylib from your code before shipping! ».

    Why should I do that? How can I do that? Can you help me this issue?

    Thans!

  4. Just remove the libIOKit.A.dylib you’ve added to your project. Apple now has an automated tool to scan your code for private (ie not-approved methods) and will reject your app if it contains private methods.

  5. But, if I remove the libIOKit.A.dylib I can’t use this routine and its APIs. Am I right? Or Is there another way to read the battery level programmatically without using the SDK 3.0 APIs(5%)? If I can’t use your routine because Apple wouldn’t allow private APIs, do you know any other way to do that (using C language for instance)?

    Thanks for your help.

  6. Hello Stephan,
    Is it possible to get the values for curCapacity and maxCapacity using this method, and use them in different calculations? I have tried using them, but I get the error ‘undeclared’.

  7. Hello Stephan,
    How would I use a similar thing, but return maxCapacity and curCapacity individually rather than percent?

    Thanks in advance,
    Stu

  8. Stuart,

    Just change the definition of the method to:

    - (NSDictionary *) batteryData

    Then replace the

    return percent;
    by
    return [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:curCapacity], @"curCapacity", [NSNumber numberWithInt:maxCapacity], @"maxCapacity", nil];

    also replace the last line to

    return nil;

    That should do the trick.

  9. Thanks Stephan,

    I have done this but get the error « incompatible types in return » at the other two instances of return 1.0f; Should these be return nil; also?

    Before I used double level = [self batteryLevel]; when I was using your code to return a percent value. I then put the variable ‘level’ into the required label. How exactly would I put the value for curCapacity into a label now that NSDictionary is being used? I tried:

    double level = [self batteryData];
    curCapacity.text = [NSString stringWithFormat:@ »%.2f % », curCapacity];

    This did not work. Please note I changed:

    – (double) batteryLevel;

    In the .h file to:

    – (NSDictionary *) batteryData;

    Thanks once again for your help! I hope you can understand what I mean.
    Stuart

  10. Stuart,

    You are right, you have to change all « return -1.0f » by « return nil ».

    Better, you should replace all « return -1.0f » and the last line: « return nil » by:
    return [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:-1], @ »curCapacity », [NSNumber numberWithInt:-1], @ »maxCapacity », nil];;

    So in your code, you should do:

    int level = [[[self batteryData] objectForKey:@ »curCapacity »] intValue];
    curCapacity.text = [NSString stringWithFormat:@ »%d mAh », curCapacity];

    Note that level is now an int, not a float.

    HTH,
    Stephan

  11. AT command AT+CBC gives the battery level with 1 % accuracy
    But dont know if apple will allow it aur not . though I have implemented
    🙂

  12. you might want to CFRelease(blob) and CFRelease(sources)
    in all of the code paths to avoid leaking them. (thanks, Instruments!)

Laisser un commentaire