📄 battery.c
字号:
/*| Copyright (C) 1999-2006 Bill Wilson|| Author: Bill Wilson billw@gkrellm.net| Latest versions might be found at: http://gkrellm.net|| This program is free software which I release under the GNU General Public| License. You may redistribute and/or modify this program under the terms| of that 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. Version 2 is in the| COPYRIGHT file in the top level directory of this distribution.| | To get a copy of the GNU General Puplic License, write to the Free Software| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA*/#include "gkrellm.h"#include "gkrellm-private.h"#include "gkrellm-sysdeps.h"#include <math.h>#define BAT_CONFIG_KEYWORD "battery"typedef enum { BATTERYDISPLAY_PERCENT, BATTERYDISPLAY_TIME, BATTERYDISPLAY_RATE, BATTERYDISPLAY_EOM /* end of modes */ } BatteryDisplayMode; typedef struct { gint id; GkrellmPanel *panel; GkrellmKrell *krell; GkrellmDecal *power_decal; GkrellmDecal *time_decal; GkrellmAlert *alert; gboolean enabled; BatteryDisplayMode display_mode; gfloat charge_rate; /* % / min */ gboolean present, on_line, charging; gint percent; gint time_left; /* In minutes, -1 if minutes unavail */ } Battery;static GList *battery_list;static GkrellmMonitor *mon_battery;static GtkWidget *battery_vbox;static Battery *composite_battery, *launch_battery;static gboolean enable_composite, enable_each, enable_estimate;static gint poll_interval = 5, full_cap_fallback = 5000;static GkrellmLauncher launch;static GkrellmAlert *bat_alert; /* One alert dupped for each battery */static gint style_id;static gboolean alert_units_percent, alert_units_need_estimate_mode;static void (*read_battery_data)();static void create_battery_panel(Battery *bat, gboolean first_create);static gint n_batteries;static Battery *battery_nth(gint n, gboolean create) { Battery *bat; if (n > 10) return NULL; if (n < 0) { if (!composite_battery && create) { bat = g_new0(Battery, 1); battery_list = g_list_prepend(battery_list, bat); bat->id = GKRELLM_BATTERY_COMPOSITE_ID; /* -1 */ composite_battery = bat; gkrellm_alert_dup(&bat->alert, bat_alert); } return composite_battery; } if (composite_battery) ++n; while ( (bat = (Battery *) g_list_nth_data(battery_list, n)) == NULL && create ) { bat = g_new0(Battery, 1); battery_list = g_list_append(battery_list, bat); bat->id = n_batteries++; gkrellm_alert_dup(&bat->alert, bat_alert); } return bat; } /* Themers need to be able to see the battery monitor. */static voidread_battery_demo(void) { gboolean on_line, charging; gint percent, time_left; static gint bump = 60; if (bump <= 5) bump = 60; bump -= 5; on_line = bump > 45; if (on_line) { charging = TRUE; time_left = 200 + (60 - bump) * 20; percent = time_left / 5; } else { charging = FALSE; time_left = bump; percent = 1 + bump; } gkrellm_battery_assign_data(0, TRUE, on_line, charging, percent, time_left); }static gbooleansetup_battery_interface(void) { if (!read_battery_data && !_GK.client_mode && gkrellm_sys_battery_init()) read_battery_data = gkrellm_sys_battery_read_data; if (_GK.demo) read_battery_data = read_battery_demo; return read_battery_data ? TRUE : FALSE; }void gkrellm_battery_client_divert(void (*read_func)()) { read_battery_data = read_func; }voidgkrellm_battery_assign_data(gint n, gboolean present, gboolean on_line, gboolean charging, gint percent, gint time_left) { Battery *bat; bat = battery_nth(n, TRUE); if (!bat) return; bat->present = present; bat->on_line = on_line; bat->charging = charging; bat->percent = percent; bat->time_left = time_left; } /* Help out some laptops with Linux ACPI bugs */gintgkrellm_battery_full_cap_fallback(void) { return full_cap_fallback; }/* -------------------------------------------------------------- *//* estimate (guess-timate?) battery time remaining, based on the rate of discharge (and conversely the time to charge based on the rate of charge). - some BIOS' only provide battery levels, not any estimate of the time remaining Battery charge/discharge characteristics (or, why dc/dt doesn't really work) - the charge/discharge curves of most battery types tend to be very non- linear (http://www.google.com/search?q=battery+charge+discharge+curve) - on discharge, most battery types will initially fall somewhat rapidly from 100 percent, then flatten out and stay somewhat linear until suddenly "dropping out" when nearly depleted (approx. 10-20% capacity). For practical purposes we can consider this point to be the end of the discharge curve. This is simple enough to model via a fixed capacity offset to cut out just at the knee of this curve, and allows us to reasonably approximate the rest of the curve by a linear function and simple dc/dt calculation. - with regard to charging, however, it's not quite so easy. With a constant voltage charger, the battery capacity rises exponentially (charging current decreases as battery terminal voltage rises). The final stages of charging are very gradual, with a relatively long period at "almost but not quite 100%". Unfortunately a linear extrapolation at the beginning of an exponential curve will be a poor approximation to the true expected time to charge, tending to be significantly undervalued. Using an exponential model to estimate time to approx. 90-95% (2.5 * exp. time constant) seems to give a more reasonable fit. That said, the poor relative resolution at higher charge values makes estimating the exponential time constant difficult towards the end of the charge cycle (the curve's very nearly flat). So, I've settled on a mixed model - for c < ~70 I use an exponential model, and switch to linear above that (or if the charge rate seems to have otherwise "flatlined"). Empirically, this method seems to give reasonable results [1] - certainly much better than seeing "0:50:00 to full" for a good half an hour (i.e. as happens with apmd, which uses a linear model for both charging + discharging). Note that a constant-current charger should be pretty well linear all the way along the charge curve, which means the linear rate extrapolation should work well in this case. The user can choose which model they wish to use via estimate_model. [1] I logged my Compaq Armada M300's capacity (via /proc/apm) over one complete discharge/charge cycle (machine was idle the whole time). The discharge curve was linear to approx. 14% when the BIOS alerted of impending doom; upon plugging in the external power supply the capacity rose exponentially to 100%, with a time constant of approx. 0.8 hr (i.e. approx. 2+ hrs to full charge). Linear rate of change calculation: - in an ideal, continuous world, estimated time to 0(100) would simply be the remaining capacity divided by the charge rate ttl = c / dc(t)/dt - alas, the reported battery capacity is bound to integer values thus c(t) is a discontinuous function. i.e. has fairly large steps. And of course then dc/dt is undefined at the discontinuities. - to avoid this issue the rate of charge is determined by the deltas from the start of the last state change (charge/discharge cycle) (time T) ttl(t) = c(t) / ((C - c(t)) / (T - t)) C = charge at time T Furthermore, the rate changes are windowed and filtered to mitigate c(t) transients (e.g. at the start of discharge) and smooth over discontinuities (and fudge for battery characteristics, ref. above).*/#define BAT_SLEEP_DETECT 300 /* interval of >300s => must have slept */#define BAT_DISCHARGE_TRANSIENT 10 /* ignore first 10% of discharge cycle */#define BAT_EMPTY_CAPACITY 12 /* when is the battery "flat"? */#define BAT_RATECHANGE_WINDOW 90 /* allow rate changes after 90s */#define BAT_CHARGE_MODEL_LIMIT 60 /* linear charge model cutover point */#define BAT_RATE_SMOOTHING 0.3 /* rate smoothing weight *//* #define BAT_ESTIMATE_DEBUG */ /* user-provided nominal battery runtimes, hrs (used to determine initial | discharge, stable, charge rate (%/min)) */static gfloat estimate_runtime[2] = {0};static gint estimate_model[2] = {0};static gboolean reset_estimate;static voidestimate_battery_time_left(Battery *bat) { /* ?0 = at time 0 (state change); ?1 = at last "sample" (rate change) */ static time_t t0 = -1, t1; static gint p0, p1; static gint c0; static gfloat rate = 0; static time_t dt; static gint dp; time_t t = time(NULL); /* 1 charging; 0 power on and stable; -1 discharging */ gint charging = bat->charging ? 1 : (bat->on_line ? 0 : -1);#ifdef BAT_ESTIMATE_DEBUG fprintf(stderr, "%ld bc?=%d ac?=%d (%+d) bp=%d\t", t, bat->charging, bat->on_line, charging, bat->percent);#endif if ( reset_estimate || t0 < 0 || c0 != charging || (t - t1) > BAT_SLEEP_DETECT ) { /* init, state change, or sleep/hibernation */ reset_estimate = FALSE; c0 = charging; t0 = t1 = t; if (charging < 0 && (bat->percent > 100 - BAT_DISCHARGE_TRANSIENT)) p0 = p1 = 100 - BAT_DISCHARGE_TRANSIENT; else p0 = p1 = bat->percent; dp = dt = 0; rate = 0.0; /* convert runtime (hrs) to signed rate (%/min) */ if (charging < 0) rate = -100 / (estimate_runtime[0] * 60); else if (charging > 0) rate = 100 / (estimate_runtime[1] * 60);#ifdef BAT_ESTIMATE_DEBUG fprintf(stderr, "[r = %.3f]\t", rate);#endif } else { time_t dt1 = t - t1; /* delta since last rate change */ gint dp1 = bat->percent - p1; /* time for a rate change? */ if ( dt1 > BAT_RATECHANGE_WINDOW && ((charging > 0 && dp1 >= 0) || (charging < 0 && dp1 <= 0)) ) { dt = t - t0; /* since state change */ dp = bat->percent - p0; if (dp1 == 0) /* flatlining (dp/dt = 0) */ rate = (1 - BAT_RATE_SMOOTHING/4) * rate; else rate = BAT_RATE_SMOOTHING * ((gdouble) dp / (gdouble) (dt/60)) + (1 - BAT_RATE_SMOOTHING) * rate;#ifdef BAT_ESTIMATE_DEBUG fprintf(stderr, "%d [dp = %+d dt = %.2f rate = %.3f]\t", (gint) dp1, dp, (gdouble) dt / 60, rate);#endif t1 = t; p1 = bat->percent; } } if (charging && rate != 0.0) /* (dis)charging */ { gfloat eta; gint p = charging > 0 ? 100 - bat->percent : bat->percent - BAT_EMPTY_CAPACITY; if ( charging > 0 && estimate_model[1] && bat->percent < BAT_CHARGE_MODEL_LIMIT && dp > 0 ) /* charging, use exponential: eta =~ 2.5 * time-constant (~=92%) */ eta = -2.5 * dt/60 / (log(1 - (gdouble)dp/(gdouble)(p+dp))); else eta = abs((gdouble)p / rate); /* use linear */#ifdef BAT_ESTIMATE_DEBUG fprintf(stderr, "eta = %.2f\t", eta);#endif /* round off to nearest 5 mins */ bat->time_left = (gint)((eta > 0 ? eta + 2.5: 0) / 5) * 5; bat->charge_rate = rate; } else { bat->time_left = INT_MAX; /* inf */ bat->charge_rate = 0.0; }#ifdef BAT_ESTIMATE_DEBUG fprintf(stderr, "\n");#endif }static voiddraw_time_left_decal(Battery *bat, gboolean force) { GkrellmDecal *d; gchar buf[16]; gint x, w, t; int battery_display_mode = bat->display_mode; static BatteryDisplayMode last_mode = BATTERYDISPLAY_EOM; if (!bat->panel) return; if (bat->time_left == -1) battery_display_mode = BATTERYDISPLAY_PERCENT; if (last_mode != battery_display_mode) force = TRUE; last_mode = bat->display_mode; switch (battery_display_mode) { case BATTERYDISPLAY_TIME: t = bat->time_left; if (t == INT_MAX || t == INT_MIN) snprintf(buf, sizeof(buf), "--"); else snprintf(buf, sizeof(buf), "%2d:%02d", t / 60, t % 60); break; case BATTERYDISPLAY_RATE: /* t is used by draw_decal_text() to see if a refresh is reqd */ t = (gint) (bat->charge_rate * 100.0); snprintf(buf, sizeof(buf), "%0.1f%%/m", bat->charge_rate); break; case BATTERYDISPLAY_PERCENT: default:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -