/* * Copyright (c) 2002 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.1 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This 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 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: Remaining Time To Empty Remaining Time To Full Charge IsCharging BatteryIsPresent Future work: - ApplePMU.kext should send notifications when the battery info has changed. This plugin should only check the battery on each of those notifications. Right now the plugin checks the battery every second so that we can observe AC plug/unplug events in "real time" and present them to the UI (in Battery Monitor). - This plugin should also compare its new battery info to the most recently published battery info to avoid unnecessarily duplicated work of clients. Note that whenever we publish a new battery state, every "interested client" of the PowerSource API receives a notification. ****/ #define MAX_BATTERY_NUM 2 #define kBATTERY_HISTORY 30 // static global variables for tracking battery state 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 _batteryHistory[MAX_BATTERY_NUM][kBATTERY_HISTORY]; static int _historyCount,_historyLimit; static double _hoursRemaining[MAX_BATTERY_NUM]; static int _state; static int _pmExists; static unsigned int _avgCount; static mach_port_t _batteryPort; static int _pluggedIn, _lastpluggedIn; static int _charging; static int _impendingSleep = 0; static CFStringRef PMUBatteryDynamicStore[MAX_BATTERY_NUM]; // These are defined below static void calculateTimeRemaining(void); static bool _IOPMCalculateBatteryInfo(CFArrayRef, CFDictionaryRef *); static void _IOPMCalculateBatterySetup(void); __private_extern__ void BatteryTimeRemaining_prime(void) { // setup battery calculation global variables _IOPMCalculateBatterySetup(); // 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")); 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; } } __private_extern__ void BatteryTimeRemainingBatteryPollingTimer(CFArrayRef battery_info) { CFDictionaryRef result[MAX_BATTERY_NUM]; int i; static SCDynamicStoreRef store = NULL; static CFDictionaryRef old_battery[MAX_BATTERY_NUM] = {NULL, NULL}; if(!battery_info) return; if(!store) store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("PMUBattery configd plugin"), NULL, NULL); bzero(result, MAX_BATTERY_NUM*sizeof(CFDictionaryRef)); _IOPMCalculateBatteryInfo(battery_info, result); // Publish the results of calculation in the SCDynamicStore for(i=0; i _capacity[i]) _charge[i] = _capacity[i]; } // Calculate time remaining per battery if((_avgCount % 10) == 0){ calculateTimeRemaining(); ehours = (int)(_hoursRemaining[0] + _hoursRemaining[1]); eminutes = (int)(60.0*(_hoursRemaining[0] + _hoursRemaining[1] - (double)ehours)); } _avgCount++; // If power source has changed, reset history count and restart battery sampling // triggers stillCalc = 1 if(_pluggedIn != _lastpluggedIn) { _historyCount = 0; _historyLimit = 0; _lastpluggedIn = _pluggedIn; } // 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) { _historyCount = 0; _historyLimit = 0; _state = -1; } // Determine if we have a good time remaining calculation yet. If so, set stillCalc = 0 if(!_historyLimit && (_historyCount < 1)){ stillCalc = 1; } // is the calculated time valid? (0 <= hours < 10) && ( if (!((ehours < 10) && (ehours >= 0)) && ((eminutes < 61) && (eminutes >= 0))) { stillCalc = 1; } // Stuff battery info into CFDictionaries for(i=0; i 0.95 && isACAdapterConnected(i)) { // Fully charged! CFDictionarySetValue(mutDict, CFSTR(kIOPSIsChargingKey), kCFBooleanFalse); CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToEmptyKey), n0); CFDictionarySetValue(mutDict, CFSTR(kIOPSTimeToFullChargeKey), n0); } else if(stillCalc) { // If we are still calculating then our time remaining // numbers aren't valid yet. Stuff with -1. CFDictionarySetValue(mutDict, CFSTR(kIOPSIsChargingKey), _charging ? 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(_charging) { // 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 { // Set isCharging to False CFDictionarySetValue(mutDict, CFSTR(kIOPSIsChargingKey), kCFBooleanFalse); 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 TRUE; } static void calculateTimeRemaining(void) { int cnt; float deltaAvg[2]; if (!((_flags[0] & 0x4) == 4)) { _charge[0] = 0; _batLevel[0] = 0; } if (!((_flags[1] & 0x4) == 4)) { _charge[1] = 0; _batLevel[1] = 0; } //Put new level into history (both batteries) _batteryHistory[0][_historyCount] = _batLevel[0] - _charge[0]; _batteryHistory[1][_historyCount] = _batLevel[1] - _charge[1]; //If these are both zero, discount them! if (!_batteryHistory[0][_historyCount] && !_batteryHistory[1][_historyCount] ) { return; } //Reset the delta values deltaAvg[0] = deltaAvg[1] = 0; //See if we have hit the historyLimit...basically, we want to make sure //the whole array is filled, so we get an accurate avg if (_historyLimit) { for(cnt = 0;cnt < kBATTERY_HISTORY;cnt++) { deltaAvg[0] += _batteryHistory[0][cnt]; deltaAvg[1] += _batteryHistory[1][cnt]; } deltaAvg[0] = deltaAvg[0]/kBATTERY_HISTORY; deltaAvg[1] = deltaAvg[1]/kBATTERY_HISTORY; } else { for(cnt = 0;cnt <= _historyCount;cnt++) { deltaAvg[0] += _batteryHistory[0][cnt]; deltaAvg[1] += _batteryHistory[1][cnt]; } deltaAvg[0] = deltaAvg[0]/(_historyCount+1); deltaAvg[1] = deltaAvg[1]/(_historyCount+1); } //Reset the history counter (loop around in the buffer) if (_historyCount >= kBATTERY_HISTORY-1) { _historyCount = 0; _historyLimit++; } else { //or just increment _historyCount++; } if ((_flags[0] & kIOBatteryChargerConnect) || (_flags[1] & kIOBatteryChargerConnect)) { int toCharge[2]; 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) { _historyCount = _historyLimit = 0; } _state = 1; if (deltaAvg[0] > 0) { deltaAvg[0] = 0; } if (deltaAvg[1] > 0) { deltaAvg[1] = 0; } //_hoursRemaining[0] = -1*((((double)toCharge[0]+(double)toCharge[1]))/((double)deltaAvg[0]+(double)deltaAvg[1]))/360; _hoursRemaining[0] = -1*(((double)toCharge[0])/((double)deltaAvg[0]+(double)deltaAvg[1]))/360; _hoursRemaining[1] = -1*(((double)toCharge[1])/((double)deltaAvg[0]+(double)deltaAvg[1]))/360; } 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) { _historyCount = _historyLimit = 0; } //_state == 0 means that there are no batteries _state = 0; } else { if (deltaAvg[0] < 0) { deltaAvg[0] = 0; } if (deltaAvg[1] < 0) { deltaAvg[1] = 0; } //if We just switched states (plugged to not), then start the charge array from scratch if (_state != 2) { _historyCount = _historyLimit = 0; } _state = 2; //_hoursRemaining[0] = ((((double)_charge[0]+(double)_charge[1]))/((double)deltaAvg[0]+(double)deltaAvg[1]))/360; _hoursRemaining[0] = (((double)_charge[0])/((double)deltaAvg[0]+(double)deltaAvg[1]))/360; _hoursRemaining[1] = (((double)_charge[1])/((double)deltaAvg[0]+(double)deltaAvg[1]))/360; } _batLevel[0] = _charge[0]; _batLevel[1] = _charge[1]; }