/* * Copyright (c) 2002 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Copyright (c) 2002 Apple Computer, Inc. All rights reserved. * * HISTORY * * 29-Aug-02 ebold created * */ #include #include #include #include #include #include #include #include "BatteryTimeRemaining.h" /**** PMUBattery configd plugin The functions in battery.c calculate the "meta-data" from the raw data available from the hardware. We provide the following information in a convenient CFDictionary format: Name CurrentCapacity MaxCapacity Remaining Time To Empty Remaining Time To Full Charge IsCharging IsPresent Type ****/ #define MAX_BATTERY_NUM 2 #define kMaxBatterySamples 30 #define kMINIMUM_TIME_SPAN 30.0 // static global variables for tracking battery state static int _batCount; static int _batLevel[MAX_BATTERY_NUM]; static int _flags[MAX_BATTERY_NUM]; static int _charge[MAX_BATTERY_NUM]; static int _capacity[MAX_BATTERY_NUM]; static CFStringRef _batName[MAX_BATTERY_NUM]; static double _hoursRemaining[MAX_BATTERY_NUM]; static int _state; static int _pmExists; static int _pluggedIn, _lastpluggedIn; static int _impendingSleep = 0; typedef struct { CFAbsoluteTime bTime; long bLevel[MAX_BATTERY_NUM]; } batterySample; static CFAbsoluteTime sample_time_span = 0.0; static int samples_taken = 0; static int battery_history_index = 0; static int battery_history_start = 0; static batterySample battery_history[kMaxBatterySamples]; static CFStringRef PMUBatteryDynamicStore[MAX_BATTERY_NUM]; // forward declarations static void _initializeBatteryCalculations(void); static void _stashBatteryInfo(CFArrayRef); static bool _calculateBatteryTimeRemaining(void); static bool _checkCalcsStillValid(int); static void _packageBatteryInfo(bool, CFDictionaryRef *); static int _isBatteryPresent(int i) { return ((_flags[i] & kIOBatteryInstalled)?1:0); } static int _isACAdapterConnected(int i) { return ((_flags[i] & kIOBatteryChargerConnect)?1:0); } static int _isChargingBit(int i) { return ((_flags[i] & kIOBatteryCharge)?1:0); } // _isCharging does some magic to decide if the battery is REALLY charging static int _isCharging(int i) { return ( _isChargingBit(i) && _isBatteryPresent(i) && _isACAdapterConnected(i) ); } __private_extern__ void BatteryTimeRemaining_prime(void) { // Initialize SCDynamicStore battery key names PMUBatteryDynamicStore[0] = SCDynamicStoreKeyCreate(kCFAllocatorDefault, CFSTR("%@%@/%@"), kSCDynamicStoreDomainState, CFSTR(kIOPSDynamicStorePath), CFSTR("InternalBattery-0")); PMUBatteryDynamicStore[1] = SCDynamicStoreKeyCreate(kCFAllocatorDefault, CFSTR("%@%@/%@"), kSCDynamicStoreDomainState, CFSTR(kIOPSDynamicStorePath), CFSTR("InternalBattery-1")); // setup battery calculation global variables _initializeBatteryCalculations(); return; } __private_extern__ void BatteryTimeRemainingSleepWakeNotification(natural_t messageType) { switch ( messageType ) { case kIOMessageSystemWillSleep: // System is going to sleep - reset time remaining calculations. // Battery drain during sleep will produce an unrealistic time remaining // expectation on wake from sleep unless we reset the average sample. _impendingSleep = 1; break; case kIOMessageSystemHasPoweredOn: _impendingSleep = 0; break; } } static void _initializeBatteryCalculations(void) { io_connect_t pm_tmp; CFArrayRef info; int i; for(i=0; i < kMaxBatterySamples; i++) { battery_history[i].bTime = 0; battery_history[i].bLevel[0] = battery_history[i].bLevel[1] = 0; } _batName[0] = CFSTR("InternalBattery-0"); _batName[1] = CFSTR("InternalBattery-1"); if( (pm_tmp = IOPMFindPowerManagement(0)) ){ if((IOPMCopyBatteryInfo(0, &info) != 0) || !info ){ // no batteries detected _pmExists = 0; return; } // Batteries detected, get their initial state _batCount = CFArrayGetCount(info); for(i = 0;i < _batCount;i++){ CFNumberGetValue(CFDictionaryGetValue(CFArrayGetValueAtIndex((CFArrayRef)info,i), CFSTR("Current")),kCFNumberSInt32Type,&_charge[i]); _batLevel[i] = _charge[i]; CFNumberGetValue(CFDictionaryGetValue(CFArrayGetValueAtIndex((CFArrayRef)info,i), CFSTR("Flags")),kCFNumberSInt32Type,&_flags[i]); _flags[i] &= kIOBatteryInstalled; if(_flags[i] & kIOBatteryChargerConnect) _lastpluggedIn = _pluggedIn = TRUE; else _lastpluggedIn = _pluggedIn = FALSE; } _pmExists = 1; // make initial call to populate array and publish state BatteryTimeRemainingBatteriesHaveChanged(info); CFRelease(info); IOObjectRelease(pm_tmp); } // _state tracks the battery/plug state. If it changes, we know to reset our calculations. // _state = 0 means there are no batteries // _state = 1 means we are hooked up to AC/charger power, but not necessarily charging // _state = 2 means we aren't hooked up to AC power, and are d_isCharging if(_isACAdapterConnected(0)) _state = 1; else if(!_isBatteryPresent(0) && !_isBatteryPresent(1)) _state = 0; else _state = 2; samples_taken=0; return; } __private_extern__ void BatteryTimeRemainingBatteriesHaveChanged(CFArrayRef battery_info) { CFDictionaryRef result[MAX_BATTERY_NUM] = {NULL, NULL}; int i; int still_calculating; int not_enough_samples; static SCDynamicStoreRef store = NULL; static CFDictionaryRef old_battery[MAX_BATTERY_NUM] = {NULL, NULL}; if(!battery_info) return; #if DEBUGLEVEL>50 printf("battery sample taken!\n"); fflush(stdout); #endif _stashBatteryInfo(battery_info); not_enough_samples = !_calculateBatteryTimeRemaining(); still_calculating = !_checkCalcsStillValid(not_enough_samples); _packageBatteryInfo(still_calculating, result); // Publish the results of calculation in the SCDynamicStore if(!store) store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("PMUBattery configd plugin"), NULL, NULL); for(i=0; i _capacity[i]) _charge[i] = _capacity[i]; } if (!_isBatteryPresent(0)) { _charge[0] = 0; _batLevel[0] = 0; } if (!_isBatteryPresent(1)) { _charge[1] = 0; _batLevel[1] = 0; } // Stash state into battery_history array battery_history_index = (battery_history_index+1) % kMaxBatterySamples; battery_history[battery_history_index].bTime = CFAbsoluteTimeGetCurrent(); battery_history[battery_history_index].bLevel[0] = _charge[0]; battery_history[battery_history_index].bLevel[1] = _charge[1]; if(samples_taken < kMaxBatterySamples) { // expect battery_history_start == 0 samples_taken++; } else { // expect samples_taken == kMaxBatterySamples battery_history_start = (battery_history_index+1)%kMaxBatterySamples; } sample_time_span = battery_history[battery_history_index].bTime - battery_history[battery_history_start].bTime; } /* _calculateBatteryTimeRemaining * returns true if we reached a valid estimate * returns false if we're still calculating */ bool _calculateBatteryTimeRemaining(void) { int i; CFAbsoluteTime time_span = 0.0; int samples_counted; int early_index, late_index; double discharge_rate[MAX_BATTERY_NUM]; long int batt_level_delta[MAX_BATTERY_NUM]; if(samples_taken <= 1) { // Not enough data to go from. Return. return false; // return "not enough data" } #if DEBUGLEVEL>50 syslog(LOG_INFO, "global_span=%d\n", lround(sample_time_span)); syslog(LOG_INFO, "samples_taken=%d, battery_history_start=%d, battery_history_index=%d\n", samples_taken, battery_history_start, battery_history_index); #endif samples_counted = 0; discharge_rate[0] = discharge_rate[1] = 0.0; early_index = late_index = battery_history_index; time_span = 0.0; // Sum up the lats few samples to determine the battery discharge rate. while( (time_span < 300.0) && (early_index != battery_history_start) ) { // decrement early end of our range early_index = (early_index - 1 + kMaxBatterySamples) % kMaxBatterySamples; time_span = battery_history[late_index].bTime - battery_history[early_index].bTime; samples_counted++; } if(time_span < kMINIMUM_TIME_SPAN) { // Not enough data to go from. return false; // return "not enough data" } #if DEBUGLEVEL>50 syslog(LOG_INFO, "loop exit conditions (time_span=%d<300.0)=%d, samples_counted=%d, samples_taken=%d\n", lround(time_span), (time_span<300.0), samples_counted, samples_taken); syslog(LOG_INFO, "early_index = %d, late_index = %d\n", early_index, late_index); #endif for(i = 0; i50 syslog(LOG_INFO, " (%d) starting level = %d, ending level = %d, over %d seconds\n", i, battery_history[early_index].bLevel[i], battery_history[late_index].bLevel[i], lround(time_span)); #endif batt_level_delta[i] = battery_history[early_index].bLevel[i] - battery_history[late_index].bLevel[i]; discharge_rate[i] = ((double)batt_level_delta[i])/(double)lround(time_span); } #if DEBUGLEVEL>50 syslog(LOG_INFO, "discharge_rate = %ld/%d = %f\n", batt_level_delta[0], lround(time_span), discharge_rate[0]); #endif // Use the charge or discharge rate calculated above, combined with the current battery // level, to estimate the time remaining on this set of batteries. if ((_flags[0] & kIOBatteryChargerConnect) || (_flags[1] & kIOBatteryChargerConnect)) { double discharge_rate_combined; int toCharge[MAX_BATTERY_NUM]; toCharge[0] = (((_flags[0] & 0x4) == 4)?_capacity[0]:0) - _charge[0]; toCharge[1] = (((_flags[1] & 0x4) == 4)?_capacity[1]:0) - _charge[1]; if (_state != 1) { resetBatterySamples(); } _state = 1; if (discharge_rate[0] > 0) { discharge_rate[0] = 0; } if (discharge_rate[1] > 0) { discharge_rate[1] = 0; } discharge_rate_combined = ((double)discharge_rate[0]+(double)discharge_rate[1]); if(discharge_rate_combined != 0.0) { _hoursRemaining[0] = -1*(((double)toCharge[0])/discharge_rate_combined) / 3600; _hoursRemaining[1] = -1*(((double)toCharge[1])/discharge_rate_combined) / 3600; } else { _hoursRemaining[0] = _hoursRemaining[1] = -1.0; } } else if (!_isBatteryPresent(0) && !_isBatteryPresent(1)) { //When there are no batteries installed //if there were batteries installed before hand, then reset the history (so we get //a fresh buffer when a battery is installed) if (_state) { resetBatterySamples(); } //_state == 0 means that there are no batteries _state = 0; } else { double discharge_rate_combined; if (discharge_rate[0] < 0) { discharge_rate[0] = 0; } if (discharge_rate[1] < 0) { discharge_rate[1] = 0; } //if We just switched states (plugged to not), then start the charge array from scratch if (_state != 2) { resetBatterySamples(); } _state = 2; discharge_rate_combined = ((double)discharge_rate[0]+(double)discharge_rate[1]); if(discharge_rate_combined != 0.0) { _hoursRemaining[0] = (((double)_charge[0])/discharge_rate_combined) / 3600; _hoursRemaining[1] = (((double)_charge[1])/discharge_rate_combined) / 3600; } else { _hoursRemaining[0] = _hoursRemaining[1] = -1.0; } } #if DEBUGLEVEL>50 syslog(LOG_INFO, "remaining [0] = %d:%2d, [1] = %d:%2d\n", lround(_hoursRemaining[0]*60.0), lround(_hoursRemaining[0]*60.0)%60, lround(_hoursRemaining[1]*60.0)); #endif _batLevel[0] = _charge[0]; _batLevel[1] = _charge[1]; return true; } /* _calculateBatteryTimeRemaining * returns true if we reached a valid estimate * returns false if we're still calculating */ bool _checkCalcsStillValid(int not_enough_battery_samples) { static int old_num_present = -1; int num_present = _isBatteryPresent(0) + _isBatteryPresent(1); bool calcsValid = true; int ehours = 0; int eminutes = 0; int i; // If power source has changed, reset history count and restart battery sampling // triggers stillCalc = 1 if(_pluggedIn != _lastpluggedIn) { resetBatterySamples(); _lastpluggedIn = _pluggedIn; return false; } // System will sleep soon. Re-calculate time remaining from scratch. // Keep counts wired to 0 until _impendingSleep is non-NULL // We set _impendingSleep=0 when the system is fully awake. if(_impendingSleep) { resetBatterySamples(); _state = -1; return false; } // is the calculated time reasonable? (0 <= hours < 10) && ( ehours = (int)(_hoursRemaining[0] + _hoursRemaining[1]); eminutes = (int)(60.0*(_hoursRemaining[0] + _hoursRemaining[1] - (double)ehours)); if (!((ehours < 10) && (ehours >= 0)) && ((eminutes < 61) && (eminutes >= 0))) { return false; } // 2 batteries to 1? or 1 to 2? or 0 to 1? if(num_present != old_num_present) { resetBatterySamples(); old_num_present = num_present; return false; } old_num_present = num_present; // If we're plugged-in but not charging, we're fully charged and there's nothing // to calculate. Set stillCalc to 0. if( (_isACAdapterConnected(0) && !_isCharging(0)) || (_isACAdapterConnected(1) && !_isCharging(1)) ); { return true; } return !not_enough_battery_samples; } void _packageBatteryInfo(bool calc_still_calc, CFDictionaryRef *ret) { CFNumberRef n, n0, nneg1; CFMutableDictionaryRef mutDict = NULL; int i; int temp; int minutes; int set_capacity, set_charge; bool stillCalc = calc_still_calc; // Stuff battery info into CFDictionaries for(i=0; i<_batCount; i++) { // Create the battery info dictionary mutDict = NULL; mutDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if(!mutDict) return; // Set transport type to "Internal" CFDictionarySetValue(mutDict, CFSTR(kIOPSTransportTypeKey), CFSTR(kIOPSInternalType)); // Set Power Source State to AC/Battery CFDictionarySetValue(mutDict, CFSTR(kIOPSPowerSourceStateKey), _pluggedIn ? CFSTR(kIOPSACPowerValue):CFSTR(kIOPSBatteryPowerValue)); // round charge and capacity down to a % scale if(0 != _capacity[i]) { set_capacity = 100; set_charge = (int)((double)_charge[i]*100.0/(double)_capacity[i]); } else { // Bad battery or bad reading => 0 capacity set_capacity = set_charge = 0; } // Set maximum capacity n = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &set_capacity); if(n) { CFDictionarySetValue(mutDict, CFSTR(kIOPSMaxCapacityKey), n); CFRelease(n); } // Set current charge n = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &set_charge); if(n) { CFDictionarySetValue(mutDict, CFSTR(kIOPSCurrentCapacityKey), n); CFRelease(n); } // Set isPresent flag CFDictionarySetValue(mutDict, CFSTR(kIOPSIsPresentKey), (_isBatteryPresent(i)) ? kCFBooleanTrue:kCFBooleanFalse); // Set _isCharging and time remaining minutes = (int)(60.0*_hoursRemaining[i]); temp = 0; n0 = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &temp); temp = -1; nneg1 = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &temp); if( !_isBatteryPresent(i) ) { // remaining time calculations only have meaning if the battery is present CFDictionarySetValue(mutDict, CFSTR(kIOPSIsChargingKey), kCFBooleanFalse); CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToFullChargeKey), n0); CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToEmptyKey), n0); } else { // A battery is installed if(stillCalc) { // If we are still calculating then our time remaining // numbers aren't valid yet. Stuff with -1. CFDictionarySetValue(mutDict, CFSTR(kIOPSIsChargingKey), _isCharging(i) ? kCFBooleanTrue : kCFBooleanFalse); CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToFullChargeKey), nneg1); CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToEmptyKey), nneg1); } else { // else there IS a battery installed, and remaining time calculation makes sense if(_isCharging(i)) { // Set _isCharging to True CFDictionarySetValue(mutDict, CFSTR(kIOPSIsChargingKey), kCFBooleanTrue); n = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &minutes); if(n) { CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToFullChargeKey), n); CFRelease(n); } CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToEmptyKey), n0); } else { // Not Charging // Set _isCharging to False CFDictionarySetValue(mutDict, CFSTR(kIOPSIsChargingKey), kCFBooleanFalse); // But are we plugged in? if(_isACAdapterConnected(i)) { // plugged in but not charging == fully charged CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToFullChargeKey), n0); CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToEmptyKey), n0); } else { // not charging, not plugged in == d_isCharging n = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &minutes); if(n) { CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToEmptyKey), n); CFRelease(n); } CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToFullChargeKey), n0); } } } } CFRelease(n0); CFRelease(nneg1); // Set name CFDictionarySetValue(mutDict, CFSTR(kIOPSNameKey), _batName[i]); ret[i] = mutDict; } return; }