// IBAM, the Intelligent Battery Monitor // Copyright (C) 2001-2003, Sebastian Ritterbusch (IBAM@Ritterbusch.de) // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // #include #include #include #include #include #include #include #include #include #include // thus I may prevent evil krells to change to others.. #include // for mkdir #include // for mkdir #define PMU_PWR_AC_PRESENT 0x00000001 /* Arguments, with symbols from linux/apm_bios.h. Information is + from the Get Power Status (0x0a) call unless otherwise noted. + + 0) Linux driver version (this will change if format changes) + 1) APM BIOS Version. Usually 1.0 or 1.1. + 2) APM flags from APM Installation Check (0x00): + bit 0: APM_16_BIT_SUPPORT + bit 1: APM_32_BIT_SUPPORT + bit 2: APM_IDLE_SLOWS_CLOCK + bit 3: APM_BIOS_DISABLED + bit 4: APM_BIOS_DISENGAGED + 3) AC line status + 0x00: Off-line + 0x01: On-line + 0x02: On backup power (APM BIOS 1.1 only) + 0xff: Unknown + 4) Battery status + 0x00: High + 0x01: Low + 0x02: Critical + 0x03: Charging + 0xff: Unknown + 5) Battery flag + bit 0: High + bit 1: Low + bit 2: Critical + bit 3: Charging + bit 7: No system battery + 0xff: Unknown + 6) Remaining battery life (percentage of charge): + 0-100: valid + -1: Unknown + 7) Remaining battery life (time units): + Number of remaining minutes or seconds + -1: Unknown + 8) min = minutes; sec = seconds */ inline int battery_status::onBattery(void) const { return acLineStatus==0; } inline int battery_status::charging(void) const { return chargeStatus; } inline int battery_status::percent(void) const { return remainingBatteryPercent; } inline int battery_status::seconds(void) const { return remainingBatteryLifeSeconds; } inline battery_status::battery_status(string path) { Path = path; } inline battery_status::~battery_status(void) { } inline void battery_status::update(void) { cout << "battery_status::update() called. This should never happen!" << endl; } inline apm_status::apm_status(string path) : battery_status(path) { update(); } inline pmu_status::pmu_status(string path) : battery_status(path) { update(); } inline acpi_status::acpi_status(string path) : battery_status(path) { update(); } inline void apm_status::update(void) { ifstream in; int i; in.open(Path.c_str()); for(i=0;i<10 && in.fail();i++) in.open(Path.c_str()); if(in.fail()) { acLineStatus=0; batteryStatus=0; remainingBatteryPercent=-1; remainingBatteryLifeSeconds=-1; return; } string driverVersion, biosVersion; int apmFlags, batteryFlag; char c,d; in >> driverVersion; in >> biosVersion; in >> c >> d; // 0x in >> c >> d; apmFlags=(c>'9'?c-'a'+10:c-'0')*16+(d>'9'?d-'a'+10:d-'0'); in >> c >> d; // 0x in >> c >> d; acLineStatus=(c>'9'?c-'a'+10:c-'0')*16+(d>'9'?d-'a'+10:d-'0'); in >> c >> d; // 0x in >> c >> d; batteryStatus=(c>'9'?c-'a'+10:c-'0')*16+(d>'9'?d-'a'+10:d-'0'); in >> c >> d; // 0x in >> c >> d; batteryFlag=(c>'9'?c-'a'+10:c-'0')*16+(d>'9'?d-'a'+10:d-'0'); chargeStatus = (batteryStatus&8)!=0; in >> remainingBatteryPercent >> c; // % string minsec; in >> remainingBatteryLifeSeconds >> minsec; if(minsec=="min") remainingBatteryLifeSeconds*=60; #ifdef DEBUG cout << "Driver Version: " << driverVersion << endl; cout << "Bios Version: " << biosVersion << endl; cout << "APM Flags: " << apmFlags << endl; cout << "AC Line Status: " << acLineStatus << endl; cout << "Battery Status: " << batteryStatus << endl; cout << "Battery Flag: " << batteryFlag << endl; cout << "Remaining Percent: " << remainingBatteryPercent << endl; cout << "Remaining Seconds: " << remainingBatteryLifeSeconds << endl; #endif } inline void pmu_status::update(void) { ifstream in; int i; in.open((Path+"/info").c_str()); for (i = 0; i < 10 && in.fail(); i++) in.open((Path+"/info").c_str()); if (in.fail()) { acLineStatus = 0; chargeStatus = 0; remainingBatteryLifeSeconds = -1; remainingBatteryPercent = -1; return; } stringbuf buf; char c; int d, cur_charge = 0, max_charge = 0; for (i = 0; i < 4; i++) { in.get(buf, ':'); in >> c >> d; if (i == 2) acLineStatus = d; } in.close(); in.open((Path+"/battery_0").c_str()); for (i = 0; i < 10 && in.fail(); i++) in.open((Path+"/battery_0").c_str()); if (in.fail()) { acLineStatus = 0; chargeStatus = 0; remainingBatteryLifeSeconds = -1; remainingBatteryPercent = -1; return; } for (i = 0; i < 6; i++) { in.get(buf, ':'); in >> c >> d; if (i == 0) chargeStatus = (d&PMU_PWR_AC_PRESENT)==0; if (i == 1) cur_charge = d; if (i == 2) max_charge = d; if (i == 5) remainingBatteryLifeSeconds = d; } remainingBatteryPercent = (int)(cur_charge*100/max_charge); } inline void acpi_status::update(void) { } inline void percent_data::size_to(int newpercents) { if(newpercents>=maxpercents) { newpercents++; double *time_for=new double[newpercents]; double *time_deriv=new double[newpercents]; int *samples=new int[newpercents]; int i; for(i=0;i=0;i--) if(a.time_samples[i]) { if(a.time_deriv_for_percent[i]<0) a.time_deriv_for_percent[i]=0; o << i << '\t' << a.time_for_percent[i] << '\t' << sqrt(a.time_deriv_for_percent[i]) << '\t' << a.time_samples[i] << endl; } return o; } inline double percent_data::add_data(int percent,double time_for,int samples) { if(percent<0) return 0; size_to(percent); double ratio; if(time_samples[percent]) ratio=time_for/time_for_percent[percent]; else ratio=time_for/(IBAM_ASSUME_DEFAULT_BATTERY_MIN*60./100.); double old_time_for_percent=time_for_percent[percent]; time_for_percent[percent]= (time_for_percent[percent]*time_samples[percent] +time_for*samples )/(samples+time_samples[percent]); time_deriv_for_percent[percent]= ( (time_deriv_for_percent[percent]+old_time_for_percent*old_time_for_percent)*time_samples[percent] +time_for*time_for*samples )/(samples+time_samples[percent])-time_for_percent[percent]*time_for_percent[percent]; time_samples[percent]+=samples; return ratio; } inline double percent_data::average(int a,int b) // average from a to b { if(a>b) { int c=a;a=b;b=c; } if(a<0) { a=0; if(b<0) b=0; } if(b>=maxpercents) { b=maxpercents-1; if(a>=maxpercents) a=b; } int i; double su(0); int co(0); for(i=a;i<=b;i++) { if(time_samples[i]) { su+=time_for_percent[i]*time_samples[i]; co+=time_samples[i]; } } if(co) return (su/co); int gotdata=0; for(a--,b++;(a>0 || b=maxpercents) b=maxpercents-1; su+=time_for_percent[a]*time_samples[a] +time_for_percent[b]*time_samples[b]; co+=time_samples[a]+time_samples[b]; if(time_samples[a] || time_samples[b]) gotdata++; } if(co) return (su/co); return (IBAM_ASSUME_DEFAULT_BATTERY_MIN*60/100); } inline double percent_data::add_data(int percent,double time_for,double time_deriv_for,int samples) { if(percent<0) return 0; size_to(percent); double ratio; if(time_samples[percent]) ratio=time_for/time_for_percent[percent]; else ratio=time_for/average(percent,percent); double old_time_for_percent=time_for_percent[percent]; time_for_percent[percent]= (time_for_percent[percent]*time_samples[percent] +time_for*samples )/(samples+time_samples[percent]); time_deriv_for_percent[percent]= ( (time_deriv_for_percent[percent]+old_time_for_percent*old_time_for_percent)*time_samples[percent] + (time_deriv_for+time_for*time_for)*samples )/(samples+time_samples[percent])-time_for_percent[percent]*time_for_percent[percent]; time_samples[percent]+=samples; return ratio; } inline istream & operator >>(istream & i,percent_data &a) { setlocale(LC_ALL,"en_US"); while(!i.fail() && !i.eof()) { int percent; double time_for(-1); double time_deriv_for(-1); int samples; i >> percent >> time_for >> time_deriv_for>> samples; if(time_for>=0) a.add_data(percent,time_for,time_deriv_for*time_deriv_for,samples); } return i; } inline istream & percent_data::import(istream & i) { setlocale(LC_ALL,"en_US"); percent_data & a(*this); double maxval=0; while(!i.fail() && !i.eof()) { int val; double time_for(-1); int samples; i >> val >> time_for >> samples; if(val>maxval) maxval=val; if(time_for>=0) a.add_data(int(double(val)/maxval*100+.5),time_for*maxval/100,samples/10+1); } return i; } inline double percent_data::remain(int percent) { double r=0; size_to(percent); int i; for(i=percent;i>0;i--) { if(time_samples[i]) r+=time_for_percent[i]; else { int down=i-15; int up=i+15; if(down<0) down=0; if(up>=maxpercents) up=maxpercents-1; r+=average(down,up); } } return r; } inline double percent_data::inverted_remain(int percent) { double r=0; size_to(percent); int i; for(i=percent+1;i=maxpercents) up=maxpercents-1; r+=average(down,up); } } return r; } inline double percent_data::total(void) { double r=0; int i; for(i=maxpercents-1;i>0;i--) { if(time_samples[i]) r+=time_for_percent[i]; else { int down=i-15; int up=i+15; if(down<0) down=0; if(up>=maxpercents) up=maxpercents-1; r+=average(down,up); } } return r; } inline ibam::ibam(void) : data_changed(0), battery_loaded(0),battery_changed(0), charge_loaded(0),charge_changed(0), profile_changed(0),adaptive_damping_battery(15), adaptive_damping_charge(15), lasttime(time(NULL)),lastpercent(0),lastratio(1), laststatus(-1), currenttime(time(NULL)),isvalid(0),profile_logging(1), profile_number(0),profile_active(0) { string pmu_path = "/proc/pmu"; ifstream pmu; pmu.open((pmu_path+"/info").c_str()); if (pmu.is_open()) { #ifdef DEBUG cout << "using pmu" << endl; #endif pmu.close(); apm = new pmu_status(); } else { #ifdef DEBUG cout << "using apm" << endl; #endif apm = new apm_status(); } home=getenv("HOME"); if(home!="") home+="/"; mkdir((home+".ibam").c_str(),0755); ifstream in((home+".ibam/ibam.rc").c_str()); string saveversion; in >> saveversion; if(saveversion==VERSION) in >> lasttime >> lastpercent >> lastratio >> laststatus >> adaptive_damping_battery >> adaptive_damping_charge >> profile_logging >> profile_number >> profile_active; else data_changed=1; // force update in.close(); if(adaptive_damping_battery<2) adaptive_damping_battery=2; if(adaptive_damping_charge<2) adaptive_damping_charge=2; if(adaptive_damping_battery>200) adaptive_damping_battery=200; if(adaptive_damping_charge>200) adaptive_damping_charge=200; currentpercent=apm->percent(); if(currentpercent!=-1) isvalid=1; currentstatus=apm->onBattery()?1:apm->charging()?2:0; if(currentstatus!=laststatus) lastratio=1; } inline void ibam::update(void) { save(); apm->update(); currenttime=time(NULL); currentpercent=apm->percent(); if(currentpercent!=-1) isvalid=1; else isvalid=0; currentstatus=apm->onBattery()?1:apm->charging()?2:0; if(currentstatus!=laststatus) lastratio=1; } inline int ibam::valid(void) const { return isvalid; } inline void ibam::import(void) { { ifstream in(".ibam.battery.rc"); battery.import(in); battery_changed=1; } { ifstream in(".ibam.charge.rc"); charge.import(in); charge_changed=1; } } inline void ibam::load_battery(void) { if(!battery_loaded) { ifstream in((home+".ibam/battery.rc").c_str()); in >> battery; battery_loaded=1; } } inline void ibam::load_charge(void) { if(!charge_loaded) { ifstream in((home+".ibam/charge.rc").c_str()); in >> charge; charge_loaded=1; } } inline void ibam::update_statistics(void) { if(currentstatus==laststatus && currenttime-lasttime1 && last_av>sec_per_min)) adaptive_damping_battery*=1.01; else adaptive_damping_battery*=.99; } if(sec_per_min>=IBAM_MINIMAL_SECONDS_PER_PERCENT && sec_per_min<=IBAM_MAXIMAL_SECONDS_PER_PERCENT) { int i; last_sec_per_min=sec_per_min; last_sec_per_min_prediction=last_av; profile_changed=1; for(i=currentpercent;i<=lastpercent;i++) lastratio=(lastratio*adaptive_damping_battery+battery.add_data(i,sec_per_min))/(adaptive_damping_battery+1); battery_changed=1; data_changed=1; } } else if(currentpercent>lastpercent) // strange data { data_changed=1; // discard if(profile_logging && profile_active) profile_number++; profile_active=0; } break; case 2: // charging if(currentpercent>lastpercent) { load_charge(); double sec_per_min; sec_per_min=(currenttime-lasttime)/double(currentpercent-lastpercent); double last_av=charge.average(lastpercent,currentpercent); if(fabs(last_av-sec_per_min)<1.01*fabs(last_av/lastratio-sec_per_min)) { if((lastratio>1 && last_avsec_per_min)) adaptive_damping_charge*=1.01; else adaptive_damping_charge*=.99; } if(sec_per_min<=IBAM_MAXIMAL_SECONDS_PER_PERCENT && sec_per_min>=IBAM_MINIMAL_SECONDS_PER_PERCENT) { int i; last_sec_per_min=sec_per_min; last_sec_per_min_prediction=last_av; profile_changed=1; for(i=currentpercent;i>=lastpercent;i--) lastratio=(lastratio*adaptive_damping_charge+1/charge.add_data(i,sec_per_min))/(adaptive_damping_charge+1); charge_changed=1; data_changed=1; } } else if(currentpercentseconds(); } inline int ibam::seconds_left_battery(void) { load_battery(); return int(battery.remain(currentpercent)+.5); } inline int ibam::seconds_left_battery_adaptive(void) { load_battery(); return int(battery.remain(currentpercent)*lastratio+.5); } inline int ibam::percent_battery_bios(void) { return apm->percent(); } inline int ibam::percent_battery(void) { load_battery(); return int((100.*seconds_left_battery())/battery.total()+.5); } inline int ibam::seconds_left_charge(void) { load_charge(); return int(charge.inverted_remain(currentpercent)+.5); } inline int ibam::seconds_left_charge_adaptive(void) { load_charge(); return int(charge.inverted_remain(currentpercent)/lastratio+.5); } inline int ibam::percent_charge(void) { load_charge(); return 100-int((100.*seconds_left_charge())/charge.total()+.5); } inline int ibam::seconds_battery_total(void) { load_battery(); return int(battery.total()+.5); } inline int ibam::seconds_battery_total_adaptive(void){ load_battery(); return int(battery.total()*lastratio+.5); } inline int ibam::seconds_charge_total(void) { load_charge(); return int(charge.total()+.5); } inline int ibam::seconds_charge_total_adaptive(void) { load_charge(); return int(charge.total()/lastratio+.5); } inline int ibam::seconds_battery_correction(void) { if(currentstatus!=laststatus || currentstatus==0 || lastpercent!=currentpercent) return 0; if(currentstatus==1) return lasttime-currenttime; load_battery(); load_charge(); return int((currenttime-lasttime)*(battery.average(currentpercent-1,currentpercent+1)/charge.average(currentpercent-1,currentpercent+1))+.5); } inline int ibam::seconds_charge_correction(void) { if(currentstatus!=laststatus || currentstatus==0 || lastpercent!=currentpercent) return 0; if(currentstatus==2) return lasttime-currenttime; load_battery(); load_charge(); return int((currenttime-lasttime)/(battery.average(currentpercent-1,currentpercent+1)/charge.average(currentpercent-1,currentpercent+1))+.5); } inline int ibam::onBattery(void) { return apm->onBattery(); } inline int ibam::charging(void) { return apm->charging(); } inline double percent_data::average_derivation(int a,int b) // average standard derivation from a to b { if(a>b) { int c=a;a=b;b=c; } if(a<0) { a=0; if(b<0) b=0; } if(b>=maxpercents) { b=maxpercents-1; if(a>=maxpercents) a=b; } int i; double su(0); int co(0); for(i=a;i<=b;i++) { if(time_samples[i]) { if(time_deriv_for_percent[i]>0) su+=sqrt(time_deriv_for_percent[i])*time_samples[i]; co+=time_samples[i]; } } if(co) return (su/co); int gotdata=0; for(a--,b++;(a>0 || b=maxpercents) b=maxpercents-1; if(time_deriv_for_percent[a]>0 && time_samples[a]) su+=sqrt(time_deriv_for_percent[a])*time_samples[a]; if(time_deriv_for_percent[b]>0 && time_samples[b]) su+=sqrt(time_deriv_for_percent[b])*time_samples[b]; co+=time_samples[a]+time_samples[b]; if(time_samples[a] || time_samples[b]) gotdata++; } if(co) return (su/co); return 20; }