Mac OS X: Getting Power Source information

Some time ago, I started working on a small tool for OS X showing me various info regarding my battery. I still have to package it in some way and release it. But here are some stuff I’ve been using in this tool that might help others…

There are basically two sources of information regarding the battery / power source:

  • using the power source API: IOKit/ps
  • using the power management API: IOKit/pwr_mgt

For both you will need to add the IOKit framework to your project.

Let’s first have a look at what you can get out of the power source API. First you need to get a power source description for the battery:

    CFTypeRef blob = IOPSCopyPowerSourcesInfo();
    CFArrayRef sources = IOPSCopyPowerSourcesList(blob);
    if (CFArrayGetCount(sources) == 0)
        return;	// Could not retrieve battery information.  System may not have a battery.
    CFDictionaryRef pSource = IOPSGetPowerSourceDescription(blob, CFArrayGetValueAtIndex(sources, currentBattery));

You’ll have to replace currentBattery by the number of the battery you want to get info about (I use 0 for the first battery).

Now you have the power source description (which is basically just a dictionary containing all the info we want to get about the battery), you extract the info you need using GETVAL:

    const void *psValue;

    if (GETVAL(pSource, kIOPSNameKey, &psValue)) {
        name = (__bridge NSString *)psValue;
    }
    else {
        name = @"-";
    }
    
    if (GETVAL(pSource, kIOPSTypeKey, &psValue)) {
        type = (__bridge NSString *)psValue;
    }
    else {
        type = @"-";
    }
    
    if (GETVAL(pSource, kIOPSPowerSourceStateKey, &psValue)) {
        state = (__bridge NSString *)psValue;
    }
    else {
        state = @"-";
    }
    
    if (GETVAL(pSource, kIOPSHardwareSerialNumberKey, &psValue)) {
        serial = (__bridge NSString *)psValue;
    }
    else {
        serial = @"-";
    }
    
    if (GETVAL(pSource, kIOPSBatteryHealthKey, &psValue)) {
        health = (__bridge NSString *)psValue;
    }
    else {
        health = @"-";
    }
    
    if (GETVAL(pSource, kIOPSHealthConfidenceKey, &psValue)) {
        healthConfidence = (__bridge NSString *)psValue;
    }
    else {
        healthConfidence = @"-";
    }
    
    if (GETVAL(pSource, kIOPSBatteryHealthConditionKey, &psValue)) {
        healthCondition = (__bridge NSString *)psValue;
    }
    else {
        healthCondition = @"-";
    }
    
    if (GETVAL(pSource, kIOPSBatteryFailureModesKey, &psValue)) {
        failureModes = (__bridge NSArray *)psValue;
    }
    else {
        failureModes = [NSMutableArray array];
    }
    
    if (GETVAL(pSource, kIOPSVoltageKey, &psValue)) {
        CFNumberGetValue((CFNumberRef)psValue, kCFNumberIntType, &volt);
        voltage = [NSString stringWithFormat:@"%d mV", volt];
    }
    else {
        voltage = @"-";
    }
    
    if (GETVAL(pSource, kIOPSCurrentCapacityKey, &psValue)) {
        CFNumberGetValue((CFNumberRef)psValue, kCFNumberSInt32Type, &curCapacity);
    }
    
    if (GETVAL(pSource, kIOPSMaxCapacityKey, &psValue)) {
        CFNumberGetValue((CFNumberRef)psValue, kCFNumberSInt32Type, &maxCapacity);
    }
    
    if (GETVAL(pSource, kIOPSDesignCapacityKey, &psValue)) {
        CFNumberGetValue((CFNumberRef)psValue, kCFNumberSInt32Type, &desCapacity);
    }    
    
    if ((GETVAL(pSource, kIOPSIsChargingKey, &bval)) && (bval == kCFBooleanTrue)) {
        charge = 1;
    }
    else {
        charge = 0;
    }
    
    if ((GETVAL(pSource, kIOPSIsPresentKey, &bval)) && (bval == kCFBooleanFalse)) {
        present = 1;
    }
    else {
        present = 0;
    }
    
    if (STRMATCH(state, CFSTR(kIOPSACPowerValue))) {
        is_ac = 1;
    }
    else {
        is_ac = 0;
    }

If you’re interested in the time to empty the battery, you can get it this way:

    if (GETVAL(pSource, kIOPSTimeToEmptyKey, &numval)) {
        SInt32 val = -1;
        CFNumberGetValue(numval, kCFNumberSInt32Type, &val);
        
        if ((val <= 0) && (is_ac)) {
            timeToEmpty = @"-";
        }
        else if (val <= 0) {
            timeToEmpty = @"Calculating...";
        }
        else {
            timeToEmpty = [NSString stringWithFormat:@"%d:%02d", (int) val / 60, (int) val % 60 ];
        }
    }
    else {
        timeToEmpty = @"-";
    }

Note that if we’re on A/C (i.e. plugged in), 0 will be reported as time to empty (hence the first check). The time reported is in seconds so we use stringWithFormat to get something like 4:35 (4 minutes 35 seconds).

To get the time to full charge is pretty much the same thing:

    if (GETVAL(pSource, kIOPSTimeToFullChargeKey, &numval)) {
        SInt32 val = -1;
        CFNumberGetValue(numval, kCFNumberSInt32Type, &val);
        
        if ((val <= 0) && (!is_ac)) {
            timeToFullCharge = @"-";
        }
        else if (charge && val <= 0) {
            timeToFullCharge = @"Calculating...";
        }
        else if (val <= 0) {
            timeToFullCharge = @"Charged";
        }
        else {
            timeToFullCharge = [NSString stringWithFormat:@"%d:%02d", (int) val / 60, (int) val % 60 ];
        }
    }    
    else {
        timeToFullCharge = @"-";
    }

That’s it. If you’re interested in other kind of data like manufacturer, temperature, cycle counts, you’ll have to use the second API (the power management API) but it’s getting late and will become a second post on this topic…

Leave a Reply

Your email address will not be published. Required fields are marked *